@evenicanpm/portal-table-ui 2.3.2 → 2.4.0

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/package.json CHANGED
@@ -1,51 +1,50 @@
1
1
  {
2
- "name": "@evenicanpm/portal-table-ui",
3
- "version": "2.3.2",
4
- "description": "",
5
- "license": "ISC",
6
- "author": "",
7
- "type": "module",
8
- "scripts": {
9
- "test": "echo \"Error: no test specified\" && exit 1",
10
- "docs": "npx typedoc src --plugin typedoc-github-theme",
11
- "format": "npx @biomejs/biome format --write ./src",
12
- "build": "npx tsc"
13
- },
14
- "exports": {
15
- ".": "./src/index.ts"
16
- },
17
- "types": "./dist/index.d.ts",
18
- "files": [
19
- "dist",
20
- "src"
21
- ],
22
- "devDependencies": {
23
- "@emotion/react": "^11.11.3",
24
- "@emotion/styled": "^11.11.0",
25
- "@mui/icons-material": "^7.3.6",
26
- "@mui/material": "^6.1.7",
27
- "@types/react": "^18.3.24",
28
- "json-server": "^1.0.0-beta.3",
29
- "react": "^19.0.0",
30
- "typedoc": "^0.28.15",
31
- "typedoc-github-theme": "^0.3.1",
32
- "typescript": "^5.6.3"
33
- },
34
- "peerDependencies": {
35
- "@emotion/react": "^11.11.3",
36
- "@emotion/styled": "^11.11.0",
37
- "@mui/icons-material": "^7.3.6",
38
- "@mui/material": "^6.1.7",
39
- "@tanstack/react-query": "^5.90.11",
40
- "@types/react": "^18.3.24",
41
- "typescript": "^5.6.3"
42
- },
43
- "dependencies": {
44
- "@arrows/multimethod": "^2.1.0",
45
- "@evenicanpm/ui": "^2.0.0",
46
- "@mui/lab": "^7.0.1-beta.19",
47
- "date-fns": "^4.1.0",
48
- "zod": "^4.4.3"
49
- },
50
- "gitHead": "213ad422feec74815e682bd9950ef94893e5ecda"
51
- }
2
+ "name": "@evenicanpm/portal-table-ui",
3
+ "version": "2.4.0",
4
+ "description": "",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": "./src/index.ts"
10
+ },
11
+ "types": "./dist/index.d.ts",
12
+ "files": [
13
+ "dist",
14
+ "src"
15
+ ],
16
+ "devDependencies": {
17
+ "@emotion/react": "^11.11.3",
18
+ "@emotion/styled": "^11.11.0",
19
+ "@mui/icons-material": "^6.1.7",
20
+ "@mui/material": "^6.1.7",
21
+ "@types/react": "^19.2.2",
22
+ "json-server": "^1.0.0-beta.3",
23
+ "react": "^19.2.7",
24
+ "typedoc": "^0.28.15",
25
+ "typedoc-github-theme": "^0.3.1",
26
+ "typescript": "^5.6.3"
27
+ },
28
+ "peerDependencies": {
29
+ "@emotion/react": "^11.11.3",
30
+ "@emotion/styled": "^11.11.0",
31
+ "@mui/icons-material": "^6.1.7",
32
+ "@mui/material": "^6.1.7",
33
+ "@tanstack/react-query": "^5.100.4",
34
+ "@types/react": "^19.2.2",
35
+ "typescript": "^5.6.3"
36
+ },
37
+ "dependencies": {
38
+ "@arrows/multimethod": "^2.1.0",
39
+ "@mui/lab": "^7.0.1-beta.19",
40
+ "date-fns": "^4.1.0",
41
+ "zod": "^4.4.3",
42
+ "@evenicanpm/ui": "2.4.0"
43
+ },
44
+ "scripts": {
45
+ "test": "echo \"Error: no test specified\" && exit 1",
46
+ "docs": "npx typedoc src --plugin typedoc-github-theme",
47
+ "format": "npx @biomejs/biome format --write ./src",
48
+ "build": "npx tsc"
49
+ }
50
+ }
@@ -124,7 +124,11 @@ const Details = ({
124
124
  resourceName={resourceName}
125
125
  />
126
126
  ) : (
127
- <Grid container spacing={2}>
127
+ <Grid
128
+ container
129
+ spacing={2}
130
+ sx={{ display: { xs: "block", sm: "flex" } }}
131
+ >
128
132
  {primitiveItems?.map((col) => {
129
133
  const maskedValue = col.maskFn(col.value, col.mask);
130
134
  const Component = getRowComponent(maskedValue, col.key);
@@ -199,8 +203,8 @@ const getRowComponent = multi(
199
203
  */
200
204
  const getRowLayout = multi(
201
205
  detailTypeDispatch,
202
- method("PRIMITIVE", () => ({ size: 6, display: "flex" })),
203
- method("STATUS", () => ({ size: 6, display: "flex" })),
206
+ method("PRIMITIVE", () => ({ size: { xs: 12, sm: 6 }, display: "flex" })),
207
+ method("STATUS", () => ({ size: { xs: 12, sm: 6 }, display: "flex" })),
204
208
  method("DOCUMENTS", () => ({ size: 12 })),
205
209
  method("OBJECT", () => ({ size: 12 })),
206
210
  );
@@ -4,6 +4,7 @@ import {
4
4
  Box,
5
5
  Button,
6
6
  Card,
7
+ Chip,
7
8
  Divider,
8
9
  Paper,
9
10
  Skeleton,
@@ -13,12 +14,13 @@ import {
13
14
  TableContainer,
14
15
  TableHead,
15
16
  TableRow,
17
+ Tooltip,
16
18
  Typography,
17
19
  useMediaQuery,
18
20
  useTheme,
19
21
  } from "@mui/material";
20
22
  import Grid2 from "@mui/material/Grid2";
21
- import { StatusPill } from "../../status-pill";
23
+ import type { StatusColorMap } from "../../../../../types/schemas";
22
24
  import { ActionButton } from "../../table-row/action-button";
23
25
  import type { DetailsTemplateProps } from "./template-registry";
24
26
 
@@ -48,6 +50,19 @@ const formatDate = (dateString?: string): string => {
48
50
  }
49
51
  };
50
52
 
53
+ /**
54
+ * InvoiceType enum type.
55
+ */
56
+ export enum InvoiceType {
57
+ None = 0,
58
+ SalesOrderInvoice = 1,
59
+ FreeTextInvoice = 2,
60
+ ProjectInvoice = 3,
61
+ CreditNoteInvoice = 4,
62
+ FreeTextCreditNoteInvoice = 5,
63
+ ProjectCreditNoteInvoice = 6,
64
+ }
65
+
51
66
  interface InvoiceLine {
52
67
  ItemId?: string;
53
68
  ProductName?: string;
@@ -61,6 +76,7 @@ interface InvoiceLine {
61
76
 
62
77
  interface InvoiceData {
63
78
  Status?: string | number;
79
+ InvoiceType?: InvoiceType;
64
80
  IsOverdue?: boolean;
65
81
  SalesInvoiceLine?: InvoiceLine[];
66
82
  InvoiceSubtotal?: number;
@@ -81,6 +97,17 @@ interface InvoiceData {
81
97
  ChargeTotal?: number;
82
98
  }
83
99
 
100
+ export const statusColorMap: StatusColorMap = {
101
+ // Regular invoice labels
102
+ Unpaid: "primary",
103
+ "Partially Paid": "primary",
104
+ Paid: "success",
105
+ // Credit memo labels
106
+ Unused: "primary",
107
+ "Partially Used": "primary",
108
+ Used: "success",
109
+ };
110
+
84
111
  interface TableColumn {
85
112
  label: string;
86
113
  width: number;
@@ -102,9 +129,7 @@ const isDataEmpty = (data: InvoiceData | null | undefined): boolean => {
102
129
 
103
130
  export const InvoiceDetailsTemplate = ({
104
131
  data,
105
- statusColorMap,
106
132
  row,
107
- statusTextMap,
108
133
  loading = false,
109
134
  actions,
110
135
  resourceName = "invoice",
@@ -114,6 +139,13 @@ export const InvoiceDetailsTemplate = ({
114
139
 
115
140
  const invoice = data as InvoiceData;
116
141
 
142
+ const isCreditNote =
143
+ invoice?.InvoiceType === InvoiceType.CreditNoteInvoice ||
144
+ invoice?.InvoiceType === InvoiceType.FreeTextCreditNoteInvoice;
145
+
146
+ const isFreeTextInvoice =
147
+ invoice?.InvoiceType === InvoiceType.FreeTextInvoice;
148
+
117
149
  // Responsive width
118
150
  const containerWidth = isMobile ? "95%" : "75%";
119
151
 
@@ -140,9 +172,12 @@ export const InvoiceDetailsTemplate = ({
140
172
  const tax = invoice?.TotalTaxAmount ?? 0;
141
173
  const total = invoice?.Amount ?? 0;
142
174
  const chargeTotal = invoice?.ChargeTotal ?? 0;
143
- // Fixed column definitions with specific widths
175
+
176
+ // Hide the # column for credit note types
144
177
  const columns: TableColumn[] = [
145
- { label: "#", width: 60, aligned: "left" },
178
+ ...(!isCreditNote && !isFreeTextInvoice
179
+ ? [{ label: "#", width: 60, aligned: "left" as const }]
180
+ : []),
146
181
  { label: "Product", width: 200, aligned: "left" },
147
182
  { label: "Qty", width: 40, aligned: "right" },
148
183
  { label: "Unit", width: 60, aligned: "right" },
@@ -155,6 +190,11 @@ export const InvoiceDetailsTemplate = ({
155
190
  // Calculate total width for table container
156
191
  const totalTableWidth = columns.reduce((sum, col) => sum + col.width, 0);
157
192
 
193
+ // Remap action labels for credit notes
194
+ const resolvedActions = isCreditNote
195
+ ? actions?.map((a) => (a.label === "Pay" ? { ...a, label: "Use" } : a))
196
+ : actions;
197
+
158
198
  if (loading) {
159
199
  return (
160
200
  <Box sx={{ p: { xs: 2, sm: 3 }, width: containerWidth, mx: "auto" }}>
@@ -252,11 +292,7 @@ export const InvoiceDetailsTemplate = ({
252
292
  mb={3}
253
293
  >
254
294
  {invoice?.Status !== undefined && (
255
- <StatusPill
256
- status={invoice.Status}
257
- statusColorMap={statusColorMap}
258
- statusTextMap={statusTextMap}
259
- />
295
+ <Chip label={invoice.Status} color={statusColorMap[invoice.Status]} />
260
296
  )}
261
297
  <Box
262
298
  display="flex"
@@ -264,13 +300,13 @@ export const InvoiceDetailsTemplate = ({
264
300
  alignItems="center"
265
301
  sx={{ "@media print": { display: "none" } }}
266
302
  >
267
- {actions?.map((action) => {
303
+ {resolvedActions?.map((action) => {
268
304
  const enabled = !action.isEnabled || action.isEnabled(row);
269
305
  if (!enabled) {
270
306
  return (
271
- <span
307
+ <Box
272
308
  key={action.label}
273
- style={{
309
+ sx={{
274
310
  display: "inline-block",
275
311
  width: action.icon ? "34px" : "42px",
276
312
  height: "34px",
@@ -280,7 +316,6 @@ export const InvoiceDetailsTemplate = ({
280
316
  }
281
317
  return (
282
318
  <ActionButton
283
- key={action.label}
284
319
  docId={invoice.Id}
285
320
  action={action}
286
321
  resourceName={resourceName}
@@ -293,7 +328,7 @@ export const InvoiceDetailsTemplate = ({
293
328
  size="small"
294
329
  sx={{ "@media print": { display: "none" } }}
295
330
  >
296
- Print Invoice
331
+ {isCreditNote ? "Print Credit Memo" : "Print Invoice"}
297
332
  </Button>
298
333
  </Box>
299
334
  </Box>
@@ -413,20 +448,19 @@ export const InvoiceDetailsTemplate = ({
413
448
  {invoice?.InvoiceDate && (
414
449
  <Grid2 size={{ xs: 12, sm: 6 }}>
415
450
  <Typography variant="body2" color="text.secondary">
416
- Invoice Date
451
+ {isCreditNote ? "Date Issued" : "Invoice Date"}
417
452
  </Typography>
418
453
  <Typography>{formatDate(invoice.InvoiceDate)}</Typography>
419
454
  </Grid2>
420
455
  )}
421
- {invoice?.DueDate && (
456
+ {/* Due date hidden for credit notes */}
457
+ {invoice?.DueDate && !isCreditNote && (
422
458
  <Grid2 size={{ xs: 12, sm: 6 }}>
423
459
  <Typography variant="body2" color="text.secondary">
424
460
  Due Date
425
461
  </Typography>
426
-
427
462
  <Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
428
463
  <Typography>{formatDate(invoice.DueDate)}</Typography>
429
-
430
464
  {invoice?.IsOverdue && (
431
465
  <FlagIcon sx={{ color: "error.main" }} fontSize="small" />
432
466
  )}
@@ -441,18 +475,8 @@ export const InvoiceDetailsTemplate = ({
441
475
  {/* Line Items Section */}
442
476
  {items.length > 0 ? (
443
477
  <>
444
- <TableContainer>
445
- <Table
446
- sx={{
447
- minWidth: totalTableWidth,
448
- "@media print": {
449
- p: 0,
450
- scrollbarWidth: "none !important",
451
- msOverflowStyle: "none !important",
452
- overflow: "visible !important",
453
- },
454
- }}
455
- >
478
+ <TableContainer className="invoice-details-table-container">
479
+ <Table className="invoice-details-table">
456
480
  <TableHead>
457
481
  <TableRow>
458
482
  {columns.map((col) => (
@@ -489,17 +513,20 @@ export const InvoiceDetailsTemplate = ({
489
513
 
490
514
  return (
491
515
  <TableRow key={itemKey}>
492
- <TableCell
493
- sx={{
494
- width: "100%",
495
- textAlign: "left",
496
- "@media print": { width: 60, px: 1 },
497
- }}
498
- >
499
- <Typography variant="body2">
500
- {item?.ItemId || idx + 1}
501
- </Typography>
502
- </TableCell>
516
+ {/* Hide # column for credit note types */}
517
+ {!isCreditNote && !isFreeTextInvoice && (
518
+ <TableCell
519
+ sx={{
520
+ width: "100%",
521
+ textAlign: "left",
522
+ "@media print": { width: 60, px: 1 },
523
+ }}
524
+ >
525
+ <Typography variant="body2">
526
+ {item?.ItemId || idx + 1}
527
+ </Typography>
528
+ </TableCell>
529
+ )}
503
530
  <TableCell sx={{ width: "100%", textAlign: "left" }}>
504
531
  <Typography variant="body2">{productName}</Typography>
505
532
  </TableCell>
@@ -536,15 +563,27 @@ export const InvoiceDetailsTemplate = ({
536
563
  <TableCell
537
564
  sx={{
538
565
  width: "100%",
539
- textAlign: "center",
566
+ textAlign: "left",
567
+ px: 0,
540
568
  "@media print": { width: "auto", minWidth: 0 },
541
569
  }}
542
570
  align="center"
543
571
  >
544
572
  {item.HasDiscount && (
545
- <Typography color="error.main" fontWeight={700}>
546
- *
547
- </Typography>
573
+ <Tooltip
574
+ title="Indicates the line item has savings."
575
+ arrow
576
+ disableHoverListener={isMobile}
577
+ >
578
+ <Typography
579
+ component="span"
580
+ color="error.main"
581
+ fontWeight={700}
582
+ sx={{ cursor: "pointer" }}
583
+ >
584
+ *
585
+ </Typography>
586
+ </Tooltip>
548
587
  )}
549
588
  </TableCell>
550
589
  </TableRow>
@@ -554,6 +593,14 @@ export const InvoiceDetailsTemplate = ({
554
593
  </Table>
555
594
  </TableContainer>
556
595
 
596
+ {isMobile && items.some((item) => item.HasDiscount) && (
597
+ <Box>
598
+ <Typography variant="caption" color="text.secondary">
599
+ * Indicates the line item has savings.
600
+ </Typography>
601
+ </Box>
602
+ )}
603
+
557
604
  {/* Totals */}
558
605
  <Box mt={3} display="flex" justifyContent="flex-end">
559
606
  <Box width={{ xs: "100%", sm: 300 }}>
@@ -578,31 +625,40 @@ export const InvoiceDetailsTemplate = ({
578
625
  <Divider sx={{ my: 1.5 }} />
579
626
  <Box display="flex" justifyContent="space-between" mt={1}>
580
627
  <Typography variant="body1" fontWeight={700}>
581
- Total
628
+ {isCreditNote ? "Credit Total" : "Total"}
582
629
  </Typography>
583
630
  <Typography variant="body1" fontWeight={700}>
584
631
  {formatCurrency(total)}
585
632
  </Typography>
586
633
  </Box>
587
634
 
588
- {/* Paid and Remaining lines */}
589
- {invoice?.Status !== 1 && (
635
+ {/* Paid / Applied line */}
636
+ {invoice?.Status !== "Paid" && invoice?.Status !== "Used" && (
590
637
  <Box display="flex" justifyContent="space-between" mt={1}>
591
- <Typography variant="body2">Paid</Typography>
592
638
  <Typography variant="body2">
593
- {formatCurrency(total - (invoice?.AmountBalance ?? 0))}
639
+ {isCreditNote ? "Used" : "Paid"}
594
640
  </Typography>
595
- </Box>
596
- )}
597
-
598
- {invoice?.Status !== 1 && (invoice?.AmountBalance ?? 0) > 0 && (
599
- <Box display="flex" justifyContent="space-between" mt={0.5}>
600
- <Typography variant="body2">Remaining</Typography>
601
641
  <Typography variant="body2">
602
- {formatCurrency(invoice?.AmountBalance ?? 0)}
642
+ {formatCurrency(
643
+ Math.abs(total - (invoice?.AmountBalance ?? 0)),
644
+ )}
603
645
  </Typography>
604
646
  </Box>
605
647
  )}
648
+
649
+ {/* Remaining line */}
650
+ {invoice?.Status !== "Paid" &&
651
+ invoice?.Status !== "Used" &&
652
+ (invoice?.AmountBalance ?? 0) !== 0 && (
653
+ <Box display="flex" justifyContent="space-between" mt={0.5}>
654
+ <Typography variant="body2">
655
+ {isCreditNote ? "Remaining Credit" : "Remaining"}
656
+ </Typography>
657
+ <Typography variant="body2">
658
+ {formatCurrency(Math.abs(invoice?.AmountBalance ?? 0))}
659
+ </Typography>
660
+ </Box>
661
+ )}
606
662
  </Box>
607
663
  </Box>
608
664
  </>
@@ -25,7 +25,7 @@ const ColumnSelect = ({
25
25
  id="demo-simple-select"
26
26
  value={selected}
27
27
  sx={{
28
- maxWidth: "150px",
28
+ maxWidth: "180px",
29
29
  "& .MuiOutlinedInput-notchedOutline": { border: "none" },
30
30
  "& .MuiSelect-select": {
31
31
  paddingTop: 0,
@@ -336,8 +336,12 @@ export default function DocumentTable({
336
336
  );
337
337
  })}
338
338
  </FlexBox>
339
- <TableContainer>
340
- <Table aria-label="simple table" sx={{ tableLayout: { md: "fixed" } }}>
339
+ <TableContainer className="invoice-list-table-container">
340
+ <Table
341
+ className="invoice-list-table"
342
+ aria-label="simple table"
343
+ sx={{ tableLayout: { md: "fixed" } }}
344
+ >
341
345
  <TableHeader
342
346
  columns={visibleCols}
343
347
  loading={configPending}
@@ -85,7 +85,7 @@ export function resolveActions(
85
85
  return [
86
86
  {
87
87
  ...action,
88
- label: a.Name,
88
+ label: action.label ?? a.Name,
89
89
  },
90
90
  ];
91
91
  });
@@ -105,6 +105,6 @@ export function resolveBulkActions(
105
105
  console.warn(`Unknown bulk action type: ${a.Type}`);
106
106
  return [];
107
107
  }
108
- return [{ ...action, label: a.Name }];
108
+ return [{ ...action, label: action.label ?? a.Name }];
109
109
  });
110
110
  }