@evenicanpm/portal-table-ui 2.0.1 → 2.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/package.json +2 -2
- package/src/features/table/components/dashboard/table/bulk-action-button.tsx +10 -4
- package/src/features/table/components/dashboard/table/details/details.tsx +18 -3
- package/src/features/table/components/dashboard/table/details/templates/invoice-details.tsx +85 -24
- package/src/features/table/components/dashboard/table/table-header.tsx +1 -1
- package/src/features/table/components/dashboard/table/table-input.tsx +6 -2
- package/src/features/table/components/dashboard/table/table-row/action-button.tsx +4 -1
- package/src/features/table/components/dashboard/table/table-row/table-row.tsx +2 -9
- package/src/features/table/components/dashboard/table/table.tsx +6 -3
- package/src/features/table/components/dashboard/table-dashboard.tsx +22 -1
- package/src/features/table/types/schemas.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evenicanpm/portal-table-ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"@tanstack/react-query": "^5.90.11",
|
|
48
48
|
"date-fns": "^4.1.0"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "69aee6408276ef8e1428ef5c871c5dbf63e7b59a"
|
|
51
51
|
}
|
|
@@ -25,11 +25,17 @@ export function BulkActionButton({
|
|
|
25
25
|
const { mutate, isPending } = useMutation(mutationOptions);
|
|
26
26
|
|
|
27
27
|
const handleClick = () => {
|
|
28
|
-
mutate(
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
mutate(
|
|
29
|
+
{
|
|
30
|
+
resourceName,
|
|
31
|
+
selectedIds,
|
|
31
32
|
},
|
|
32
|
-
|
|
33
|
+
{
|
|
34
|
+
onSuccess: () => {
|
|
35
|
+
clearSelection();
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
);
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
const Icon = action.icon;
|
|
@@ -87,13 +87,28 @@ const Details = ({
|
|
|
87
87
|
mb={2}
|
|
88
88
|
height="100%"
|
|
89
89
|
>
|
|
90
|
-
<Button
|
|
90
|
+
<Button
|
|
91
|
+
onClick={handleBack}
|
|
92
|
+
startIcon={<ArrowBackIcon />}
|
|
93
|
+
sx={{ "@media print": { display: "none" } }}
|
|
94
|
+
>
|
|
91
95
|
{documentTypeLabel}
|
|
92
96
|
</Button>
|
|
93
|
-
<H6 color="grey.800">
|
|
97
|
+
<H6 color="grey.800" sx={{ "@media print": { display: "none" } }}>
|
|
94
98
|
{documentTypeLabel}-{id}
|
|
95
99
|
</H6>
|
|
96
|
-
<
|
|
100
|
+
<H6
|
|
101
|
+
color="grey.800"
|
|
102
|
+
sx={{
|
|
103
|
+
display: "none",
|
|
104
|
+
"@media print": { display: "inline-block" },
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
{documentTypeLabel.slice(0, documentTypeLabel.length - 1)} #{id}
|
|
108
|
+
</H6>
|
|
109
|
+
<Subtitle sx={{ "@media print": { display: "none" } }}>
|
|
110
|
+
DETAILS
|
|
111
|
+
</Subtitle>
|
|
97
112
|
</FlexBox>
|
|
98
113
|
</Box>
|
|
99
114
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Typography,
|
|
16
16
|
useMediaQuery,
|
|
17
17
|
useTheme,
|
|
18
|
+
Button,
|
|
18
19
|
} from "@mui/material";
|
|
19
20
|
import Grid2 from "@mui/material/Grid2";
|
|
20
21
|
import { StatusPill } from "../../status-pill";
|
|
@@ -79,6 +80,12 @@ interface InvoiceData {
|
|
|
79
80
|
OrderedByCustomerName?: string;
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
interface TableColumn {
|
|
84
|
+
label: string;
|
|
85
|
+
width: number;
|
|
86
|
+
aligned: "inherit" | "left" | "center" | "right" | "justify";
|
|
87
|
+
}
|
|
88
|
+
|
|
82
89
|
const detailSkeletonKeys = ["from", "bill-to", "invoice-date", "due-date"];
|
|
83
90
|
const tableSkeletonRowKeys = ["line-1", "line-2", "line-3", "line-4"];
|
|
84
91
|
const totalSkeletonKeys = ["subtotal", "tax", "total"];
|
|
@@ -133,15 +140,15 @@ export const InvoiceDetailsTemplate = ({
|
|
|
133
140
|
const total = invoice?.Amount ?? 0;
|
|
134
141
|
|
|
135
142
|
// Fixed column definitions with specific widths
|
|
136
|
-
const columns = [
|
|
137
|
-
{ label: "#", width: 60 },
|
|
138
|
-
{ label: "Product", width: 200 },
|
|
139
|
-
{ label: "Qty", width: 60 },
|
|
140
|
-
{ label: "Unit", width: 60 },
|
|
141
|
-
{ label: "Unit Price", width: 120 },
|
|
142
|
-
{ label: "Discount", width: 100 },
|
|
143
|
-
{ label: "Line Total", width: 120 },
|
|
144
|
-
{ label: "", width: 40 },
|
|
143
|
+
const columns: TableColumn[] = [
|
|
144
|
+
{ label: "#", width: 60, aligned: "left" },
|
|
145
|
+
{ label: "Product", width: 200, aligned: "left" },
|
|
146
|
+
{ label: "Qty", width: 60, aligned: "right" },
|
|
147
|
+
{ label: "Unit", width: 60, aligned: "right" },
|
|
148
|
+
{ label: "Unit Price", width: 120, aligned: "right" },
|
|
149
|
+
{ label: "Discount", width: 100, aligned: "right" },
|
|
150
|
+
{ label: "Line Total", width: 120, aligned: "right" },
|
|
151
|
+
{ label: "", width: 40, aligned: "center" },
|
|
145
152
|
];
|
|
146
153
|
|
|
147
154
|
// Calculate total width for table container
|
|
@@ -221,8 +228,19 @@ export const InvoiceDetailsTemplate = ({
|
|
|
221
228
|
);
|
|
222
229
|
}
|
|
223
230
|
|
|
231
|
+
function handleOnClick() {
|
|
232
|
+
window.print();
|
|
233
|
+
}
|
|
234
|
+
|
|
224
235
|
return (
|
|
225
|
-
<Box
|
|
236
|
+
<Box
|
|
237
|
+
sx={{
|
|
238
|
+
p: { xs: 2, sm: 3 },
|
|
239
|
+
width: containerWidth,
|
|
240
|
+
mx: "auto",
|
|
241
|
+
"@media print": { p: 0, width: "100%" },
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
226
244
|
{/* Header */}
|
|
227
245
|
<Box
|
|
228
246
|
display="flex"
|
|
@@ -239,7 +257,12 @@ export const InvoiceDetailsTemplate = ({
|
|
|
239
257
|
statusTextMap={statusTextMap}
|
|
240
258
|
/>
|
|
241
259
|
)}
|
|
242
|
-
<Box
|
|
260
|
+
<Box
|
|
261
|
+
display="flex"
|
|
262
|
+
gap={2}
|
|
263
|
+
alignItems="center"
|
|
264
|
+
sx={{ "@media print": { display: "none" } }}
|
|
265
|
+
>
|
|
243
266
|
{actions?.map((action) => {
|
|
244
267
|
const enabled = !action.isEnabled || action.isEnabled(row);
|
|
245
268
|
if (!enabled) {
|
|
@@ -263,14 +286,25 @@ export const InvoiceDetailsTemplate = ({
|
|
|
263
286
|
/>
|
|
264
287
|
);
|
|
265
288
|
})}
|
|
289
|
+
<Button
|
|
290
|
+
onClick={handleOnClick}
|
|
291
|
+
variant="outlined"
|
|
292
|
+
sx={{ "@media print": { display: "none" } }}
|
|
293
|
+
>
|
|
294
|
+
Print Invoice
|
|
295
|
+
</Button>
|
|
266
296
|
</Box>
|
|
267
297
|
</Box>
|
|
268
298
|
|
|
269
299
|
{/* Notes Section */}
|
|
270
300
|
{invoice?.Notes && (
|
|
271
301
|
<Card
|
|
272
|
-
|
|
273
|
-
|
|
302
|
+
sx={{
|
|
303
|
+
p: { xs: 2, sm: 3 },
|
|
304
|
+
borderRadius: 3,
|
|
305
|
+
mb: 3,
|
|
306
|
+
"@media print": { border: "none", boxShadow: "none", p: 0 },
|
|
307
|
+
}}
|
|
274
308
|
>
|
|
275
309
|
<Typography variant="subtitle1" color="text.secondary" mb={1}>
|
|
276
310
|
Notes
|
|
@@ -286,7 +320,13 @@ export const InvoiceDetailsTemplate = ({
|
|
|
286
320
|
)}
|
|
287
321
|
|
|
288
322
|
{/* Main Invoice Card */}
|
|
289
|
-
<Card
|
|
323
|
+
<Card
|
|
324
|
+
sx={{
|
|
325
|
+
p: { xs: 2, sm: 3 },
|
|
326
|
+
borderRadius: 3,
|
|
327
|
+
"@media print": { border: "none", boxShadow: "none", p: 0 },
|
|
328
|
+
}}
|
|
329
|
+
>
|
|
290
330
|
{/* Details Section */}
|
|
291
331
|
{(invoice?.CompanyName ||
|
|
292
332
|
invoice?.Name ||
|
|
@@ -400,7 +440,17 @@ export const InvoiceDetailsTemplate = ({
|
|
|
400
440
|
{items.length > 0 ? (
|
|
401
441
|
<>
|
|
402
442
|
<TableContainer>
|
|
403
|
-
<Table
|
|
443
|
+
<Table
|
|
444
|
+
sx={{
|
|
445
|
+
minWidth: totalTableWidth,
|
|
446
|
+
"@media print": {
|
|
447
|
+
p: 0,
|
|
448
|
+
scrollbarWidth: "none !important",
|
|
449
|
+
msOverflowStyle: "none !important",
|
|
450
|
+
overflow: "visible !important",
|
|
451
|
+
},
|
|
452
|
+
}}
|
|
453
|
+
>
|
|
404
454
|
<TableHead>
|
|
405
455
|
<TableRow>
|
|
406
456
|
{columns.map((col) => (
|
|
@@ -412,6 +462,7 @@ export const InvoiceDetailsTemplate = ({
|
|
|
412
462
|
fontWeight: 600,
|
|
413
463
|
backgroundColor: theme.palette.background.paper,
|
|
414
464
|
}}
|
|
465
|
+
align={col.aligned}
|
|
415
466
|
>
|
|
416
467
|
<Typography variant="body2" fontWeight={600}>
|
|
417
468
|
{col.label}
|
|
@@ -435,29 +486,35 @@ export const InvoiceDetailsTemplate = ({
|
|
|
435
486
|
].join("-");
|
|
436
487
|
|
|
437
488
|
return (
|
|
438
|
-
<TableRow key={
|
|
439
|
-
<TableCell
|
|
489
|
+
<TableRow key={`${item.ItemId}-${idx}`}>
|
|
490
|
+
<TableCell
|
|
491
|
+
sx={{
|
|
492
|
+
width: "100%",
|
|
493
|
+
textAlign: "left",
|
|
494
|
+
"@media print": { width: 60, px: 1 },
|
|
495
|
+
}}
|
|
496
|
+
>
|
|
440
497
|
<Typography variant="body2">
|
|
441
498
|
{item?.ItemId || idx + 1}
|
|
442
499
|
</Typography>
|
|
443
500
|
</TableCell>
|
|
444
|
-
<TableCell sx={{ width:
|
|
501
|
+
<TableCell sx={{ width: "100%", textAlign: "left" }}>
|
|
445
502
|
<Typography variant="body2">{productName}</Typography>
|
|
446
503
|
</TableCell>
|
|
447
|
-
<TableCell sx={{ width:
|
|
504
|
+
<TableCell sx={{ width: "100%", textAlign: "right" }}>
|
|
448
505
|
<Typography variant="body2">{quantity}</Typography>
|
|
449
506
|
</TableCell>
|
|
450
|
-
<TableCell sx={{ width:
|
|
507
|
+
<TableCell sx={{ width: "100%", textAlign: "right" }}>
|
|
451
508
|
<Typography variant="body2">
|
|
452
509
|
{item?.UnitOfMeasure || "—"}
|
|
453
510
|
</Typography>
|
|
454
511
|
</TableCell>
|
|
455
|
-
<TableCell sx={{ width:
|
|
512
|
+
<TableCell sx={{ width: "100%", textAlign: "right" }}>
|
|
456
513
|
<Typography variant="body2">
|
|
457
514
|
{formatCurrency(unitPrice)}
|
|
458
515
|
</Typography>
|
|
459
516
|
</TableCell>
|
|
460
|
-
<TableCell sx={{ width: 100,
|
|
517
|
+
<TableCell sx={{ width: "100%", textAlign: "right" }}>
|
|
461
518
|
<Typography
|
|
462
519
|
variant="body2"
|
|
463
520
|
color={
|
|
@@ -469,13 +526,17 @@ export const InvoiceDetailsTemplate = ({
|
|
|
469
526
|
: formatCurrency(0)}
|
|
470
527
|
</Typography>
|
|
471
528
|
</TableCell>
|
|
472
|
-
<TableCell sx={{ width: 100,
|
|
529
|
+
<TableCell sx={{ width: "100%", textAlign: "right" }}>
|
|
473
530
|
<Typography variant="body2" fontWeight={500}>
|
|
474
531
|
{formatCurrency(lineTotal)}
|
|
475
532
|
</Typography>
|
|
476
533
|
</TableCell>
|
|
477
534
|
<TableCell
|
|
478
|
-
sx={{
|
|
535
|
+
sx={{
|
|
536
|
+
width: "100%",
|
|
537
|
+
textAlign: "center",
|
|
538
|
+
"@media print": { width: "auto", minWidth: 0 },
|
|
539
|
+
}}
|
|
479
540
|
align="center"
|
|
480
541
|
>
|
|
481
542
|
{item.HasDiscount && (
|
|
@@ -78,10 +78,14 @@ const SearchField = React.memo(
|
|
|
78
78
|
onChange={(e) => {
|
|
79
79
|
setSearchTerm(e.target.value);
|
|
80
80
|
}}
|
|
81
|
-
placeholder={
|
|
81
|
+
placeholder={
|
|
82
|
+
selectedColumn?.Label
|
|
83
|
+
? `Search by ${selectedColumn.Label}`
|
|
84
|
+
: "Select a column to search"
|
|
85
|
+
}
|
|
82
86
|
endAdornment={
|
|
83
87
|
<ColumnSelect
|
|
84
|
-
selected={selectedColumn
|
|
88
|
+
selected={selectedColumn?.Data || ""}
|
|
85
89
|
searchCols={searchCols}
|
|
86
90
|
handleChange={(value) => {
|
|
87
91
|
const col = searchCols.find((c) => c.Data === value);
|
|
@@ -14,15 +14,8 @@ type ColumnLogic =
|
|
|
14
14
|
| {
|
|
15
15
|
id: string;
|
|
16
16
|
mask: string | undefined;
|
|
17
|
-
maskFn: unknown;
|
|
18
|
-
cellFn:
|
|
19
|
-
| ((
|
|
20
|
-
value: string | number | string[],
|
|
21
|
-
) => React.ReactElement<
|
|
22
|
-
unknown,
|
|
23
|
-
string | React.JSXElementConstructor<unknown>
|
|
24
|
-
>)
|
|
25
|
-
| ((value: string | number) => string | number);
|
|
17
|
+
maskFn: (value: unknown, mask?: string) => string | number | string[];
|
|
18
|
+
cellFn: (value: string | number | string[]) => React.ReactNode;
|
|
26
19
|
}[]
|
|
27
20
|
| undefined;
|
|
28
21
|
|
|
@@ -152,15 +152,18 @@ export default function DocumentTable({
|
|
|
152
152
|
|
|
153
153
|
// Depends on searchableCols, must be declared after transformer
|
|
154
154
|
const [searchColumn, setSearchColumn] = useState<Column>(
|
|
155
|
-
() => searchableCols?.[0],
|
|
155
|
+
() => searchableCols?.[0] as Column,
|
|
156
156
|
);
|
|
157
157
|
|
|
158
|
+
// always derive a safe usable column
|
|
159
|
+
const effectiveSearchColumn = searchColumn ?? searchableCols?.[0];
|
|
160
|
+
|
|
158
161
|
const { data: fetchedRows, isPending: rowsPending } = useQuery(
|
|
159
162
|
resource.rowsQueryFn({
|
|
160
163
|
page,
|
|
161
164
|
pageSize: pageSize + 1, // Fetch one extra to check if there's a next page
|
|
162
165
|
filters,
|
|
163
|
-
searchColumn:
|
|
166
|
+
searchColumn: effectiveSearchColumn?.Data || "",
|
|
164
167
|
searchTerm,
|
|
165
168
|
}),
|
|
166
169
|
);
|
|
@@ -281,7 +284,7 @@ export default function DocumentTable({
|
|
|
281
284
|
setSearchTerm={setSearchTerm}
|
|
282
285
|
setColumn={setSearchColumn}
|
|
283
286
|
searchCols={searchableCols || []}
|
|
284
|
-
selectedColumn={
|
|
287
|
+
selectedColumn={effectiveSearchColumn}
|
|
285
288
|
/>
|
|
286
289
|
{filterLogic?.map((filter) => (
|
|
287
290
|
<filter.FilterComponent
|
|
@@ -39,7 +39,13 @@ export const TableDashboard = ({ resources, viewConfigQuery }: TableProps) => {
|
|
|
39
39
|
return (
|
|
40
40
|
<Box sx={{ width: "100%", minHeight: "70vh" }}>
|
|
41
41
|
<TabContext value={value}>
|
|
42
|
-
<Box
|
|
42
|
+
<Box
|
|
43
|
+
sx={{
|
|
44
|
+
borderBottom: 1,
|
|
45
|
+
borderColor: "divider",
|
|
46
|
+
"@media print": { display: "none" },
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
43
49
|
<TabList onChange={handleChange}>
|
|
44
50
|
{resources?.map((resource, i) => (
|
|
45
51
|
<Tab
|
|
@@ -62,6 +68,21 @@ export const TableDashboard = ({ resources, viewConfigQuery }: TableProps) => {
|
|
|
62
68
|
pageSize={pageSize}
|
|
63
69
|
setPageSize={setPageSize}
|
|
64
70
|
/>
|
|
71
|
+
{/* CHILD TABLE */}
|
|
72
|
+
{resource.children?.length ? (
|
|
73
|
+
<Box mt={6}>
|
|
74
|
+
{resource.children.map((child, idx) => (
|
|
75
|
+
<Box key={child.name + String(idx)} mt={4}>
|
|
76
|
+
<Table
|
|
77
|
+
viewConfigQuery={viewConfigQuery}
|
|
78
|
+
resource={child}
|
|
79
|
+
pageSize={pageSize}
|
|
80
|
+
setPageSize={setPageSize}
|
|
81
|
+
/>
|
|
82
|
+
</Box>
|
|
83
|
+
))}
|
|
84
|
+
</Box>
|
|
85
|
+
) : null}
|
|
65
86
|
</TabPanel>
|
|
66
87
|
</Box>
|
|
67
88
|
))}
|