@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.
- package/README.md +461 -0
- package/SETUP.md +230 -0
- package/api/getAnalytics.d.ts +38 -0
- package/api/getAnalytics.js +346 -0
- package/api/getBalanceTransactions.d.ts +29 -0
- package/api/getBalanceTransactions.js +125 -0
- package/api/getDesignerInfo.d.ts +37 -0
- package/api/getDesignerInfo.js +98 -0
- package/api/getDesigners.d.ts +28 -0
- package/api/getDesigners.js +63 -0
- package/api/getPreviousSales.d.ts +23 -0
- package/api/getPreviousSales.js +82 -0
- package/api/getSales.d.ts +29 -0
- package/api/getSales.js +50 -0
- package/api/getSalesRange.d.ts +23 -0
- package/api/getSalesRange.js +58 -0
- package/api/utils/authMiddleware.js +84 -0
- package/api/utils/dateUtils.js +69 -0
- package/api/utils/feeCalculator.js +148 -0
- package/api/utils/processors/invoiceProcessor.js +337 -0
- package/api/utils/processors/paymentProcessor.js +462 -0
- package/api/utils/salesDataProcessing.js +596 -0
- package/api/utils/salesDataProcessor.js +224 -0
- package/api/utils/stripeFetcher.js +248 -0
- package/components/DateRangeSalesTable.js +1072 -0
- package/components/DebugValues.js +48 -0
- package/components/LicenseTypeList.js +193 -0
- package/components/LoginForm.js +219 -0
- package/components/PeriodComparison.js +501 -0
- package/components/Sales.js +773 -0
- package/components/SalesChart.js +307 -0
- package/components/SalesPortalPage.js +147 -0
- package/components/SalesTable.js +677 -0
- package/components/SummaryCards.js +345 -0
- package/components/TopPerformers.js +331 -0
- package/components/TypefaceList.js +154 -0
- package/components/table-columns.js +70 -0
- package/components/table-row-cells.js +295 -0
- package/data/countryCode.json +318 -0
- package/hooks/useSalesDateQuery.d.ts +20 -0
- package/hooks/useSalesDateQuery.js +71 -0
- package/index.d.ts +172 -0
- package/index.js +33 -0
- package/package.json +87 -0
- package/styles/sales-portal.module.scss +383 -0
- package/styles/sales-portal.theme.d.ts +5 -0
- package/styles/sales-portal.theme.js +799 -0
- package/utils/currencyUtils.d.ts +20 -0
- package/utils/currencyUtils.js +79 -0
- package/utils/salesDataProcessing.d.ts +44 -0
- package/utils/salesDataProcessing.js +596 -0
- package/utils/useSalesDateQuery.js +71 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
// Period comparison component for comparing sales data across different time periods
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Grid,
|
|
5
|
+
Typography,
|
|
6
|
+
Box,
|
|
7
|
+
ToggleButtonGroup,
|
|
8
|
+
ToggleButton,
|
|
9
|
+
FormControl,
|
|
10
|
+
InputLabel,
|
|
11
|
+
Select,
|
|
12
|
+
MenuItem,
|
|
13
|
+
CircularProgress,
|
|
14
|
+
Paper
|
|
15
|
+
} from '@mui/material';
|
|
16
|
+
import { ResponsiveChartContainer } from '@mui/x-charts/ResponsiveChartContainer';
|
|
17
|
+
import { BarPlot } from '@mui/x-charts/BarChart';
|
|
18
|
+
import { LinePlot } from '@mui/x-charts/LineChart';
|
|
19
|
+
import { ChartsXAxis } from '@mui/x-charts/ChartsXAxis';
|
|
20
|
+
import { ChartsYAxis } from '@mui/x-charts/ChartsYAxis';
|
|
21
|
+
import { ChartsTooltip } from '@mui/x-charts/ChartsTooltip';
|
|
22
|
+
import { ChartsLegend } from '@mui/x-charts/ChartsLegend';
|
|
23
|
+
import styles from '../styles/sales-portal.module.scss';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Process chart data from sales information
|
|
27
|
+
* @param {Array} sales - Sales data
|
|
28
|
+
* @param {Date} date - Date for the data
|
|
29
|
+
* @returns {Object} Processed chart data
|
|
30
|
+
*/
|
|
31
|
+
const processChartData = (sales, date) => {
|
|
32
|
+
// Initialize arrays for each day of the month (1-31)
|
|
33
|
+
const daysInMonth = new Date(date.getUTCFullYear(), date.getUTCMonth() + 1, 0).getUTCDate();
|
|
34
|
+
const xAxis = Array.from({ length: daysInMonth }, (_, i) => i + 1);
|
|
35
|
+
|
|
36
|
+
// Initialize data arrays with zeros
|
|
37
|
+
const salesData = Array(daysInMonth).fill(0);
|
|
38
|
+
const orderData = Array(daysInMonth).fill(0);
|
|
39
|
+
const discountData = Array(daysInMonth).fill(0);
|
|
40
|
+
const refundData = Array(daysInMonth).fill(0);
|
|
41
|
+
const taxData = Array(daysInMonth).fill(0);
|
|
42
|
+
const shippingData = Array(daysInMonth).fill(0);
|
|
43
|
+
|
|
44
|
+
let refundTotal = 0;
|
|
45
|
+
|
|
46
|
+
// Process each sale
|
|
47
|
+
sales.forEach(sale => {
|
|
48
|
+
// Get the day of the month (1-31)
|
|
49
|
+
const saleDate = new Date(sale.date);
|
|
50
|
+
const day = saleDate.getUTCDate() - 1; // Adjust to 0-based index
|
|
51
|
+
|
|
52
|
+
// Skip if the day is out of range
|
|
53
|
+
if (day < 0 || day >= daysInMonth) return;
|
|
54
|
+
|
|
55
|
+
// Update cumulative data for the day and all subsequent days
|
|
56
|
+
for (let i = day; i < daysInMonth; i++) {
|
|
57
|
+
salesData[i] += sale.total || 0;
|
|
58
|
+
orderData[i] += 1;
|
|
59
|
+
discountData[i] += sale.amountDiscounted || 0;
|
|
60
|
+
taxData[i] += sale.taxAmount || 0;
|
|
61
|
+
shippingData[i] += sale.shippingCost || 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Process refunds
|
|
65
|
+
if (sale.refunds && sale.refunds.length > 0) {
|
|
66
|
+
sale.refunds.forEach(refund => {
|
|
67
|
+
refundTotal += refund.adjustedTotal || 0;
|
|
68
|
+
// Get the day of the refund
|
|
69
|
+
const refundDate = new Date(refund.date);
|
|
70
|
+
const refundDay = refundDate.getUTCDate() - 1;
|
|
71
|
+
|
|
72
|
+
// Skip if the refund day is out of range
|
|
73
|
+
if (refundDay < 0 || refundDay >= daysInMonth) return;
|
|
74
|
+
|
|
75
|
+
// Update refund data for the day and all subsequent days
|
|
76
|
+
for (let i = refundDay; i < daysInMonth; i++) {
|
|
77
|
+
refundData[i] += refund.adjustedTotal || 0;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Convert from cents to dollars for display
|
|
84
|
+
const salesDataInDollars = salesData.map(value => value / 100);
|
|
85
|
+
const discountDataInDollars = discountData.map(value => value / 100);
|
|
86
|
+
const refundDataInDollars = refundData.map(value => value / 100);
|
|
87
|
+
const taxDataInDollars = taxData.map(value => value / 100);
|
|
88
|
+
const shippingDataInDollars = shippingData.map(value => value / 100);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
xAxis,
|
|
92
|
+
salesData: salesDataInDollars,
|
|
93
|
+
salesMax: salesDataInDollars[daysInMonth - 1] || 0,
|
|
94
|
+
orderData,
|
|
95
|
+
orderMax: orderData[daysInMonth - 1] || 0,
|
|
96
|
+
discountData: discountDataInDollars,
|
|
97
|
+
discountMax: discountDataInDollars[daysInMonth - 1] || 0,
|
|
98
|
+
refundData: refundDataInDollars,
|
|
99
|
+
refundMax: refundDataInDollars[daysInMonth - 1] || 0,
|
|
100
|
+
refundTotal: refundTotal / 100,
|
|
101
|
+
taxData: taxDataInDollars,
|
|
102
|
+
shippingData: shippingDataInDollars
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Format currency values in USD
|
|
108
|
+
* @param {number} value - Value to format as currency
|
|
109
|
+
* @returns {string} Formatted currency string
|
|
110
|
+
*/
|
|
111
|
+
const formatCurrency = (value) => {
|
|
112
|
+
// Values are already in dollars, no need to divide by 100
|
|
113
|
+
return new Intl.NumberFormat('en-US', {
|
|
114
|
+
style: 'currency',
|
|
115
|
+
currency: 'USD',
|
|
116
|
+
minimumFractionDigits: 0,
|
|
117
|
+
maximumFractionDigits: 0,
|
|
118
|
+
}).format(value);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Period comparison component that allows comparing sales metrics across two time periods
|
|
123
|
+
* @param {Object} props - Component props
|
|
124
|
+
* @param {Array} props.currentSales - Current period sales data
|
|
125
|
+
* @param {Object} props.chartState - Current period chart data
|
|
126
|
+
* @param {Object} props.designer - Designer information
|
|
127
|
+
* @param {boolean} props.loading - Loading state
|
|
128
|
+
* @param {Date} props.date - Current date being displayed
|
|
129
|
+
* @param {Function} props.updateLoadingState - Function to update loading state
|
|
130
|
+
* @returns {JSX.Element} Period comparison component
|
|
131
|
+
*/
|
|
132
|
+
export default function PeriodComparison({
|
|
133
|
+
currentSales = [],
|
|
134
|
+
chartState,
|
|
135
|
+
designer = {},
|
|
136
|
+
loading = false,
|
|
137
|
+
date,
|
|
138
|
+
updateLoadingState
|
|
139
|
+
}) {
|
|
140
|
+
// Comparison type: MoM (Month over Month) or YoY (Year over Year)
|
|
141
|
+
const [comparisonType, setComparisonType] = useState('MoM');
|
|
142
|
+
|
|
143
|
+
// Metric to compare: revenue, orders, avgOrderValue, etc.
|
|
144
|
+
const [metric, setMetric] = useState('revenue');
|
|
145
|
+
|
|
146
|
+
// Previous period data
|
|
147
|
+
const [previousSales, setPreviousSales] = useState([]);
|
|
148
|
+
const [previousChartState, setPreviousChartState] = useState(null);
|
|
149
|
+
const [loadingPrevious, setLoadingPrevious] = useState(false);
|
|
150
|
+
|
|
151
|
+
// Process chart data for the metric being compared
|
|
152
|
+
const [comparisonData, setComparisonData] = useState({
|
|
153
|
+
currentPeriodLabel: '',
|
|
154
|
+
previousPeriodLabel: '',
|
|
155
|
+
currentData: [],
|
|
156
|
+
previousData: [],
|
|
157
|
+
xAxisData: []
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Fetch previous period data when date, comparison type, or designer changes
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (!date || !designer?.user || !designer?.password) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const fetchPreviousPeriodData = async () => {
|
|
167
|
+
setLoadingPrevious(true);
|
|
168
|
+
|
|
169
|
+
// Calculate the previous period date based on comparison type
|
|
170
|
+
let previousDate = new Date(date);
|
|
171
|
+
if (comparisonType === 'MoM') {
|
|
172
|
+
// Month over Month - go back 1 month
|
|
173
|
+
previousDate.setUTCMonth(previousDate.getUTCMonth() - 1);
|
|
174
|
+
} else {
|
|
175
|
+
// Year over Year - go back 1 year
|
|
176
|
+
previousDate.setUTCFullYear(previousDate.getUTCFullYear() - 1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const response = await fetch('/api/sales-portal/getSales', {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
headers: { 'Content-Type': 'application/json' },
|
|
183
|
+
body: JSON.stringify({
|
|
184
|
+
user: designer?.user,
|
|
185
|
+
password: designer?.password,
|
|
186
|
+
date: previousDate,
|
|
187
|
+
admin: designer?.admin
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const data = await response.json();
|
|
192
|
+
|
|
193
|
+
if (data.success) {
|
|
194
|
+
setPreviousSales(data.data);
|
|
195
|
+
|
|
196
|
+
// Process previous period chart data
|
|
197
|
+
if (data.data && data.data.length > 0) {
|
|
198
|
+
// Process chart data with correct cents to dollars conversion
|
|
199
|
+
const processedChartData = processChartData(data.data, previousDate);
|
|
200
|
+
setPreviousChartState(processedChartData);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error('Error fetching previous period data:', error);
|
|
205
|
+
} finally {
|
|
206
|
+
setLoadingPrevious(false);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
fetchPreviousPeriodData();
|
|
211
|
+
}, [date, comparisonType, designer?.user, designer?.password, designer?.admin]);
|
|
212
|
+
|
|
213
|
+
// Process comparison data based on selected metric when data changes
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
if (!chartState || !date) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Ensure we have valid chart state before processing
|
|
220
|
+
if (!chartState.xAxis || !chartState.salesData) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Get current period label
|
|
225
|
+
const currentMonthName = new Date(date).toLocaleString("en-US", { timeZone: 'UTC', month: "long" });
|
|
226
|
+
const currentYear = new Date(date).getUTCFullYear();
|
|
227
|
+
const currentPeriodLabel = `${currentMonthName} ${currentYear}`;
|
|
228
|
+
|
|
229
|
+
// Get previous period label
|
|
230
|
+
let previousPeriodLabel = 'Previous Period';
|
|
231
|
+
if (previousChartState) {
|
|
232
|
+
let previousDate = new Date(date);
|
|
233
|
+
if (comparisonType === 'MoM') {
|
|
234
|
+
previousDate.setUTCMonth(previousDate.getUTCMonth() - 1);
|
|
235
|
+
} else {
|
|
236
|
+
previousDate.setUTCFullYear(previousDate.getUTCFullYear() - 1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const previousMonthName = previousDate.toLocaleString("en-US", { timeZone: 'UTC', month: "long" });
|
|
240
|
+
const previousYear = previousDate.getUTCFullYear();
|
|
241
|
+
previousPeriodLabel = `${previousMonthName} ${previousYear}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Generate x-axis data (days 1-31)
|
|
245
|
+
const daysInMonth = new Date(date.getUTCFullYear(), date.getUTCMonth() + 1, 0).getUTCDate();
|
|
246
|
+
const xAxisData = Array.from({ length: daysInMonth }, (_, i) => i + 1);
|
|
247
|
+
|
|
248
|
+
// Generate data arrays based on selected metric
|
|
249
|
+
let currentData = Array(daysInMonth).fill(0);
|
|
250
|
+
let previousData = Array(daysInMonth).fill(0);
|
|
251
|
+
|
|
252
|
+
// Check if we're comparing daily values or cumulative
|
|
253
|
+
const useCumulativeData = true; // Could be a toggle in the UI
|
|
254
|
+
|
|
255
|
+
switch (metric) {
|
|
256
|
+
case 'revenue':
|
|
257
|
+
if (useCumulativeData) {
|
|
258
|
+
currentData = chartState.salesData || Array(daysInMonth).fill(0);
|
|
259
|
+
previousData = previousChartState?.salesData || Array(daysInMonth).fill(0);
|
|
260
|
+
} else {
|
|
261
|
+
// For non-cumulative data, calculate daily values
|
|
262
|
+
currentData = (chartState.salesData || Array(daysInMonth).fill(0)).map((val, i, arr) =>
|
|
263
|
+
i === 0 ? val : val - arr[i-1]
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// Process previous data safely
|
|
267
|
+
previousData = previousChartState?.salesData
|
|
268
|
+
? previousChartState.salesData.map((val, i, arr) =>
|
|
269
|
+
i === 0 ? val : val - arr[i-1]
|
|
270
|
+
)
|
|
271
|
+
: Array(daysInMonth).fill(0);
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
|
|
275
|
+
case 'orders':
|
|
276
|
+
if (useCumulativeData) {
|
|
277
|
+
currentData = chartState.orderData || Array(daysInMonth).fill(0);
|
|
278
|
+
previousData = previousChartState?.orderData || Array(daysInMonth).fill(0);
|
|
279
|
+
} else {
|
|
280
|
+
currentData = (chartState.orderData || Array(daysInMonth).fill(0)).map((val, i, arr) =>
|
|
281
|
+
i === 0 ? val : val - arr[i-1]
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
previousData = previousChartState?.orderData
|
|
285
|
+
? previousChartState.orderData.map((val, i, arr) =>
|
|
286
|
+
i === 0 ? val : val - arr[i-1]
|
|
287
|
+
)
|
|
288
|
+
: Array(daysInMonth).fill(0);
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
|
|
292
|
+
case 'discounts':
|
|
293
|
+
if (useCumulativeData) {
|
|
294
|
+
currentData = chartState.discountData || Array(daysInMonth).fill(0);
|
|
295
|
+
previousData = previousChartState?.discountData || Array(daysInMonth).fill(0);
|
|
296
|
+
} else {
|
|
297
|
+
currentData = (chartState.discountData || Array(daysInMonth).fill(0)).map((val, i, arr) =>
|
|
298
|
+
i === 0 ? val : val - arr[i-1]
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
previousData = previousChartState?.discountData
|
|
302
|
+
? previousChartState.discountData.map((val, i, arr) =>
|
|
303
|
+
i === 0 ? val : val - arr[i-1]
|
|
304
|
+
)
|
|
305
|
+
: Array(daysInMonth).fill(0);
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
case 'refunds':
|
|
310
|
+
if (useCumulativeData) {
|
|
311
|
+
currentData = chartState.refundData || Array(daysInMonth).fill(0);
|
|
312
|
+
previousData = previousChartState?.refundData || Array(daysInMonth).fill(0);
|
|
313
|
+
} else {
|
|
314
|
+
currentData = (chartState.refundData || Array(daysInMonth).fill(0)).map((val, i, arr) =>
|
|
315
|
+
i === 0 ? val : val - arr[i-1]
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
previousData = previousChartState?.refundData
|
|
319
|
+
? previousChartState.refundData.map((val, i, arr) =>
|
|
320
|
+
i === 0 ? val : val - arr[i-1]
|
|
321
|
+
)
|
|
322
|
+
: Array(daysInMonth).fill(0);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
|
|
326
|
+
default:
|
|
327
|
+
currentData = [];
|
|
328
|
+
previousData = [];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Calculate the current day (for limiting data display in current month)
|
|
332
|
+
const currentDate = new Date();
|
|
333
|
+
const isCurrentMonth = date.getUTCMonth() === currentDate.getUTCMonth() &&
|
|
334
|
+
date.getUTCFullYear() === currentDate.getUTCFullYear();
|
|
335
|
+
const currentDay = isCurrentMonth ? currentDate.getUTCDate() : daysInMonth;
|
|
336
|
+
|
|
337
|
+
// Limit the data to the current day if viewing the current month
|
|
338
|
+
if (isCurrentMonth) {
|
|
339
|
+
currentData = currentData.slice(0, currentDay);
|
|
340
|
+
previousData = previousData.slice(0, currentDay);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Set the comparison data
|
|
344
|
+
setComparisonData({
|
|
345
|
+
currentPeriodLabel,
|
|
346
|
+
previousPeriodLabel,
|
|
347
|
+
currentData,
|
|
348
|
+
previousData,
|
|
349
|
+
xAxisData: xAxisData.slice(0, isCurrentMonth ? currentDay : daysInMonth)
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
}, [chartState, previousChartState, date, metric, comparisonType]);
|
|
353
|
+
|
|
354
|
+
// Handle comparison type change
|
|
355
|
+
const handleComparisonTypeChange = (event, newType) => {
|
|
356
|
+
if (newType) {
|
|
357
|
+
setComparisonType(newType);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// Handle metric change
|
|
362
|
+
const handleMetricChange = (event) => {
|
|
363
|
+
setMetric(event.target.value);
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// Get y-axis label based on selected metric
|
|
367
|
+
const getYAxisLabel = () => {
|
|
368
|
+
switch (metric) {
|
|
369
|
+
case 'revenue':
|
|
370
|
+
return 'Revenue';
|
|
371
|
+
case 'orders':
|
|
372
|
+
return 'Order Count';
|
|
373
|
+
case 'discounts':
|
|
374
|
+
return 'Discount Amount';
|
|
375
|
+
case 'refunds':
|
|
376
|
+
return 'Refund Amount';
|
|
377
|
+
default:
|
|
378
|
+
return '';
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Skip rendering if no data
|
|
383
|
+
if (!chartState || currentSales.length === 0) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return (
|
|
388
|
+
<Grid container className={styles.salesSection} sx={{ mt: 4 }}>
|
|
389
|
+
<Grid item xs={12} sx={{ mb: 2 }}>
|
|
390
|
+
<Paper elevation={0} sx={{ p: 2, borderRadius: '4px', border: '1px solid rgba(0, 0, 0, 0.12)' }}>
|
|
391
|
+
<Box sx={{
|
|
392
|
+
display: 'flex',
|
|
393
|
+
justifyContent: 'space-between',
|
|
394
|
+
alignItems: 'center',
|
|
395
|
+
flexWrap: 'wrap',
|
|
396
|
+
mb: 2
|
|
397
|
+
}}>
|
|
398
|
+
<Typography variant="h5" fontWeight="bold">
|
|
399
|
+
Period Comparison
|
|
400
|
+
{(loading || loadingPrevious) && <CircularProgress size={20} sx={{ ml: 2 }} />}
|
|
401
|
+
</Typography>
|
|
402
|
+
|
|
403
|
+
<Box sx={{
|
|
404
|
+
display: 'flex',
|
|
405
|
+
alignItems: 'center',
|
|
406
|
+
gap: 2
|
|
407
|
+
}}>
|
|
408
|
+
{/* Metric selection */}
|
|
409
|
+
<FormControl size="small" sx={{ minWidth: 150 }}>
|
|
410
|
+
<InputLabel id="metric-select-label">Metric</InputLabel>
|
|
411
|
+
<Select
|
|
412
|
+
labelId="metric-select-label"
|
|
413
|
+
id="metric-select"
|
|
414
|
+
value={metric}
|
|
415
|
+
label="Metric"
|
|
416
|
+
onChange={handleMetricChange}
|
|
417
|
+
>
|
|
418
|
+
<MenuItem value="revenue">Revenue</MenuItem>
|
|
419
|
+
<MenuItem value="orders">Orders</MenuItem>
|
|
420
|
+
<MenuItem value="discounts">Discounts</MenuItem>
|
|
421
|
+
<MenuItem value="refunds">Refunds</MenuItem>
|
|
422
|
+
</Select>
|
|
423
|
+
</FormControl>
|
|
424
|
+
|
|
425
|
+
{/* Comparison type toggle */}
|
|
426
|
+
<ToggleButtonGroup
|
|
427
|
+
value={comparisonType}
|
|
428
|
+
exclusive
|
|
429
|
+
onChange={handleComparisonTypeChange}
|
|
430
|
+
size="small"
|
|
431
|
+
>
|
|
432
|
+
<ToggleButton value="MoM">
|
|
433
|
+
Month over Month
|
|
434
|
+
</ToggleButton>
|
|
435
|
+
<ToggleButton value="YoY">
|
|
436
|
+
Year over Year
|
|
437
|
+
</ToggleButton>
|
|
438
|
+
</ToggleButtonGroup>
|
|
439
|
+
</Box>
|
|
440
|
+
</Box>
|
|
441
|
+
|
|
442
|
+
{/* Comparison chart */}
|
|
443
|
+
<ResponsiveChartContainer
|
|
444
|
+
xAxis={[{
|
|
445
|
+
data: comparisonData.xAxisData,
|
|
446
|
+
scaleType: 'band',
|
|
447
|
+
id: 'x-axis-id',
|
|
448
|
+
label: 'Day of Month',
|
|
449
|
+
}]}
|
|
450
|
+
yAxis={[{
|
|
451
|
+
scaleType: 'linear',
|
|
452
|
+
valueFormatter: (value) => {
|
|
453
|
+
if (metric === 'orders') {
|
|
454
|
+
return value;
|
|
455
|
+
}
|
|
456
|
+
return formatCurrency(value);
|
|
457
|
+
},
|
|
458
|
+
id: 'y-axis-id',
|
|
459
|
+
label: getYAxisLabel(),
|
|
460
|
+
}]}
|
|
461
|
+
series={[
|
|
462
|
+
{
|
|
463
|
+
type: 'line',
|
|
464
|
+
data: comparisonData.currentData,
|
|
465
|
+
label: comparisonData.currentPeriodLabel,
|
|
466
|
+
color: 'var(--blue, blue)',
|
|
467
|
+
valueFormatter: (value) => {
|
|
468
|
+
if (metric === 'orders') {
|
|
469
|
+
return value;
|
|
470
|
+
}
|
|
471
|
+
return formatCurrency(value);
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
type: 'line',
|
|
476
|
+
data: comparisonData.previousData,
|
|
477
|
+
label: comparisonData.previousPeriodLabel,
|
|
478
|
+
color: 'var(--grey800, grey)',
|
|
479
|
+
valueFormatter: (value) => {
|
|
480
|
+
if (metric === 'orders') {
|
|
481
|
+
return value;
|
|
482
|
+
}
|
|
483
|
+
return formatCurrency(value);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
]}
|
|
487
|
+
height={300}
|
|
488
|
+
margin={{ left: 70, right: 20, top: 50, bottom: 30 }}
|
|
489
|
+
>
|
|
490
|
+
<ChartsLegend position="top" />
|
|
491
|
+
<LinePlot />
|
|
492
|
+
<ChartsXAxis label="Day of Month" />
|
|
493
|
+
<ChartsYAxis label={getYAxisLabel()} />
|
|
494
|
+
<ChartsTooltip />
|
|
495
|
+
</ResponsiveChartContainer>
|
|
496
|
+
|
|
497
|
+
</Paper>
|
|
498
|
+
</Grid>
|
|
499
|
+
</Grid>
|
|
500
|
+
);
|
|
501
|
+
}
|