@liiift-studio/sales-portal 3.1.4 → 3.1.6

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.
@@ -67,6 +67,7 @@ export default function Insights({
67
67
  let grossRevenue, netRevenue, orderCount, averageOrderValue, discountTotal, discountRate, refundTotal, refundRate;
68
68
  let topLocation = 'Unknown', topLocationPercentage = 0, locationChange = null;
69
69
  let hasPreviousData = false;
70
+ let previousRevenue = 0, previousOrderCount = 0, previousAvgOrderValue = 0, previousRefundTotal = 0;
70
71
  let revenueChange = null, orderCountChange = null, avgOrderValueChange = null, refundChange = null;
71
72
 
72
73
  if (isYearView) {
@@ -113,11 +114,6 @@ export default function Insights({
113
114
  locationChange = locData.locationChange ?? null;
114
115
 
115
116
  // Calculate previous period metrics (if available)
116
- let previousRevenue = 0;
117
- let previousOrderCount = 0;
118
- let previousAvgOrderValue = 0;
119
- let previousRefundTotal = 0;
120
-
121
117
  if (previousSales && previousSales.length > 0) {
122
118
  hasPreviousData = true;
123
119
  previousOrderCount = previousSales.length;
@@ -167,21 +163,21 @@ export default function Insights({
167
163
  title: 'Gross Sales',
168
164
  value: formatCurrency(grossRevenue),
169
165
  change: revenueChange,
170
- tooltip: isYearView ? `${year} year total` : hasPreviousData ? 'vs prior month' : 'No prior data available',
166
+ tooltip: isYearView ? `${year} year total` : hasPreviousData ? `Previous: ${formatCurrency(previousRevenue)}` : 'No prior data available',
171
167
  bgcolor: '#000000',
172
168
  },
173
169
  {
174
170
  title: 'Orders',
175
171
  value: orderCount,
176
172
  change: orderCountChange,
177
- tooltip: isYearView ? `${year} year total` : hasPreviousData ? 'vs prior month' : 'No prior data available',
173
+ tooltip: isYearView ? `${year} year total` : hasPreviousData ? `Previous: ${previousOrderCount}` : 'No prior data available',
178
174
  bgcolor: '#000000',
179
175
  },
180
176
  {
181
177
  title: 'Avg. Order Value',
182
178
  value: formatCurrency(averageOrderValue),
183
179
  change: avgOrderValueChange,
184
- tooltip: isYearView ? `${year} year average` : hasPreviousData ? 'vs prior month' : 'No prior data available',
180
+ tooltip: isYearView ? `${year} year average` : hasPreviousData ? `Previous: ${formatCurrency(previousAvgOrderValue)}` : 'No prior data available',
185
181
  bgcolor: '#000000',
186
182
  },
187
183
  {
@@ -253,6 +249,7 @@ export default function Insights({
253
249
  sx={{
254
250
  display: 'flex',
255
251
  flexWrap: 'wrap',
252
+ width: '100%',
256
253
  gap: isMobile ? 1.5 : 2,
257
254
  mt: 2,
258
255
  position: 'relative',
@@ -706,56 +706,53 @@ export default function Sales(props) {
706
706
  const portalIndex = allPortals.indexOf(currentPortal) + 1;
707
707
 
708
708
  style.textContent = `
709
- @media print {
710
- @page {
711
- size: letter portrait;
712
- margin: 0.5in;
713
- }
714
- /* Hide everything except this sales portal */
715
- header, nav, footer, #navBar, #footer, #titleContainer,
716
- .exportSection, .date-range-sales-table-wrapper,
717
- .salesPortal:not(:nth-child(${portalIndex})) {
718
- display: none !important;
719
- }
720
- .salesPortal, .salesPortalWrap {
721
- background: white !important;
722
- }
723
- body {
724
- -webkit-print-color-adjust: exact !important;
725
- print-color-adjust: exact !important;
726
- }
727
- /* Clean up layout */
728
- .salesPortal {
729
- padding: 0 !important;
730
- width: 100% !important;
731
- }
732
- .salesPortal > * {
733
- page-break-inside: avoid;
734
- }
735
- /* Hide interactive elements */
736
- button, .MuiIconButton-root, .MuiInput-root,
737
- .MuiDateCalendar-root, .print-button,
738
- .year-overview-wrapper, .sales-chart-wrapper {
739
- display: none !important;
740
- }
741
- /* Full width sections */
742
- .sales-header-section, .sales-data-section,
743
- .insights-wrapper, .sales-table-wrapper {
744
- width: 100% !important;
745
- padding: 0 !important;
746
- margin: 10px 0 !important;
747
- }
748
- /* Readable text */
749
- .MuiTypography-root {
750
- color: black !important;
751
- }
752
- /* Compact cards for print */
753
- .MuiPaper-root {
754
- box-shadow: none !important;
755
- border: 1px solid #ddd !important;
756
- page-break-inside: avoid;
757
- }
758
- }
709
+ @media print {
710
+ @page { size: letter portrait; margin: 0.5in; }
711
+ body { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
712
+
713
+ /* Hide site chrome and other portals */
714
+ header, nav, footer, #navBar, #footer, #titleContainer,
715
+ .exportSection, .date-range-sales-table-wrapper,
716
+ .salesPortal:not(:nth-child(${portalIndex})) { display: none !important; }
717
+
718
+ /* Hide interactive controls */
719
+ .MuiIconButton-root, .MuiInput-root, .MuiDateCalendar-root,
720
+ .print-button, .MuiFilledInput-root, .MuiFormControl-root { display: none !important; }
721
+
722
+ /* Portal layout */
723
+ .salesPortal, .salesPortalWrap { background: white !important; padding: 0 !important; width: 100% !important; }
724
+
725
+ /* Full width sections */
726
+ .sales-header-section, .sales-data-section, .insights-wrapper, .sales-table-wrapper,
727
+ .sales-chart-wrapper, .year-overview-wrapper, .typeface-list-wrapper,
728
+ .license-type-list-wrapper, .top-performers-wrapper { width: 100% !important; padding: 0 !important; margin: 10px 0 !important; }
729
+
730
+ /* Page break control */
731
+ .insights-wrapper, .top-performers-wrapper, .typeface-list-wrapper,
732
+ .license-type-list-wrapper, .sales-table-wrapper { page-break-inside: avoid; break-inside: avoid; }
733
+
734
+ /* Prevent cutting elements across pages */
735
+ .MuiPaper-root, tr, .designers-section > div,
736
+ .top-performers-section > div, .product-section > div { page-break-inside: avoid; break-inside: avoid; }
737
+
738
+ /* Insight cards: keep dark bg with white text */
739
+ .insights-wrapper .MuiPaper-root { background-color: #000 !important; color: white !important; border: none !important; box-shadow: none !important; }
740
+ .insights-wrapper .MuiTypography-root { color: inherit !important; }
741
+
742
+ /* Other text: black on white */
743
+ .sales-header-section .MuiTypography-root, .typeface-list-wrapper .MuiTypography-root,
744
+ .license-type-list-wrapper .MuiTypography-root, .sales-table-wrapper .MuiTypography-root { color: black !important; }
745
+
746
+ /* Section header bars: keep dark bg */
747
+ .designers-section .MuiTypography-h5, .top-performers-section .MuiTypography-h5,
748
+ .product-section .MuiTypography-h5 { color: white !important; }
749
+
750
+ /* Charts: ensure visible */
751
+ .sales-chart-wrapper, .year-overview-wrapper { max-height: 400px !important; overflow: hidden !important; page-break-inside: avoid; }
752
+
753
+ /* Compact tables */
754
+ .MuiTableCell-root { padding: 4px 8px !important; font-size: 10px !important; }
755
+ }
759
756
  `;
760
757
  document.head.appendChild(style);
761
758
 
@@ -196,7 +196,7 @@ export default function TopPerformers({
196
196
 
197
197
  {/* Individual typefaces for this designer */}
198
198
  {typefaceData
199
- ?.filter(typeface => typeface.author._id === designer._id)
199
+ ?.filter(typeface => typeface.author?._id === designer._id)
200
200
  .map((typeface, j) => (
201
201
  <Typography
202
202
  key={`designer-typeface-${i}-${j}`}
@@ -209,7 +209,7 @@ export default function TopPerformers({
209
209
  }}>
210
210
  {typeface.title}
211
211
  <span className={styles.earningContainer}>
212
- <strong> {(typeface.total / (total + chartState.refundTotal)).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</strong>%
212
+ <strong> {(typeface.total / (total + (chartState?.refundTotal || 0))).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</strong>%
213
213
  </span>
214
214
  <span style={{ opacity: "0.5", textTransform: "none" }}> ({typeface.orders} Orders for a total of </span>
215
215
  <span style={{ opacity: "0.5" }}> USD</span>$<strong>{(typeface.total / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })}</strong>
@@ -112,16 +112,6 @@ export default function YearOverview({ data, loading = false, error = '', year,
112
112
 
113
113
  return (
114
114
  <Box sx={{ width: '100%' }}>
115
- {/* Year total */}
116
- <Typography variant="h6" sx={{ mb: 2, opacity: 0.7 }}>
117
- {year} Total: {symbol}{(data.yearTotal / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })}
118
- {data.yearShipping > 0 && (
119
- <span style={{ opacity: 0.5, fontSize: '0.8em' }}>
120
- {' '}(+ {symbol}{(data.yearShipping / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })} shipping)
121
- </span>
122
- )}
123
- </Typography>
124
-
125
115
  {/* Stacked bar chart */}
126
116
  {series.length > 0 ? (
127
117
  <Box sx={{ width: '100%', height: { xs: 200, sm: 300, md: 350 } }}>
@@ -176,35 +166,63 @@ export default function YearOverview({ data, loading = false, error = '', year,
176
166
  )}
177
167
 
178
168
  {/* Monthly breakdown table */}
179
- <Box sx={{ mt: 4, opacity: 0.8 }}>
169
+ <Box sx={{
170
+ mt: 4,
171
+ mb: 6,
172
+ opacity: 0.8,
173
+ display: 'grid',
174
+ gridTemplateColumns: 'auto auto auto',
175
+ columnGap: { xs: 3, sm: 4 },
176
+ width: 'fit-content',
177
+ }}>
180
178
  {data.months.map((m, i) => (
181
- <Box
182
- key={i}
183
- onClick={() => onMonthClick && onMonthClick(i)}
184
- sx={{
185
- display: 'flex',
186
- justifyContent: 'space-between',
187
- py: { xs: 1.5, sm: 0.75 },
188
- px: { xs: 1.5, sm: 1 },
189
- borderBottom: '1px solid rgba(0,0,0,0.06)',
190
- cursor: 'pointer',
191
- '&:hover': { bgcolor: 'rgba(0,0,0,0.03)' },
192
- borderRadius: '4px',
193
- }}
194
- >
195
- <Typography variant="body2">
179
+ <React.Fragment key={i}>
180
+ <Typography
181
+ variant="body2"
182
+ onClick={() => onMonthClick && onMonthClick(i)}
183
+ sx={{
184
+ py: { xs: 1.5, sm: 0.75 },
185
+ pl: { xs: 1.5, sm: 1 },
186
+ cursor: 'pointer',
187
+ borderBottom: '1px solid rgba(0,0,0,0.06)',
188
+ '&:hover': { bgcolor: 'rgba(0,0,0,0.03)' },
189
+ borderRadius: '4px 0 0 4px',
190
+ }}
191
+ >
196
192
  {MONTH_LABELS[i]} {year}
197
193
  {m.error && <span style={{ color: 'var(--red, red)', marginLeft: 8 }}>error</span>}
198
194
  </Typography>
199
- <Box sx={{ display: 'flex', gap: { xs: 1.5, sm: 3 } }}>
200
- <Typography variant="body2" sx={{ opacity: 0.5 }}>
201
- {m.salesCount} sale{m.salesCount !== 1 ? 's' : ''}
202
- </Typography>
203
- <Typography variant="body2" sx={{ fontWeight: 'bold' }}>
204
- {symbol}{(m.total / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })}
205
- </Typography>
206
- </Box>
207
- </Box>
195
+ <Typography
196
+ variant="body2"
197
+ onClick={() => onMonthClick && onMonthClick(i)}
198
+ sx={{
199
+ py: { xs: 1.5, sm: 0.75 },
200
+ opacity: 0.5,
201
+ textAlign: 'right',
202
+ cursor: 'pointer',
203
+ borderBottom: '1px solid rgba(0,0,0,0.06)',
204
+ '&:hover': { bgcolor: 'rgba(0,0,0,0.03)' },
205
+ }}
206
+ >
207
+ {m.salesCount} sale{m.salesCount !== 1 ? 's' : ''}
208
+ </Typography>
209
+ <Typography
210
+ variant="body2"
211
+ onClick={() => onMonthClick && onMonthClick(i)}
212
+ sx={{
213
+ py: { xs: 1.5, sm: 0.75 },
214
+ pr: { xs: 1.5, sm: 1 },
215
+ fontWeight: 'bold',
216
+ textAlign: 'right',
217
+ cursor: 'pointer',
218
+ borderBottom: '1px solid rgba(0,0,0,0.06)',
219
+ '&:hover': { bgcolor: 'rgba(0,0,0,0.03)' },
220
+ borderRadius: '0 4px 4px 0',
221
+ }}
222
+ >
223
+ {symbol}{(m.total / 100).toLocaleString('en-US', { minimumFractionDigits: 2 })}
224
+ </Typography>
225
+ </React.Fragment>
208
226
  ))}
209
227
  </Box>
210
228
  </Box>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liiift-studio/sales-portal",
3
- "version": "3.1.4",
3
+ "version": "3.1.6",
4
4
  "description": "Centralized sales portal package for Liiift Studio projects",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -446,6 +446,7 @@ export function processDesignersData(typefaceData) {
446
446
  let designersData = [];
447
447
 
448
448
  typefaceData?.forEach(typeface => {
449
+ if (!typeface.author?._id) return;
449
450
  let designer = designersData.find(author => author._id === typeface.author._id);
450
451
  if (designer) {
451
452
  designer.total += typeface.total;