1+ import { CheckCircle , AlertCircle } from 'lucide-react' ;
2+ import { calculateTotalCost } from '../utils/purchaseHelpers' ;
3+
4+ export default function ApprovalSection ( {
5+ purchase,
6+ user,
7+ validation,
8+ approvalLoading,
9+ onApprove,
10+ onWithdraw,
11+ canApproveRequest,
12+ canOverwriteApproval,
13+ isApproverValid,
14+ inDisallowedState
15+ } ) {
16+ const totalCost = parseFloat ( calculateTotalCost ( purchase ) . replace ( / [ ^ 0 - 9 . - ] + / g, '' ) ) || 0 ;
17+
18+ const getUserApprovalPermissions = ( ) => {
19+ const userName = user . name ;
20+ const permissions = {
21+ canStudentApprove : false ,
22+ studentApprovalLimit : 0 ,
23+ canMentorApprove : false ,
24+ mentorApprovalLimit : 0
25+ } ;
26+
27+ if ( validation [ 'Presidents' ] ?. includes ( userName ) ) {
28+ permissions . canStudentApprove = true ;
29+ permissions . studentApprovalLimit = Infinity ;
30+ } else if ( validation [ 'Leadership' ] ?. includes ( userName ) ) {
31+ permissions . canStudentApprove = true ;
32+ permissions . studentApprovalLimit = 500 ;
33+ }
34+
35+ if ( validation [ 'Mentors' ] ?. includes ( userName ) ) {
36+ permissions . canMentorApprove = true ;
37+ permissions . mentorApprovalLimit = 500 ;
38+ } else if ( validation [ 'Directors' ] ?. includes ( userName ) ) {
39+ permissions . canMentorApprove = true ;
40+ permissions . mentorApprovalLimit = Infinity ;
41+ }
42+
43+ return permissions ;
44+ } ;
45+
46+ return (
47+ < div className = "border-t pt-4 md:pt-6" >
48+ < h3 className = "text-base md:text-lg font-semibold text-gray-800 mb-3 md:mb-4" > Approvals</ h3 >
49+
50+ { /* Warning if over $2000 */ }
51+ { totalCost > 2000 && (
52+ < div className = "bg-red-50 border border-red-200 rounded-lg p-3 md:p-4 mb-3 md:mb-4 flex items-start animate-slideDown" >
53+ < AlertCircle className = "w-5 h-5 text-red-600 mr-3 mt-0.5 flex-shrink-0" />
54+ < div >
55+ < p className = "font-semibold text-red-800 text-sm md:text-base" > Cannot Approve</ p >
56+ < p className = "text-xs md:text-sm text-red-700" >
57+ Requests over $2,000 cannot be approved through this system.
58+ </ p >
59+ </ div >
60+ </ div >
61+ ) }
62+
63+ { /* No Permissions */ }
64+ { ! getUserApprovalPermissions ( ) . canStudentApprove && ! getUserApprovalPermissions ( ) . canMentorApprove && (
65+ < div className = "bg-yellow-50 border border-yellow-200 rounded-lg p-3 md:p-4 mb-3 md:mb-4 flex items-start" >
66+ < AlertCircle className = "w-5 h-5 text-yellow-600 mr-3 mt-0.5 flex-shrink-0" />
67+ < div >
68+ < p className = "font-semibold text-yellow-800 text-sm md:text-base" > No Approval Permissions</ p >
69+ < p className = "text-xs md:text-sm text-yellow-700" >
70+ You are not authorized to approve purchase requests.
71+ </ p >
72+ </ div >
73+ </ div >
74+ ) }
75+
76+ { /* Student Approver */ }
77+ < div className = "bg-gray-50 p-3 md:p-4 rounded-lg mb-3" >
78+ < p className = "text-xs md:text-sm text-gray-500 mb-2 font-medium" > Student Approver</ p >
79+ { purchase [ 'S Approver' ] ? (
80+ < div className = "space-y-2" >
81+ < div className = "flex items-center gap-2" >
82+ { isApproverValid ( purchase [ 'S Approver' ] , 'student' , totalCost ) ? (
83+ < CheckCircle className = "w-5 h-5 text-green-600 flex-shrink-0" />
84+ ) : (
85+ < AlertCircle className = "w-5 h-5 text-red-600 flex-shrink-0" />
86+ ) }
87+ < div className = "flex-1 min-w-0" >
88+ < p className = "font-semibold text-gray-800 text-sm md:text-base truncate" >
89+ { purchase [ 'S Approver' ] }
90+ </ p >
91+ { ! isApproverValid ( purchase [ 'S Approver' ] , 'student' , totalCost ) && (
92+ < p className = "text-xs text-red-600" > Invalid approver for this amount</ p >
93+ ) }
94+ </ div >
95+ </ div >
96+ < div className = "flex flex-wrap gap-2" >
97+ { purchase [ 'S Approver' ] === user . name && ! inDisallowedState ( purchase ) && (
98+ < button
99+ onClick = { ( ) => onWithdraw ( 'student' ) }
100+ disabled = { approvalLoading }
101+ className = "flex-1 sm:flex-none bg-red-600 hover:bg-red-700 disabled:bg-gray-400 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 text-sm transform active:scale-95"
102+ >
103+ { approvalLoading ? 'Withdrawing...' : 'Withdraw' }
104+ </ button >
105+ ) }
106+ { canOverwriteApproval ( purchase , 'student' ) && (
107+ < button
108+ onClick = { ( ) => onApprove ( 'student' , true ) }
109+ disabled = { approvalLoading }
110+ className = "flex-1 sm:flex-none bg-orange-600 hover:bg-orange-700 disabled:bg-gray-400 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 text-sm transform active:scale-95"
111+ >
112+ { approvalLoading ? 'Overwriting...' : 'Overwrite' }
113+ </ button >
114+ ) }
115+ </ div >
116+ </ div >
117+ ) : (
118+ < div className = "flex items-center justify-between gap-3" >
119+ < p className = "text-gray-500 italic text-sm" > Not yet approved</ p >
120+ { ( ( ) => {
121+ const approval = canApproveRequest ( purchase , 'student' ) ;
122+ return approval . canApprove ? (
123+ < button
124+ onClick = { ( ) => onApprove ( 'student' , false ) }
125+ disabled = { approvalLoading }
126+ className = "bg-green-600 hover:bg-green-700 disabled:bg-gray-400 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 text-sm whitespace-nowrap transform active:scale-95"
127+ >
128+ { approvalLoading ? 'Approving...' : 'Approve' }
129+ </ button >
130+ ) : null ;
131+ } ) ( ) }
132+ </ div >
133+ ) }
134+ </ div >
135+
136+ { /* Mentor Approver */ }
137+ < div className = "bg-gray-50 p-3 md:p-4 rounded-lg" >
138+ < p className = "text-xs md:text-sm text-gray-500 mb-2 font-medium" > Mentor Approver</ p >
139+ { purchase [ 'M Approver' ] ? (
140+ < div className = "space-y-2" >
141+ < div className = "flex items-center gap-2" >
142+ { isApproverValid ( purchase [ 'M Approver' ] , 'mentor' , totalCost ) ? (
143+ < CheckCircle className = "w-5 h-5 text-green-600 flex-shrink-0" />
144+ ) : (
145+ < AlertCircle className = "w-5 h-5 text-red-600 flex-shrink-0" />
146+ ) }
147+ < div className = "flex-1 min-w-0" >
148+ < p className = "font-semibold text-gray-800 text-sm md:text-base truncate" >
149+ { purchase [ 'M Approver' ] }
150+ </ p >
151+ { ! isApproverValid ( purchase [ 'M Approver' ] , 'mentor' , totalCost ) && (
152+ < p className = "text-xs text-red-600" > Invalid approver for this amount</ p >
153+ ) }
154+ </ div >
155+ </ div >
156+ < div className = "flex flex-wrap gap-2" >
157+ { purchase [ 'M Approver' ] === user . name && ! inDisallowedState ( purchase ) && (
158+ < button
159+ onClick = { ( ) => onWithdraw ( 'mentor' ) }
160+ disabled = { approvalLoading }
161+ className = "flex-1 sm:flex-none bg-red-600 hover:bg-red-700 disabled:bg-gray-400 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 text-sm transform active:scale-95"
162+ >
163+ { approvalLoading ? 'Withdrawing...' : 'Withdraw' }
164+ </ button >
165+ ) }
166+ { canOverwriteApproval ( purchase , 'mentor' ) && (
167+ < button
168+ onClick = { ( ) => onApprove ( 'mentor' , true ) }
169+ disabled = { approvalLoading }
170+ className = "flex-1 sm:flex-none bg-orange-600 hover:bg-orange-700 disabled:bg-gray-400 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 text-sm transform active:scale-95"
171+ >
172+ { approvalLoading ? 'Overwriting...' : 'Overwrite' }
173+ </ button >
174+ ) }
175+ </ div >
176+ </ div >
177+ ) : (
178+ < div className = "flex items-center justify-between gap-3" >
179+ < p className = "text-gray-500 italic text-sm" > Not yet approved</ p >
180+ { ( ( ) => {
181+ const approval = canApproveRequest ( purchase , 'mentor' ) ;
182+ return approval . canApprove ? (
183+ < button
184+ onClick = { ( ) => onApprove ( 'mentor' , false ) }
185+ disabled = { approvalLoading }
186+ className = "bg-indigo-600 hover:bg-indigo-700 disabled:bg-gray-400 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 text-sm whitespace-nowrap transform active:scale-95"
187+ >
188+ { approvalLoading ? 'Approving...' : 'Approve' }
189+ </ button >
190+ ) : null ;
191+ } ) ( ) }
192+ </ div >
193+ ) }
194+ </ div >
195+ </ div >
196+ ) ;
197+ }
0 commit comments