@liiift-studio/sales-portal 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +461 -0
  2. package/SETUP.md +230 -0
  3. package/api/getAnalytics.d.ts +38 -0
  4. package/api/getAnalytics.js +346 -0
  5. package/api/getBalanceTransactions.d.ts +29 -0
  6. package/api/getBalanceTransactions.js +125 -0
  7. package/api/getDesignerInfo.d.ts +37 -0
  8. package/api/getDesignerInfo.js +98 -0
  9. package/api/getDesigners.d.ts +28 -0
  10. package/api/getDesigners.js +63 -0
  11. package/api/getPreviousSales.d.ts +23 -0
  12. package/api/getPreviousSales.js +82 -0
  13. package/api/getSales.d.ts +29 -0
  14. package/api/getSales.js +50 -0
  15. package/api/getSalesRange.d.ts +23 -0
  16. package/api/getSalesRange.js +58 -0
  17. package/api/utils/authMiddleware.js +84 -0
  18. package/api/utils/dateUtils.js +69 -0
  19. package/api/utils/feeCalculator.js +148 -0
  20. package/api/utils/processors/invoiceProcessor.js +337 -0
  21. package/api/utils/processors/paymentProcessor.js +462 -0
  22. package/api/utils/salesDataProcessing.js +596 -0
  23. package/api/utils/salesDataProcessor.js +224 -0
  24. package/api/utils/stripeFetcher.js +248 -0
  25. package/components/DateRangeSalesTable.js +1072 -0
  26. package/components/DebugValues.js +48 -0
  27. package/components/LicenseTypeList.js +193 -0
  28. package/components/LoginForm.js +219 -0
  29. package/components/PeriodComparison.js +501 -0
  30. package/components/Sales.js +773 -0
  31. package/components/SalesChart.js +307 -0
  32. package/components/SalesPortalPage.js +147 -0
  33. package/components/SalesTable.js +677 -0
  34. package/components/SummaryCards.js +345 -0
  35. package/components/TopPerformers.js +331 -0
  36. package/components/TypefaceList.js +154 -0
  37. package/components/table-columns.js +70 -0
  38. package/components/table-row-cells.js +295 -0
  39. package/data/countryCode.json +318 -0
  40. package/hooks/useSalesDateQuery.d.ts +20 -0
  41. package/hooks/useSalesDateQuery.js +71 -0
  42. package/index.d.ts +172 -0
  43. package/index.js +33 -0
  44. package/package.json +87 -0
  45. package/styles/sales-portal.module.scss +383 -0
  46. package/styles/sales-portal.theme.d.ts +5 -0
  47. package/styles/sales-portal.theme.js +799 -0
  48. package/utils/currencyUtils.d.ts +20 -0
  49. package/utils/currencyUtils.js +79 -0
  50. package/utils/salesDataProcessing.d.ts +44 -0
  51. package/utils/salesDataProcessing.js +596 -0
  52. package/utils/useSalesDateQuery.js +71 -0
@@ -0,0 +1,677 @@
1
+ // Sales table component for displaying detailed sales information
2
+ import React, { useState, useEffect, useMemo } from 'react';
3
+ import { useRouter } from 'next/router';
4
+ var slugify = require('slugify');
5
+ import {
6
+ Grid,
7
+ Button,
8
+ TableRow,
9
+ TableHead,
10
+ TableContainer,
11
+ TableCell,
12
+ TableBody,
13
+ Table,
14
+ Typography,
15
+ Menu,
16
+ MenuItem,
17
+ Checkbox,
18
+ ListItemText,
19
+ IconButton,
20
+ Tooltip,
21
+ Box,
22
+ CircularProgress
23
+ } from '@mui/material';
24
+ import DownloadIcon from '@mui/icons-material/Download';
25
+ import TuneIcon from '@mui/icons-material/Tune';
26
+ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
27
+ import WarningIcon from '@mui/icons-material/Warning';
28
+ import InfoIcon from '@mui/icons-material/Info';
29
+ import styles from '../styles/sales-portal.module.scss';
30
+ import { TableRowCells, getCellValue } from './table-row-cells';
31
+ import { COLUMNS } from './table-columns';
32
+ import { DataGrid } from '@mui/x-data-grid';
33
+
34
+ export function SalesTable({ sales = [], designer = {}, admin = false, loading = false, date = new Date(), updateLoadingState = () => {} }) {
35
+ const [anchorEl, setAnchorEl] = useState(null);
36
+ const [selectedColumns, setSelectedColumns] = useState([
37
+ 'orderNumber',
38
+ 'total',
39
+ 'amountDiscounted',
40
+ 'taxAmount',
41
+ 'preTaxTotal',
42
+ 'date',
43
+ 'description',
44
+ 'typeface',
45
+ 'refund'
46
+ ]);
47
+
48
+ // Add sortModel state to manage sorting
49
+ const [sortModel, setSortModel] = useState([
50
+ {
51
+ field: 'date',
52
+ sort: 'desc'
53
+ }
54
+ ]);
55
+
56
+ // Filter model state for implementing filtering
57
+ const [filterModel, setFilterModel] = useState({
58
+ items: [],
59
+ quickFilterValues: []
60
+ });
61
+
62
+ // Reconciliation state
63
+ const [reconciliationData, setReconciliationData] = useState({
64
+ isLoading: false,
65
+ isReconciled: false,
66
+ totalBalanceChange: 0,
67
+ grossSales: 0,
68
+ difference: 0,
69
+ error: null
70
+ });
71
+
72
+ const handleMenuClick = (event) => {
73
+ setAnchorEl(event.currentTarget);
74
+ };
75
+
76
+ const handleMenuClose = () => {
77
+ setAnchorEl(null);
78
+ };
79
+
80
+ const handleColumnToggle = (columnId) => {
81
+ setSelectedColumns(prev => {
82
+ if (prev.includes(columnId)) {
83
+ return prev.filter(id => id !== columnId);
84
+ } else {
85
+ return [...prev, columnId];
86
+ }
87
+ });
88
+ };
89
+
90
+ const handleSelectAll = () => {
91
+ setSelectedColumns(COLUMNS.filter(col => !col.adminOnly || admin).map(col => col.id));
92
+ };
93
+
94
+ const handleSelectNone = () => {
95
+ setSelectedColumns([]);
96
+ };
97
+
98
+ // Memoize the columns configuration
99
+ const memoizedColumns = useMemo(() =>
100
+ COLUMNS
101
+ .filter(column => selectedColumns.includes(column.id) && (!column.adminOnly || admin))
102
+ .map(column => ({
103
+ field: column.id,
104
+ headerName: column.label || column.headerName,
105
+ flex: 1,
106
+ minWidth: 120,
107
+ // Explicitly enable sorting
108
+ sortable: true,
109
+ // Add custom comparator for currency and date values
110
+ sortComparator: (v1, v2, param1, param2) => {
111
+ // Extract the actual values from complex cell data
112
+ const getValue = (params) => {
113
+ if (!params.row) return '';
114
+ const result = getCellValue({
115
+ column,
116
+ sale: params.row.sale || null,
117
+ designer: params.row.designer || designer,
118
+ admin: params.row.admin || admin,
119
+ refund: params.row.refund || null,
120
+ isNoData: !params.row.sale,
121
+ color: params.row.color || ''
122
+ });
123
+
124
+ // Handle React elements (like links)
125
+ if (typeof result.value === 'object' && React.isValidElement(result.value)) {
126
+ return result.value.props.children || '';
127
+ }
128
+
129
+ return result.value;
130
+ };
131
+
132
+ // Get the actual values to compare
133
+ const val1 = getValue({ row: param1.api.getRow(param1.id) });
134
+ const val2 = getValue({ row: param2.api.getRow(param2.id) });
135
+
136
+ // For currency values, extract the number for comparison
137
+ if (column.id === 'total' || column.id === 'amountDiscounted' ||
138
+ column.id === 'preTaxTotal' || column.id === 'taxAmount' ||
139
+ column.id === 'stripeFees') {
140
+ // Extract numbers from currency strings like "$1,234.56"
141
+ const extractNumber = (str) => {
142
+ if (typeof str !== 'string') return str;
143
+ const match = str.replace(/[^0-9.-]+/g, '');
144
+ return match ? parseFloat(match) : 0;
145
+ };
146
+
147
+ return extractNumber(val1) - extractNumber(val2);
148
+ }
149
+
150
+ // For dates
151
+ if (column.id === 'date') {
152
+ const date1 = new Date(val1);
153
+ const date2 = new Date(val2);
154
+ return date1 - date2;
155
+ }
156
+
157
+ // Default string comparison
158
+ if (typeof val1 === 'string' && typeof val2 === 'string') {
159
+ return val1.localeCompare(val2);
160
+ }
161
+
162
+ // Fallback to default comparison
163
+ return v1 < v2 ? -1 : v1 > v2 ? 1 : 0;
164
+ },
165
+ renderCell: (params) => {
166
+ if (!params.row) return '';
167
+
168
+ const result = getCellValue({
169
+ column,
170
+ sale: params.row.sale || null,
171
+ designer: params.row.designer || designer,
172
+ admin: params.row.admin || admin,
173
+ refund: params.row.refund || null,
174
+ isNoData: !params.row.sale,
175
+ color: params.row.color || ''
176
+ });
177
+
178
+ return <div style={{ color: params.row.color || undefined }}>{result.value}</div>;
179
+ }
180
+ }))
181
+ , [selectedColumns, admin, designer]);
182
+
183
+ // Calculate gross sales from the table's Gross Total column
184
+ function calculateGrossSales() {
185
+ let grossSales = 0;
186
+ sales.forEach(sale => {
187
+ sale?.refunds?.map((refund, i) => {
188
+ grossSales -= (refund.adjustedTotal || 0);
189
+ })
190
+ grossSales += sale?.shippingProvision ? sale?.total : sale?.totalWithTax;
191
+ });
192
+ return grossSales;
193
+ }
194
+
195
+ // Fetch balance transactions from Stripe
196
+ useEffect(() => {
197
+ if (!designer?.user || !designer?.password || !date || !admin || !sales) return;
198
+
199
+ const fetchBalanceTransactions = async () => {
200
+ setReconciliationData(prev => ({ ...prev, isLoading: true, error: null }));
201
+
202
+ try {
203
+ const response = await fetch('/api/sales-portal/getBalanceTransactions', {
204
+ method: 'POST',
205
+ headers: { 'Content-Type': 'application/json' },
206
+ body: JSON.stringify({
207
+ user: designer?.user,
208
+ password: designer?.password,
209
+ date,
210
+ admin: designer?.admin
211
+ }),
212
+ });
213
+
214
+ const data = await response.json();
215
+
216
+ if (data.success) {
217
+ const totalBalanceChange = data.data.totalBalanceChange;
218
+ const grossSales = calculateGrossSales();
219
+
220
+ setReconciliationData(prev => ({
221
+ ...prev,
222
+ totalBalanceChange,
223
+ grossSales,
224
+ difference: totalBalanceChange - grossSales,
225
+ isReconciled: Math.abs(totalBalanceChange - grossSales) < 1, // Allow for rounding differences
226
+ isLoading: false
227
+ }));
228
+ } else {
229
+ setReconciliationData(prev => ({
230
+ ...prev,
231
+ error: data.message || 'Failed to fetch balance transactions',
232
+ isLoading: false
233
+ }));
234
+ }
235
+ } catch (error) {
236
+ console.error('Error fetching balance transactions:', error);
237
+ setReconciliationData(prev => ({
238
+ ...prev,
239
+ error: error.message || 'Failed to fetch balance transactions',
240
+ isLoading: false
241
+ }));
242
+ }
243
+ };
244
+
245
+ fetchBalanceTransactions();
246
+ }, [designer?.user, designer?.password, designer?.admin, date, admin, sales]);
247
+
248
+ // Early return if no designer data is available
249
+ if (!designer?.user || !designer?.password) {
250
+ return (
251
+ <Grid container sx={{ mt: 16 }} className={styles.salesSection}>
252
+ <Grid item xs={12}>
253
+ <Typography variant="h6" sx={{ color: 'var(--red, red)' }}>
254
+ Error: Designer credentials not available
255
+ </Typography>
256
+ </Grid>
257
+ </Grid>
258
+ );
259
+ }
260
+
261
+ /**
262
+ * Downloads sales data as CSV file
263
+ * @param {string} separator - CSV separator character
264
+ * @param {Object} designer - Designer information
265
+ * @param {Date} date - Current date
266
+ */
267
+ const downloadSalesData = (separator = ',', designer, date, selectedCols = selectedColumns) => {
268
+ updateLoadingState('csvDownload', true);
269
+
270
+ try {
271
+ // Get filtered columns
272
+ const filteredColumns = COLUMNS
273
+ .filter(column => selectedCols.includes(column.id) && (!column.adminOnly || admin));
274
+
275
+ // Generate row data programmatically instead of relying on DOM
276
+ const rows = !sales?.length
277
+ ? [{
278
+ sale: null,
279
+ designer,
280
+ admin,
281
+ isNoData: true,
282
+ color: "red"
283
+ }]
284
+ : sales.flatMap(sale => {
285
+ const rowsData = [];
286
+
287
+ // Add refund rows if any
288
+ if (sale?.refunds?.length > 0) {
289
+ sale.refunds.forEach((refund) => {
290
+ rowsData.push({
291
+ sale,
292
+ designer,
293
+ admin,
294
+ refund,
295
+ color: "red"
296
+ });
297
+ });
298
+ }
299
+
300
+ // Add original sale row
301
+ rowsData.push({
302
+ sale,
303
+ designer,
304
+ admin,
305
+ color: sale?.disputed ? "red!important" : "inherit"
306
+ });
307
+
308
+ return rowsData;
309
+ });
310
+
311
+ // Generate CSV header row
312
+ const headerRow = filteredColumns.map(column => `"${column.label}"`).join(separator);
313
+
314
+ // Generate CSV data rows
315
+ const dataRows = rows.map(row => {
316
+ return filteredColumns.map(column => {
317
+ const result = getCellValue({
318
+ column,
319
+ sale: row.sale,
320
+ designer: row.designer,
321
+ admin: row.admin,
322
+ refund: row.refund,
323
+ isNoData: row.isNoData,
324
+ color: row.color
325
+ });
326
+
327
+ // Format the value for CSV
328
+ let value = result.value;
329
+ if (typeof value === 'object' && React.isValidElement(value)) {
330
+ // If value is a React element (like a link), extract its text
331
+ value = value.props.children || '';
332
+ }
333
+
334
+ // Clean and format cell data
335
+ let data = String(value)
336
+ .replace(/(\r\n|\n|\r)/gm, '')
337
+ .replace(/(\s\s)/gm, ' ')
338
+ .replace(/"/g, '""');
339
+
340
+ return `"${data}"`;
341
+ }).join(separator);
342
+ });
343
+
344
+ // Combine header and data rows
345
+ const csv_string = [headerRow, ...dataRows].join('\n');
346
+
347
+ // Download it
348
+ const filename = `${slugify(designer.user, { lower: true, remove: /[*/+~.()'"!:@]/g, strict: true })}-${slugify(new Date(date).toUTCString(), { lower: true, remove: /[*/+~.()'"!:@]/g, strict: true })}.csv`;
349
+ const link = document.createElement('a');
350
+ link.style.display = 'none';
351
+ link.setAttribute('target', '_blank');
352
+ link.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv_string));
353
+ link.setAttribute('download', filename);
354
+ document.body.appendChild(link);
355
+ link.click();
356
+ document.body.removeChild(link);
357
+ } catch (error) {
358
+ console.error('Error downloading sales data:', error);
359
+ } finally {
360
+ updateLoadingState('csvDownload', false);
361
+ }
362
+ };
363
+
364
+ return (
365
+ <Grid
366
+ container
367
+ data-disabled={loading}
368
+ data-loading={loading}
369
+ className='salesTable-section'
370
+ >
371
+ {/* Reconciliation Check - Only show for admin users */}
372
+ {admin && !reconciliationData.error && sales.length > 0 && (
373
+ <Grid item xs={12} sx={{ mb: 2 }}>
374
+ <Box
375
+ sx={{
376
+ display: 'inline-block',
377
+ gap: 2,
378
+ p: 2,
379
+ mt: "20px",
380
+ borderRadius: '4px',
381
+ border: reconciliationData.isReconciled
382
+ ? '2px solid var(--green, green)'
383
+ : '2px solid var(--red, red)',
384
+ color: reconciliationData.isReconciled
385
+ ? 'var(--green, green)'
386
+ : 'var(--red, red)',
387
+ }}
388
+ >
389
+ {reconciliationData.isLoading ? (
390
+ <Typography variant="body1"><CircularProgress size={24} /> Checking reconciliation...</Typography>
391
+ ) : reconciliationData.isReconciled ? (
392
+ <Typography variant="body1" sx={{ fontWeight: 'bold' }}>
393
+ <CheckCircleIcon /> Reconciled
394
+ </Typography>
395
+ ) : (
396
+ <Box sx={{ display: 'flex', alignItems: 'center' }}>
397
+ <Tooltip title="The difference between Stripe balance change and gross sales">
398
+ <Typography variant="body1" sx={{ fontWeight: 'bold', display: 'flex', alignItems: 'center' }}>
399
+ <WarningIcon/> Needs Reconciliation (Difference: ${(Math.abs(reconciliationData.difference) / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })})
400
+ </Typography>
401
+ </Tooltip>
402
+ <Tooltip
403
+ title={
404
+ <React.Fragment>
405
+ <Typography variant="body2" sx={{ fontWeight: 'bold' }}>Possible causes:</Typography>
406
+ <Typography variant="body2">• This could be due to a sale being refunded in another month. Check for the same difference either the prior or next month.</Typography>
407
+ <Typography variant="body2">• If a payment was approved after the invoice was finalized, there might be a delayed payment that shows up next month.</Typography>
408
+ </React.Fragment>
409
+ }
410
+ placement="right"
411
+ arrow
412
+ >
413
+ <IconButton size="small" sx={{ ml: 1, color: 'inherit' }}>
414
+ <InfoIcon fontSize="small" />
415
+ </IconButton>
416
+ </Tooltip>
417
+ </Box>
418
+ )}
419
+
420
+ {!reconciliationData.isLoading && (
421
+ <>
422
+ <Typography variant="body1">
423
+ <strong>Stripe Balance Change:</strong> ${(reconciliationData.totalBalanceChange / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })}
424
+ </Typography>
425
+ <Typography variant="body1">
426
+ <strong>Gross Sales:</strong> ${(reconciliationData.grossSales / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })}
427
+ </Typography>
428
+ </>
429
+ )}
430
+
431
+ </Box>
432
+ </Grid>
433
+ )}
434
+
435
+ {/* Sales Table */}
436
+ <Grid item xs={12}
437
+ textAlign={"right"}
438
+ sx={{
439
+ opacity: !!sales?.length ? 1 : 0.25,
440
+ pointerEvents: !!sales?.length ? "" : "none",
441
+ display: "flex",
442
+ justifyContent: "flex-end",
443
+ alignItems: "flex-end",
444
+ }}
445
+ >
446
+ <Box
447
+ className='buttonStyle'
448
+ sx={{
449
+ position: "relative",
450
+ borderRadius: "4px 4px 0px 0px ",
451
+ pointerEvents: "none",
452
+ backgroundColor: 'primary.main',
453
+ color: 'primary.contrastText',
454
+ display: 'inline-flex',
455
+ alignItems: 'center',
456
+ justifyContent: 'center',
457
+ padding: '6px 16px',
458
+ fontWeight: 500,
459
+ }}
460
+ >
461
+ <Grid container alignItems="center" spacing={1}>
462
+ <Grid item>
463
+ <Tooltip title="Select columns">
464
+ <IconButton
465
+ size="small"
466
+ onClick={handleMenuClick}
467
+ sx={{
468
+ color: 'var(--white, white)',
469
+ pointerEvents: "auto",
470
+ '&:hover': {
471
+ opacity: 0.8
472
+ }
473
+ }}
474
+ >
475
+ <TuneIcon fontSize="small" />
476
+ </IconButton>
477
+ </Tooltip>
478
+ </Grid>
479
+ <Grid item>
480
+ <Tooltip title="Download CSV">
481
+ <IconButton
482
+ size="small"
483
+ onClick={() => downloadSalesData(',', designer, date, selectedColumns)}
484
+ sx={{
485
+ color: 'var(--white, white)',
486
+ pointerEvents: "auto",
487
+ '&:hover': {
488
+ opacity: 0.8
489
+ }
490
+ }}
491
+ >
492
+ <DownloadIcon fontSize="small" />
493
+ </IconButton>
494
+ </Tooltip>
495
+ </Grid>
496
+ <Grid item ml={1}>
497
+ <strong style={{ color: 'var(--white, white)' }}>CSV</strong>
498
+ </Grid>
499
+ </Grid>
500
+
501
+ <Menu
502
+ anchorEl={anchorEl}
503
+ open={Boolean(anchorEl)}
504
+ onClose={handleMenuClose}
505
+ dense={true}
506
+ PaperProps={{
507
+ sx: {
508
+ maxHeight: 300,
509
+ width: 250,
510
+ borderRadius: '4px',
511
+ }
512
+ }}
513
+ >
514
+ <Grid container>
515
+ <Grid item xs={6}>
516
+ <Button
517
+ onClick={handleSelectNone}
518
+ size='small'
519
+ variant="contained"
520
+ elevation={0}
521
+ sx={{
522
+ borderRadius: 0,
523
+ width: "100%",
524
+ boxShadow: 0
525
+ }}
526
+ >None</Button>
527
+ </Grid>
528
+ <Grid item xs={6}>
529
+ <Button
530
+ onClick={handleSelectAll}
531
+ size='small'
532
+ variant="contained"
533
+ elevation={0}
534
+ sx={{
535
+ borderRadius: 0,
536
+ width: "100%",
537
+ boxShadow: 0
538
+ }}
539
+ >All</Button>
540
+ </Grid>
541
+ </Grid>
542
+ {COLUMNS
543
+ .filter(col => !col.adminOnly || admin)
544
+ .map((column) => (
545
+ <MenuItem
546
+ key={column.id}
547
+ onClick={() => handleColumnToggle(column.id)}
548
+ sx={{
549
+ "&:hover": {
550
+ backgroundColor: "rgba(0,0,0,0.1)"
551
+ }
552
+ }}
553
+ >
554
+ <Checkbox
555
+ checked={selectedColumns.includes(column.id)}
556
+ size="small"
557
+ />
558
+ <ListItemText
559
+ primary={column.label}
560
+ primaryTypographyProps={{
561
+ variant: 'body2',
562
+ style: {
563
+ whiteSpace: 'nowrap',
564
+ overflow: 'hidden',
565
+ textOverflow: 'ellipsis',
566
+ }
567
+ }}
568
+ />
569
+ </MenuItem>
570
+ ))}
571
+ </Menu>
572
+ </Box>
573
+ </Grid>
574
+
575
+ <Grid
576
+ item
577
+ xs={12}
578
+ data-disabled={loading}
579
+ sx={{
580
+ opacity: !!sales?.length ? 1 : 0.25,
581
+ pointerEvents: !!sales?.length ? "" : "none"
582
+ }}
583
+ >
584
+ <DataGrid
585
+ id={`${slugify(designer.user, { lower: true, remove: /[*/+~.()'"!:@]/g, strict: true })}-${slugify(new Date(date).toUTCString(), { lower: true, remove: /[*/+~.()'"!:@]/g, strict: true })}`}
586
+ rows={(() => {
587
+ if (!sales?.length) {
588
+ // Return array with single no-data row
589
+ return [{
590
+ id: 'no-data',
591
+ sale: null,
592
+ designer,
593
+ admin,
594
+ color: "red"
595
+ }];
596
+ }
597
+
598
+ // Process sales data
599
+ return sales.flatMap(sale => {
600
+ const rows = [];
601
+
602
+ // Create a unique ID for this sale using orderNumber if available
603
+ const saleUniqueId = sale.orderNumber
604
+ ? `order-${sale.orderNumber}-${sale.id}`
605
+ : sale.id;
606
+
607
+ // Add refund rows if any
608
+ if (sale?.refunds?.length > 0) {
609
+ sale.refunds.forEach((refund, i) => {
610
+ rows.push({
611
+ // Create truly unique ID for each refund row
612
+ id: `${saleUniqueId}-refund-${i}-${refund.id}`,
613
+ sale,
614
+ designer,
615
+ admin,
616
+ refund,
617
+ color: "red"
618
+ });
619
+ });
620
+ }
621
+
622
+ // Add original sale row with enhanced uniqueness
623
+ rows.push({
624
+ id: `${saleUniqueId}-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
625
+ sale,
626
+ designer,
627
+ admin,
628
+ color: sale?.disputed ? "red!important" : "inherit"
629
+ });
630
+
631
+ return rows;
632
+ });
633
+ })()}
634
+ columns={memoizedColumns}
635
+ columnHeaderHeight={36}
636
+ rowHeight={28}
637
+ pagination
638
+ pageSizeOptions={[100]}
639
+ sortModel={sortModel}
640
+ onSortModelChange={(newSortModel) => setSortModel(newSortModel)}
641
+ filterModel={filterModel}
642
+ onFilterModelChange={(newFilterModel) => setFilterModel(newFilterModel)}
643
+ disableColumnMenu={true}
644
+ componentsProps={{
645
+ columnHeaders: {
646
+ sx: {
647
+ // Style for the header cells
648
+ '& .MuiDataGrid-columnHeader--sorted': {
649
+ backgroundColor: 'rgba(25, 118, 210, 0.12)',
650
+ '&:hover': {
651
+ backgroundColor: 'rgba(25, 118, 210, 0.18)'
652
+ }
653
+ }
654
+ }
655
+ }
656
+ }}
657
+ sx={{
658
+ backgroundColor: 'rgba(var(--blackRGB, 0,0,0), 0.06)',
659
+ maxHeight: 'max(300px, 50vh)',
660
+ maxWidth: 'initial!important',
661
+ borderTopRightRadius: 0,
662
+ border: "none",
663
+ '--DataGrid-overlayHeight': '300px',
664
+ // Add specific styling for sorted column headers
665
+ '& .MuiDataGrid-columnHeader--sorted': {
666
+ backgroundColor: 'rgba(25, 118, 210, 0.12)'
667
+ },
668
+ // Optional: add hover effect for sorted headers
669
+ '& .MuiDataGrid-columnHeader--sorted:hover': {
670
+ backgroundColor: 'rgba(25, 118, 210, 0.18)'
671
+ }
672
+ }}
673
+ />
674
+ </Grid>
675
+ </Grid>
676
+ );
677
+ }