@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,596 @@
1
+ /**
2
+ * Process raw sales data into chart-ready format
3
+ * @param {Array} sales - Raw sales data
4
+ * @param {Date} date - Current date being displayed
5
+ * @returns {Object} Processed chart data
6
+ */
7
+ export function processChartData(sales, date) {
8
+ const daysInMonth = new Date(date.getUTCFullYear(), date.getUTCMonth() + 1, 0).getDate();
9
+
10
+ // Initialize data arrays with actual days in month
11
+ let tmpXAxis = Array.from({ length: daysInMonth }, (_, i) => i + 1);
12
+ let tmpSalesData = Array(daysInMonth).fill(0);
13
+ let tmpDiscountLossData = Array(daysInMonth).fill(0);
14
+ let tmpTotalSalesData = Array(daysInMonth).fill(0);
15
+ let tmpOrderData = Array(daysInMonth).fill(0);
16
+ let tmpRegularOrderData = Array(daysInMonth).fill(0);
17
+ let tmpRefundData = Array(daysInMonth).fill(0);
18
+ let tmpRefundTotal = 0;
19
+ let tmpDiscountData = Array(daysInMonth).fill(0);
20
+ let tmpFirstOrderData = Array(daysInMonth).fill(0);
21
+ let tmpDiscountFirstOrderData = Array(daysInMonth).fill(0);
22
+ let tmpTaxData = Array(daysInMonth).fill(0);
23
+ let tmpShippingData = Array(daysInMonth).fill(0);
24
+ let tmpShippedOrdersData = Array(daysInMonth).fill(0);
25
+ let grandTotal = 0;
26
+ let grandTax = 0;
27
+ let grandShipping = 0;
28
+
29
+
30
+ // Process data day by day
31
+ for (let day = 1; day <= daysInMonth; day++) {
32
+ // Skip future dates
33
+ if (
34
+ new Date(date).getUTCFullYear() > new Date().getUTCFullYear() ||
35
+ (new Date(date).getUTCFullYear() >= new Date().getUTCFullYear() &&
36
+ new Date(date).getUTCMonth() > new Date().getUTCMonth()) ||
37
+ (day > new Date().getDate() &&
38
+ new Date(date).getUTCMonth() >= new Date().getUTCMonth() &&
39
+ new Date(date).getUTCFullYear() >= new Date().getUTCFullYear())
40
+ ) {
41
+ continue;
42
+ }
43
+
44
+ // Get day's sales
45
+ let todaySales = sales?.filter(
46
+ (item) => new Date(item.created * 1000).getUTCDate() === day
47
+ );
48
+
49
+ let total = 0;
50
+ let discountLost = 0;
51
+ let orders = 0;
52
+ let regularOrders = 0;
53
+ let refunds = 0;
54
+ let refundAmount = 0;
55
+ let discounts = 0;
56
+ let firstOrders = 0;
57
+ let firstOrderDiscounts = 0;
58
+ let tax = 0;
59
+ let shipping = 0;
60
+ let shippedOrders = 0;
61
+
62
+ // Create a map to track unique sales by ID to prevent duplicates
63
+ const processedSales = new Map();
64
+
65
+ // Process each sale
66
+ todaySales?.forEach((item) => {
67
+ // Create a unique identifier for this sale
68
+ const saleKey = `${item.saleType || ''}-${item.id}-${item.lineId || ''}`;
69
+
70
+ // Skip if we've already processed this exact sale
71
+ if (processedSales.has(saleKey)) {
72
+ return;
73
+ }
74
+
75
+ // Mark as processed
76
+ processedSales.set(saleKey, true);
77
+
78
+ if (item?.shippingProvision) {
79
+ shipping += item.total;
80
+ shippedOrders++;
81
+ } else {
82
+ total += item.total;
83
+ orders++;
84
+ regularOrders++;
85
+ }
86
+
87
+ // Process tax
88
+ if (item?.taxAmounts?.length) {
89
+ item.taxAmounts.forEach((taxItem) => {
90
+ tax += taxItem.amount;
91
+ });
92
+ }
93
+
94
+ // Process discounts
95
+ if (!item?.shippingProvision) {
96
+ if (item?.discountAmounts.length) {
97
+ item.discountAmounts.forEach((discount) => {
98
+ discountLost += discount.amount;
99
+ if (item?.customer?.next_invoice_sequence <= 2) firstOrderDiscounts++;
100
+ else discounts++;
101
+ regularOrders--;
102
+ });
103
+ } else if (item?.customer?.next_invoice_sequence <= 2) {
104
+ firstOrders++;
105
+ regularOrders--;
106
+ }
107
+ }
108
+
109
+ // Process refunds and disputes
110
+ if (item?.disputed) {
111
+ refunds++;
112
+ const disputeAmount = item.totalWithTax - item.total;
113
+ refundAmount += disputeAmount;
114
+ total -= disputeAmount;
115
+ }
116
+ item?.refunds?.forEach((refund) => {
117
+ total -= refund.adjustedTotal;
118
+ refundAmount += refund.adjustedTotal;
119
+ refunds++;
120
+ });
121
+ });
122
+
123
+ // Update running totals
124
+ shipping = shipping / 100;
125
+ grandShipping += shipping;
126
+ tax = tax / 100;
127
+ grandTax += tax;
128
+ discountLost = discountLost / 100;
129
+ total = total / 100;
130
+ grandTotal += total;
131
+
132
+ // Update arrays at index day-1
133
+ const idx = day - 1;
134
+ tmpOrderData[idx] = orders;
135
+ tmpRegularOrderData[idx] = regularOrders;
136
+ tmpFirstOrderData[idx] = firstOrders;
137
+ tmpDiscountData[idx] = discounts;
138
+ tmpDiscountFirstOrderData[idx] = firstOrderDiscounts;
139
+ tmpRefundData[idx] = refunds;
140
+ tmpShippingData[idx] = grandShipping;
141
+ tmpShippedOrdersData[idx] = shippedOrders;
142
+ tmpTaxData[idx] = grandTax;
143
+ tmpDiscountLossData[idx] = discountLost;
144
+ tmpSalesData[idx] = total;
145
+ tmpTotalSalesData[idx] = grandTotal + grandShipping + grandTax;
146
+ tmpRefundTotal += refundAmount;
147
+ }
148
+
149
+ // Calculate changeMax for bar chart scaling
150
+ const changeMax = Math.max(
151
+ Math.max(...tmpOrderData) || 0,
152
+ Math.max(...tmpRegularOrderData) || 0,
153
+ Math.max(...tmpFirstOrderData) || 0,
154
+ Math.max(...tmpDiscountData) || 0,
155
+ Math.max(...tmpDiscountFirstOrderData) || 0,
156
+ Math.max(...tmpShippedOrdersData) || 0,
157
+ Math.max(...tmpRefundData) || 0
158
+ );
159
+
160
+ return {
161
+ xAxis: tmpXAxis,
162
+ discountLostData: tmpDiscountLossData,
163
+ salesData: tmpSalesData,
164
+ totalSalesData: tmpTotalSalesData,
165
+ salesMax: Math.max(...tmpTotalSalesData) || 0,
166
+ orderData: tmpOrderData,
167
+ orderMax: Math.max(...tmpOrderData) || 0,
168
+ regularOrderData: tmpRegularOrderData,
169
+ regularOrderMax: Math.max(...tmpRegularOrderData) || 0,
170
+ discountData: tmpDiscountData,
171
+ discountMax: Math.max(...tmpDiscountData) || 0,
172
+ discountFirstOrderData: tmpDiscountFirstOrderData,
173
+ firstOrderDiscountMax: Math.max(...tmpDiscountFirstOrderData) || 0,
174
+ firstOrderData: tmpFirstOrderData,
175
+ firstOrderMax: Math.max(...tmpFirstOrderData) || 0,
176
+ refundData: tmpRefundData,
177
+ refundMax: Math.max(...tmpRefundData) || 0,
178
+ refundTotal: tmpRefundTotal / 100,
179
+ taxData: tmpTaxData,
180
+ shippingData: tmpShippingData,
181
+ shippedOrdersData: tmpShippedOrdersData,
182
+ shippedOrdersMax: Math.max(...tmpShippedOrdersData) || 0,
183
+ changeMax: changeMax,
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Process sales data into typeface-specific data with detailed financial breakdowns
189
+ * @param {Array} sales - Raw sales data
190
+ * @param {Array} designers - Designer information
191
+ * @returns {Array} Processed typeface data with various financial totals
192
+ */
193
+ export function processTypefaceData(sales, designers) {
194
+ let typefaceData = [];
195
+
196
+ // Create a map to track unique sales by ID to prevent duplicates
197
+ const processedSales = new Map();
198
+
199
+ sales?.forEach((sale) => {
200
+ // Create a unique identifier for this sale
201
+ const saleKey = `${sale.saleType || ''}-${sale.id}-${sale.lineId || ''}`;
202
+
203
+ // Skip if we've already processed this exact sale
204
+ if (processedSales.has(saleKey)) {
205
+ return;
206
+ }
207
+
208
+ // Mark as processed
209
+ processedSales.set(saleKey, true);
210
+
211
+ // Extract typeface title
212
+ var title = '';
213
+ if (sale.description) {
214
+ title = sale.description.substring(0, sale.description.indexOf(' ('));
215
+ if (title === '') title = sale.description.substring(0, sale.description.indexOf(' —'));
216
+ if (title === '') title = sale.description.substring(0, sale.description.indexOf('.'));
217
+ if (title === '') title = sale.description;
218
+ } else {
219
+ title = 'Unknown Typeface';
220
+ }
221
+
222
+ // Calculate tax amounts for this sale
223
+ let saleTax = 0;
224
+ if (sale?.taxAmounts?.length) {
225
+ sale.taxAmounts.forEach((taxItem) => {
226
+ saleTax += taxItem.amount;
227
+ });
228
+ }
229
+
230
+ // Calculate discount amounts for this sale
231
+ let saleDiscounts = 0;
232
+ if (sale?.discountAmounts?.length) {
233
+ sale.discountAmounts.forEach((discount) => {
234
+ saleDiscounts += discount.amount;
235
+ });
236
+ }
237
+
238
+ // Calculate refund amounts for this sale
239
+ let saleRefunds = 0;
240
+ if (sale?.disputed) {
241
+ const disputeAmount = sale.totalWithTax - sale.total;
242
+ saleRefunds += disputeAmount;
243
+ }
244
+ if (sale?.refunds?.length) {
245
+ sale.refunds.forEach((refund) => {
246
+ saleRefunds += refund.adjustedTotal;
247
+ });
248
+ }
249
+
250
+ // Find or create typeface entry
251
+ let existingTypeface = typefaceData.find(item => item.title === title);
252
+ if (existingTypeface) {
253
+ // Update existing typeface data
254
+ existingTypeface.grossTotal += sale.total;
255
+ existingTypeface.taxTotal += saleTax;
256
+ existingTypeface.discountTotal += saleDiscounts;
257
+ existingTypeface.refundTotal += saleRefunds;
258
+ existingTypeface.total += sale.total;
259
+ existingTypeface.orders += 1;
260
+ } else {
261
+ // Create new typeface entry with detailed financials
262
+ typefaceData.push({
263
+ title: title,
264
+ total: sale.total,
265
+ grossTotal: sale.total, // Total before any adjustments
266
+ taxTotal: saleTax,
267
+ discountTotal: saleDiscounts,
268
+ refundTotal: saleRefunds,
269
+ orders: 1,
270
+ author: designers && designers.find(designer => designer._id === sale.author || designer._id === sale?.author?._id) ?
271
+ designers.find(designer => (designer._id === sale.author || designer._id === sale?.author?._id))
272
+ :
273
+ {
274
+ firstName: undefined,
275
+ lastName: undefined,
276
+ _id: sale.author?._id || sale?.author,
277
+ }
278
+ });
279
+ }
280
+
281
+ // Apply discounts and refunds to the adjusted total
282
+ existingTypeface = typefaceData.find(item => item.title === title);
283
+ sale?.discountAmounts?.forEach(discountAmount => {
284
+ existingTypeface.total -= discountAmount.amount;
285
+ });
286
+
287
+ sale?.refunds?.forEach(refund => {
288
+ existingTypeface.total -= refund.adjustedTotal;
289
+ });
290
+ });
291
+
292
+ // Calculate derived totals
293
+ typefaceData.forEach(typeface => {
294
+ // Total with discounts applied, but before tax and refunds
295
+ typeface.totalWithDiscounts = typeface.grossTotal - typeface.discountTotal;
296
+
297
+ // Total before tax, but after discounts and refunds
298
+ typeface.pretaxTotal = typeface.totalWithDiscounts - typeface.refundTotal;
299
+
300
+ // Net total after all adjustments (same as typeface.total)
301
+ typeface.netTotal = typeface.total;
302
+
303
+ // Convert cents to dollars for all monetary values
304
+ typeface.grossTotal = typeface.grossTotal;
305
+ typeface.taxTotal = typeface.taxTotal;
306
+ typeface.discountTotal = typeface.discountTotal;
307
+ typeface.refundTotal = typeface.refundTotal;
308
+ typeface.totalWithDiscounts = typeface.totalWithDiscounts;
309
+ typeface.pretaxTotal = typeface.pretaxTotal;
310
+ typeface.total = typeface.total;
311
+ typeface.netTotal = typeface.netTotal;
312
+ });
313
+
314
+ // Sort by title
315
+ return typefaceData.sort((a, b) => a.title.localeCompare(b.title));
316
+ }
317
+
318
+ /**
319
+ * Process typeface data into designer-specific data
320
+ * @param {Array} typefaceData - Processed typeface data
321
+ * @returns {Array} Processed designer data
322
+ */
323
+ /**
324
+ * Process sales data into license-type-specific data with financial breakdowns
325
+ * @param {Array} sales - Raw sales data
326
+ * @returns {Array} Processed license type data with financial totals
327
+ */
328
+ export function processLicenseTypeData(sales) {
329
+ let licenseTypeData = [];
330
+
331
+ // Create a map to track unique sales by ID to prevent duplicates
332
+ const processedSales = new Map();
333
+
334
+ sales?.forEach((sale) => {
335
+ // Create a unique identifier for this sale
336
+ const saleKey = `${sale.saleType || ''}-${sale.id}-${sale.lineId || ''}`;
337
+
338
+ // Skip if we've already processed this exact sale
339
+ if (processedSales.has(saleKey)) {
340
+ return;
341
+ }
342
+
343
+ // Mark as processed
344
+ processedSales.set(saleKey, true);
345
+
346
+ // Extract license type from description
347
+ let licenseName = 'Unknown';
348
+
349
+ // Try to extract license type from description using various patterns
350
+ // Pattern 1: "Typeface Name (License Type)"
351
+ const parenthesisMatch = sale.description && sale.description.match(/\((.*?)\)/);
352
+ if (parenthesisMatch && parenthesisMatch[1]) {
353
+ licenseName = parenthesisMatch[1].trim();
354
+ }
355
+ // Pattern 2: "Typeface Name — License Type"
356
+ else if (sale.description && sale.description.includes('—')) {
357
+ const parts = sale.description.split('—');
358
+ if (parts.length > 1) {
359
+ licenseName = parts[1].trim();
360
+ }
361
+ }
362
+ // Pattern 3: If license is explicitly provided in a license property
363
+ else if (sale.license) {
364
+ licenseName = sale.license;
365
+ }
366
+
367
+ // Calculate tax amounts for this sale
368
+ let saleTax = 0;
369
+ if (sale?.taxAmounts?.length) {
370
+ sale.taxAmounts.forEach((taxItem) => {
371
+ saleTax += taxItem.amount;
372
+ });
373
+ }
374
+
375
+ // Calculate discount amounts for this sale
376
+ let saleDiscounts = 0;
377
+ if (sale?.discountAmounts?.length) {
378
+ sale.discountAmounts.forEach((discount) => {
379
+ saleDiscounts += discount.amount;
380
+ });
381
+ }
382
+
383
+ // Calculate refund amounts for this sale
384
+ let saleRefunds = 0;
385
+ if (sale?.disputed) {
386
+ const disputeAmount = sale.totalWithTax - sale.total;
387
+ saleRefunds += disputeAmount;
388
+ }
389
+ if (sale?.refunds?.length) {
390
+ sale.refunds.forEach((refund) => {
391
+ saleRefunds += refund.adjustedTotal;
392
+ });
393
+ }
394
+
395
+ // Find or create license type entry
396
+ let existingLicense = licenseTypeData.find(item => item.name === licenseName);
397
+ if (existingLicense) {
398
+ // Update existing license data
399
+ existingLicense.grossTotal += sale.total;
400
+ existingLicense.taxTotal += saleTax;
401
+ existingLicense.discountTotal += saleDiscounts;
402
+ existingLicense.refundTotal += saleRefunds;
403
+ existingLicense.total += sale.total;
404
+ existingLicense.orders += 1;
405
+ } else {
406
+ // Create new license entry with detailed financials
407
+ licenseTypeData.push({
408
+ name: licenseName,
409
+ total: sale.total,
410
+ grossTotal: sale.total, // Total before any adjustments
411
+ taxTotal: saleTax,
412
+ discountTotal: saleDiscounts,
413
+ refundTotal: saleRefunds,
414
+ orders: 1
415
+ });
416
+ }
417
+
418
+ // Apply discounts and refunds to the adjusted total
419
+ existingLicense = licenseTypeData.find(item => item.name === licenseName);
420
+ sale?.discountAmounts?.forEach(discountAmount => {
421
+ existingLicense.total -= discountAmount.amount;
422
+ });
423
+
424
+ sale?.refunds?.forEach(refund => {
425
+ existingLicense.total -= refund.adjustedTotal;
426
+ });
427
+ });
428
+
429
+ // Calculate derived totals
430
+ licenseTypeData.forEach(license => {
431
+ // Total with discounts applied, but before tax and refunds
432
+ license.totalWithDiscounts = license.grossTotal - license.discountTotal;
433
+
434
+ // Total before tax, but after discounts and refunds
435
+ license.pretaxTotal = license.totalWithDiscounts - license.refundTotal;
436
+
437
+ // Net total after all adjustments (same as license.total)
438
+ license.netTotal = license.total;
439
+ });
440
+
441
+ // Sort by name
442
+ return licenseTypeData.sort((a, b) => a.name.localeCompare(b.name));
443
+ }
444
+
445
+ export function processDesignersData(typefaceData) {
446
+ let designersData = [];
447
+
448
+ typefaceData?.forEach(typeface => {
449
+ let designer = designersData.find(author => author._id === typeface.author._id);
450
+ if (designer) {
451
+ designer.total += typeface.total;
452
+ designer.orders += typeface.orders;
453
+ } else {
454
+ designersData.push({
455
+ _id: typeface.author._id,
456
+ firstName: typeface.author.firstName,
457
+ lastName: typeface.author.lastName,
458
+ total: typeface.total,
459
+ orders: typeface.orders,
460
+ });
461
+ }
462
+ });
463
+
464
+ return designersData;
465
+ }
466
+
467
+ /**
468
+ * Generate chart series data
469
+ * @param {Object} chartState - Current chart state
470
+ * @param {boolean} displayLosses - Whether to display loss data
471
+ * @returns {Array} Chart series data
472
+ */
473
+ export function generateSeriesData(chartState, displayLosses) {
474
+ const {
475
+ discountData,
476
+ discountFirstOrderData,
477
+ firstOrderData,
478
+ regularOrderData,
479
+ shippedOrdersData,
480
+ refundData,
481
+ shippingData,
482
+ taxData,
483
+ totalSalesData,
484
+ salesData,
485
+ discountLostData,
486
+ changeMax,
487
+ salesMax,
488
+ xAxis
489
+ } = chartState;
490
+
491
+ // Calculate start date as first day of the selected month
492
+ const startDate = new Date(chartState.xAxis[0]);
493
+
494
+ return [
495
+ !!(discountData?.length > 0 && discountData.some(value => value !== 0)) && {
496
+ type: 'bar',
497
+ data: (discountData && changeMax && salesMax) ? discountData.map((v) => (v / changeMax) * salesMax) : [0],
498
+ valueFormatter: (v) => v != null ? `${Math.round((v * changeMax) / salesMax)}` : '0',
499
+ color: 'url(#discountHatch) rgba(var(--blackRGB, 0,0,0), 0.1)',
500
+ label: 'Discounted Orders',
501
+ id: 'discountsId',
502
+ stack: 'total',
503
+ startDate: startDate
504
+ },
505
+ !!(discountFirstOrderData?.length > 0 && discountFirstOrderData.some(value => value !== 0)) && {
506
+ type: 'bar',
507
+ data: (discountFirstOrderData && changeMax && salesMax) ? discountFirstOrderData.map((v) => (v / changeMax) * salesMax) : [0],
508
+ valueFormatter: (v) => v != null ? `${Math.round((v * changeMax) / salesMax)}` : '0',
509
+ color: 'url(#discountFirstOrderHatch) rgba(var(--blueRGB, 0,0,255), .25)',
510
+ label: 'Discounted First Orders',
511
+ id: 'discountFirstOrderId',
512
+ stack: 'total',
513
+ startDate: startDate
514
+ },
515
+ !!(firstOrderData?.length > 0 && firstOrderData.some(value => value !== 0)) && {
516
+ type: 'bar',
517
+ data: (firstOrderData && changeMax && salesMax) ? firstOrderData.map((v) => (v / changeMax) * salesMax) : [0],
518
+ valueFormatter: (v) => v != null ? `${Math.round((v * changeMax) / salesMax)}` : '0',
519
+ color: 'rgba(var(--blueRGB, 0,0,255), .25)',
520
+ label: 'First Orders',
521
+ id: 'firstOrderId',
522
+ stack: 'total',
523
+ startDate: startDate
524
+ },
525
+ !!(regularOrderData?.length > 0 && regularOrderData.some(value => value !== 0)) && {
526
+ type: 'bar',
527
+ data: (regularOrderData && changeMax && salesMax) ? regularOrderData.map((v) => (v / changeMax) * salesMax) : [0],
528
+ valueFormatter: (v) => v != null ? `${Math.round((v * changeMax) / salesMax)}` : '0',
529
+ color: 'rgba(var(--blackRGB, 0,0,0), 0.25)',
530
+ label: 'Orders',
531
+ id: 'ordersId',
532
+ stack: 'total',
533
+ startDate: startDate
534
+ },
535
+ !!(shippedOrdersData?.length > 0 && shippedOrdersData.some(value => value !== 0)) && {
536
+ type: 'bar',
537
+ data: (shippedOrdersData && changeMax && salesMax) ? shippedOrdersData.map((v) => (v / changeMax) * salesMax) : [0],
538
+ valueFormatter: (v) => v != null ? `${Math.round((v * changeMax) / salesMax)}` : '0',
539
+ color: 'rgba(var(--orangeRGB, 255,165,0), .75)',
540
+ label: 'Shipped Orders',
541
+ id: 'shippedOrdersId',
542
+ stack: 'total',
543
+ startDate: startDate
544
+ },
545
+ !!(refundData?.length > 0 && refundData.some(value => value !== 0)) && {
546
+ type: 'bar',
547
+ data: (refundData && changeMax && salesMax) ? refundData.map((v) => -((v / changeMax) * salesMax)) : [0],
548
+ valueFormatter: (v) => v != null ? `${-1 * Math.round((v * changeMax) / salesMax)}` : '0',
549
+ color: 'rgba(var(--redRGB, 255,0,0), .75)',
550
+ label: 'Refunds & Disputes',
551
+ id: 'refundsId',
552
+ stack: 'total',
553
+ startDate: startDate
554
+ },
555
+ !!(shippingData?.length > 0 && shippingData.some(value => value !== 0)) && {
556
+ type: 'line',
557
+ data: shippingData,
558
+ valueFormatter: (v) => v != null ? `$${v.toLocaleString('en-US', { minimumFractionDigits: 2 })}` : '$0.00',
559
+ color: 'var(--orange, orange)',
560
+ label: 'Accrued Shipping',
561
+ startDate: startDate
562
+ },
563
+ !!(taxData?.length > 0 && taxData.some(value => value !== 0)) && {
564
+ type: 'line',
565
+ data: taxData,
566
+ valueFormatter: (v) => v != null ? `$${v.toLocaleString('en-US', { minimumFractionDigits: 2 })}` : '$0.00',
567
+ color: 'var(--red, red)',
568
+ label: 'Accrued Tax',
569
+ startDate: startDate
570
+ },
571
+ {
572
+ type: 'line',
573
+ data: totalSalesData,
574
+ valueFormatter: (v) => v != null ? `$${v.toLocaleString('en-US', { minimumFractionDigits: 2 })}` : '$0.00',
575
+ color: 'var(--green, green)',
576
+ label: 'Gross Accrued Sales',
577
+ startDate: startDate
578
+ },
579
+ {
580
+ type: 'line',
581
+ data: salesData,
582
+ valueFormatter: (v) => v != null ? `$${v.toLocaleString('en-US', { minimumFractionDigits: 2 })}` : '$0.00',
583
+ color: 'var(--blue, blue)',
584
+ label: "Day's Net Sales",
585
+ startDate: startDate
586
+ },
587
+ !!(displayLosses) && {
588
+ type: 'line',
589
+ data: discountLostData,
590
+ valueFormatter: (v) => v != null ? `$${v.toLocaleString('en-US', { minimumFractionDigits: 2 })}` : '$0.00',
591
+ color: 'var(--black, black)',
592
+ label: 'Missed Discounted Earnings',
593
+ startDate: startDate
594
+ },
595
+ ].filter(Boolean);
596
+ }
@@ -0,0 +1,71 @@
1
+ // Hook for managing sales date filtering via URL query parameters
2
+ import { useRouter } from 'next/router';
3
+ import { useState, useEffect } from 'react';
4
+
5
+ /**
6
+ * Custom hook that manages month/year selection via URL query parameters
7
+ * Provides synchronized state for date selection and URL updates
8
+ * @returns {Object} Date state and update function
9
+ */
10
+ export function useSalesDateQuery() {
11
+ const router = useRouter();
12
+
13
+ // State for date management
14
+ const [month, setMonth] = useState(-1);
15
+ const [year, setYear] = useState(-1);
16
+ const [date, setDate] = useState(null);
17
+
18
+ // Initialize from URL query parameters when router is ready
19
+ useEffect(() => {
20
+ if (!router.isReady) return;
21
+
22
+ const queryMonth = router.query.month !== undefined ? parseInt(router.query.month) : -1;
23
+ const queryYear = router.query.year !== undefined ? parseInt(router.query.year) : -1;
24
+
25
+ // If we have valid query params, use them
26
+ if (queryMonth >= 0 && queryMonth <= 11 && queryYear > 2000) {
27
+ setMonth(queryMonth);
28
+ setYear(queryYear);
29
+ } else if (month === -1 || year === -1) {
30
+ // Otherwise use current date (only if not already set)
31
+ const currentDate = new Date();
32
+ setMonth(currentDate.getUTCMonth());
33
+ setYear(currentDate.getUTCFullYear());
34
+ }
35
+ }, [router.isReady, router.query]);
36
+
37
+ // Update date object when month/year change
38
+ useEffect(() => {
39
+ if (month >= 0 && year > 0) {
40
+ const newDate = new Date(Date.UTC(year, month, 1));
41
+ setDate(newDate);
42
+ }
43
+ }, [month, year]);
44
+
45
+ /**
46
+ * Updates the date state and URL query parameters
47
+ * @param {Date} newDate - The new date to set
48
+ */
49
+ const updateDate = (newDate) => {
50
+ const newMonth = new Date(newDate).getUTCMonth();
51
+ const newYear = new Date(newDate).getUTCFullYear();
52
+
53
+ // Update state
54
+ setMonth(newMonth);
55
+ setYear(newYear);
56
+
57
+ // Update URL query parameters without page reload
58
+ router.push(
59
+ {
60
+ pathname: router.pathname,
61
+ query: { ...router.query, month: newMonth, year: newYear }
62
+ },
63
+ undefined,
64
+ { shallow: true }
65
+ );
66
+ };
67
+
68
+ return { month, year, date, updateDate };
69
+ }
70
+
71
+ export default useSalesDateQuery;