@@ -19,7 +19,10 @@ import {
1919 InputLabel ,
2020 Select ,
2121 Button ,
22- Link
22+ Link ,
23+ LinearProgress ,
24+ Card ,
25+ CardContent
2326} from '@mui/material' ;
2427import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' ;
2528import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp' ;
@@ -101,14 +104,18 @@ const ProjectSpendingHistory: React.FC<ProjectSpendingHistoryProps> = ({ wbsNum
101104 return false ;
102105 }
103106 }
104- const requestDate = new Date ( request . dateCreated ) ;
105- if ( dateFromFilter ) {
107+ const requestDate =
108+ request . reimbursementStatuses && request . reimbursementStatuses . length > 0
109+ ? new Date ( Math . min ( ...request . reimbursementStatuses . map ( ( status ) => new Date ( status . dateCreated ) . getTime ( ) ) ) )
110+ : null ;
111+
112+ if ( dateFromFilter && requestDate ) {
106113 const fromDate = new Date ( dateFromFilter ) ;
107114 if ( requestDate < fromDate ) {
108115 return false ;
109116 }
110117 }
111- if ( dateToFilter ) {
118+ if ( dateToFilter && requestDate ) {
112119 const toDate = new Date ( dateToFilter ) ;
113120 toDate . setHours ( 23 , 59 , 59 , 999 ) ;
114121 if ( requestDate > toDate ) {
@@ -161,6 +168,22 @@ const ProjectSpendingHistory: React.FC<ProjectSpendingHistoryProps> = ({ wbsNum
161168 setAmountMaxFilter ( '' ) ;
162169 } ;
163170
171+ const budgetInfo = useMemo ( ( ) => {
172+ if ( ! project || ! grouped . length ) return null ;
173+
174+ const totalBudget = project . budget ; // Budget is in cents
175+ const totalSpent = grouped . reduce ( ( sum , { request } ) => sum + ( request . totalCost || 0 ) , 0 ) ; // Total cost is in cents
176+ const budgetRemaining = totalBudget - totalSpent ;
177+ const budgetUsedPercentage = totalBudget > 0 ? ( totalSpent / totalBudget ) * 100 : 0 ;
178+
179+ return {
180+ totalBudget : totalBudget / 100 , // Convert to dollars
181+ totalSpent : totalSpent / 100 , // Convert to dollars
182+ budgetRemaining : budgetRemaining / 100 , // Convert to dollars
183+ budgetUsedPercentage : Math . min ( budgetUsedPercentage , 100 ) // Cap at 100%
184+ } ;
185+ } , [ project , grouped ] ) ;
186+
164187 const hasActiveFilters =
165188 submitterFilter || statusFilter || dateFromFilter || dateToFilter || amountMinFilter || amountMaxFilter ;
166189
@@ -212,6 +235,67 @@ const ProjectSpendingHistory: React.FC<ProjectSpendingHistoryProps> = ({ wbsNum
212235 </ Box >
213236 </ Box >
214237
238+ { budgetInfo && (
239+ < Card sx = { { mb : 3 , backgroundColor : '#2a2a2a' , border : '1px solid #444' } } >
240+ < CardContent >
241+ < Grid container spacing = { 3 } alignItems = "center" >
242+ < Grid item xs = { 12 } md = { 8 } >
243+ < Typography variant = "h6" sx = { { mb : 1 } } >
244+ Budget Overview
245+ </ Typography >
246+ < Box sx = { { mb : 2 } } >
247+ < Box sx = { { display : 'flex' , justifyContent : 'space-between' , mb : 1 } } >
248+ < Typography variant = "body2" color = "textSecondary" >
249+ Spent: ${ budgetInfo . totalSpent . toFixed ( 2 ) }
250+ </ Typography >
251+ < Typography variant = "body2" color = "textSecondary" >
252+ Total Budget: ${ budgetInfo . totalBudget . toFixed ( 2 ) }
253+ </ Typography >
254+ </ Box >
255+ < LinearProgress
256+ variant = "determinate"
257+ value = { budgetInfo . budgetUsedPercentage }
258+ sx = { {
259+ height : 8 ,
260+ borderRadius : 5 ,
261+ backgroundColor : '#444' ,
262+ '& .MuiLinearProgress-bar' : {
263+ borderRadius : 5 ,
264+ backgroundColor :
265+ budgetInfo . budgetUsedPercentage > 90
266+ ? '#f44336'
267+ : budgetInfo . budgetUsedPercentage > 75
268+ ? '#ff9800'
269+ : '#4caf50'
270+ }
271+ } }
272+ />
273+ </ Box >
274+ </ Grid >
275+ < Grid item xs = { 12 } md = { 4 } >
276+ < Box sx = { { textAlign : { xs : 'left' , md : 'right' } } } >
277+ < Typography variant = "body2" color = "textSecondary" sx = { { mb : 0.5 } } >
278+ Budget Remaining
279+ </ Typography >
280+ < Typography
281+ variant = "h6"
282+ sx = { {
283+ color : budgetInfo . budgetRemaining >= 0 ? '#4caf50' : '#f44336' ,
284+ fontWeight : 'bold'
285+ } }
286+ >
287+ ${ budgetInfo . budgetRemaining . toFixed ( 2 ) }
288+ </ Typography >
289+ < Typography variant = "caption" color = "textSecondary" >
290+ ({ budgetInfo . budgetUsedPercentage . toFixed ( 1 ) } % used)
291+ </ Typography >
292+ </ Box >
293+ </ Grid >
294+ </ Grid >
295+ </ CardContent >
296+ </ Card >
297+ ) }
298+
215299 { showFilters && (
216300 < Box sx = { { mb : 3 , p : 2 , border : '1px solid #444' , borderRadius : 1 , backgroundColor : '#1a1a1a' } } >
217301 < Typography variant = "h6" sx = { { mb : 2 } } >
@@ -306,20 +390,24 @@ const ProjectSpendingHistory: React.FC<ProjectSpendingHistoryProps> = ({ wbsNum
306390 < TableRow >
307391 < TableCell />
308392 < TableCell > Submitter / RR Link</ TableCell >
309- < TableCell > Date</ TableCell >
393+ < TableCell > Description</ TableCell >
394+ < TableCell > Date Submitted</ TableCell >
310395 < TableCell > Status</ TableCell >
311396 < TableCell align = "right" > Total Amount</ TableCell >
312397 </ TableRow >
313398 </ TableHead >
314399 < TableBody >
315400 { filteredData . map ( ( { request, materials } ) => {
401+ const hasMaterials = materials . length > 0 ;
316402 return (
317403 < React . Fragment key = { request . reimbursementRequestId } >
318404 < TableRow hover >
319405 < TableCell >
320- < IconButton size = "small" onClick = { ( ) => handleToggleRow ( request . reimbursementRequestId ) } >
321- { openRows [ request . reimbursementRequestId ] ? < KeyboardArrowUpIcon /> : < KeyboardArrowDownIcon /> }
322- </ IconButton >
406+ { hasMaterials ? (
407+ < IconButton size = "small" onClick = { ( ) => handleToggleRow ( request . reimbursementRequestId ) } >
408+ { openRows [ request . reimbursementRequestId ] ? < KeyboardArrowUpIcon /> : < KeyboardArrowDownIcon /> }
409+ </ IconButton >
410+ ) : null }
323411 </ TableCell >
324412 < TableCell >
325413 < Link
@@ -332,7 +420,23 @@ const ProjectSpendingHistory: React.FC<ProjectSpendingHistoryProps> = ({ wbsNum
332420 'N/A' }
333421 </ Link >
334422 </ TableCell >
335- < TableCell > { new Date ( request . dateCreated ) . toLocaleDateString ( ) } </ TableCell >
423+ < TableCell >
424+ < Typography variant = "body2" sx = { { maxWidth : 300 , overflow : 'hidden' , textOverflow : 'ellipsis' } } >
425+ { request . accountCode ?. name ||
426+ request . reimbursementProducts ?. map ( ( p ) => p . name ) . join ( ', ' ) ||
427+ request . vendor ?. name ||
428+ 'No description available' }
429+ </ Typography >
430+ </ TableCell >
431+ < TableCell >
432+ { request . reimbursementStatuses && request . reimbursementStatuses . length > 0
433+ ? new Date (
434+ Math . min (
435+ ...request . reimbursementStatuses . map ( ( status ) => new Date ( status . dateCreated ) . getTime ( ) )
436+ )
437+ ) . toLocaleDateString ( )
438+ : '' }
439+ </ TableCell >
336440 < TableCell >
337441 < Chip
338442 label = { request . reimbursementStatuses ?. [ 0 ] ?. type ?. replace ( / _ / g, ' ' ) || 'N/A' }
@@ -342,14 +446,14 @@ const ProjectSpendingHistory: React.FC<ProjectSpendingHistoryProps> = ({ wbsNum
342446 </ TableCell >
343447 < TableCell align = "right" > ${ ( request . totalCost / 100 ) ?. toFixed ( 2 ) || '0.00' } </ TableCell >
344448 </ TableRow >
345- < TableRow >
346- < TableCell style = { { paddingBottom : 0 , paddingTop : 0 } } colSpan = { 5 } >
347- < Collapse in = { openRows [ request . reimbursementRequestId ] } timeout = "auto" unmountOnExit >
348- < Box sx = { { margin : 1 , background : '#181818' , borderRadius : 1 , p : 2 } } >
349- < Typography variant = "subtitle1" sx = { { color : 'secondary.main ', mb : 1 } } >
350- Line Items
351- </ Typography >
352- { materials . length > 0 ? (
449+ { hasMaterials && (
450+ < TableRow >
451+ < TableCell style = { { paddingBottom : 0 , paddingTop : 0 } } colSpan = { 6 } >
452+ < Collapse in = { openRows [ request . reimbursementRequestId ] } timeout = "auto" unmountOnExit >
453+ < Box sx = { { margin : 1 , background : '#181818 ', borderRadius : 1 , p : 2 } } >
454+ < Typography variant = "subtitle1" sx = { { color : 'secondary.main' , mb : 1 } } >
455+ Line Items
456+ </ Typography >
353457 < Table size = "small" >
354458 < TableHead >
355459 < TableRow >
@@ -368,16 +472,11 @@ const ProjectSpendingHistory: React.FC<ProjectSpendingHistoryProps> = ({ wbsNum
368472 ) ) }
369473 </ TableBody >
370474 </ Table >
371- ) : (
372- < Typography variant = "body2" color = "textSecondary" >
373- This reimbursement request has no associated BOM line items. It may have been created
374- independently or with non-BOM products.
375- </ Typography >
376- ) }
377- </ Box >
378- </ Collapse >
379- </ TableCell >
380- </ TableRow >
475+ </ Box >
476+ </ Collapse >
477+ </ TableCell >
478+ </ TableRow >
479+ ) }
381480 </ React . Fragment >
382481 ) ;
383482 } ) }
0 commit comments