@evenicanpm/portal-table-ui 1.7.0 → 1.8.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 +4 -4
- package/src/features/table/components/dashboard/table/bulk-action-button.tsx +3 -4
- package/src/features/table/components/dashboard/table/details/details.tsx +7 -0
- package/src/features/table/components/dashboard/table/details/templates/invoice-details.tsx +212 -146
- package/src/features/table/components/dashboard/table/details/templates/template-registry.ts +5 -6
- package/src/features/table/components/dashboard/table/table-row/table-row.tsx +0 -1
- package/src/features/table/components/dashboard/table/table.tsx +20 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evenicanpm/portal-table-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@arrows/multimethod": "^2.1.0",
|
|
44
|
-
"@evenicanpm/portal-types-schemas": "^1.
|
|
45
|
-
"@evenicanpm/ui": "^1.
|
|
44
|
+
"@evenicanpm/portal-types-schemas": "^1.8.1",
|
|
45
|
+
"@evenicanpm/ui": "^1.8.1",
|
|
46
46
|
"@mui/lab": "^7.0.1-beta.19",
|
|
47
47
|
"@tanstack/react-query": "^5.90.11",
|
|
48
48
|
"date-fns": "^4.1.0"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "07cfd4496c6a137e1420a6572298998b25b6369d"
|
|
51
51
|
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { Badge, Button, CircularProgress, IconButton } from "@mui/material";
|
|
2
2
|
import { useMutation } from "@tanstack/react-query";
|
|
3
|
-
import type
|
|
4
|
-
import type { UIBulkAction } from "src/features/table/types/schemas";
|
|
3
|
+
import type * as Types from "../../../types";
|
|
5
4
|
|
|
6
5
|
interface BulkActionButtonProps {
|
|
7
|
-
action: UIBulkAction;
|
|
6
|
+
action: Types.Schemas.UIBulkAction;
|
|
8
7
|
resourceName: string;
|
|
9
8
|
selectedIds: (string | number)[];
|
|
10
9
|
clearSelection: () => void;
|
|
11
|
-
selectedRows: Row[];
|
|
10
|
+
selectedRows: Types.Queries.Row[];
|
|
12
11
|
}
|
|
13
12
|
export function BulkActionButton({
|
|
14
13
|
action,
|
|
@@ -34,6 +34,9 @@ interface DetailsProps {
|
|
|
34
34
|
statusTextMap: StatusTextMap;
|
|
35
35
|
template?: string;
|
|
36
36
|
loading?: boolean;
|
|
37
|
+
|
|
38
|
+
actions?: Types.Schemas.UIAction[];
|
|
39
|
+
resourceName?: string;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/**
|
|
@@ -49,6 +52,8 @@ const Details = ({
|
|
|
49
52
|
statusColorMap,
|
|
50
53
|
statusTextMap,
|
|
51
54
|
template,
|
|
55
|
+
actions,
|
|
56
|
+
resourceName,
|
|
52
57
|
loading = false,
|
|
53
58
|
}: DetailsProps) => {
|
|
54
59
|
// Resolve the template component once — undefined when the key is absent or
|
|
@@ -96,6 +101,8 @@ const Details = ({
|
|
|
96
101
|
statusColorMap={statusColorMap}
|
|
97
102
|
statusTextMap={statusTextMap}
|
|
98
103
|
loading={loading}
|
|
104
|
+
actions={actions}
|
|
105
|
+
resourceName={resourceName}
|
|
99
106
|
/>
|
|
100
107
|
) : (
|
|
101
108
|
<Grid container spacing={2}>
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
Box,
|
|
4
|
-
Button,
|
|
5
4
|
Card,
|
|
6
5
|
Divider,
|
|
6
|
+
Paper,
|
|
7
7
|
Skeleton,
|
|
8
|
+
Table,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableCell,
|
|
11
|
+
TableContainer,
|
|
12
|
+
TableHead,
|
|
13
|
+
TableRow,
|
|
8
14
|
Typography,
|
|
15
|
+
useMediaQuery,
|
|
16
|
+
useTheme,
|
|
9
17
|
} from "@mui/material";
|
|
10
18
|
import Grid2 from "@mui/material/Grid2";
|
|
11
19
|
import { StatusPill } from "../../status-pill";
|
|
20
|
+
import { ActionButton } from "../../table-row/action-button";
|
|
12
21
|
import type { DetailsTemplateProps } from "./template-registry";
|
|
13
22
|
|
|
14
23
|
// Formatting utilities
|
|
@@ -24,12 +33,9 @@ const formatCurrency = (amount?: number): string => {
|
|
|
24
33
|
|
|
25
34
|
const formatDate = (dateString?: string): string => {
|
|
26
35
|
if (!dateString) return "—";
|
|
27
|
-
|
|
28
36
|
try {
|
|
29
37
|
const date = new Date(dateString);
|
|
30
|
-
// Check if date is valid
|
|
31
38
|
if (isNaN(date.getTime())) return dateString;
|
|
32
|
-
|
|
33
39
|
return new Intl.DateTimeFormat("en-US", {
|
|
34
40
|
year: "numeric",
|
|
35
41
|
month: "long",
|
|
@@ -41,15 +47,15 @@ const formatDate = (dateString?: string): string => {
|
|
|
41
47
|
};
|
|
42
48
|
|
|
43
49
|
interface InvoiceLine {
|
|
44
|
-
Description?: string;
|
|
45
50
|
ItemId?: string;
|
|
51
|
+
ProductName?: string;
|
|
46
52
|
Quantity?: number;
|
|
47
53
|
Price?: number;
|
|
48
|
-
|
|
54
|
+
NetAmount?: number;
|
|
49
55
|
UnitOfMeasure?: string;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
LineDiscount?: number;
|
|
57
|
+
DiscountAmount: number;
|
|
58
|
+
HasDiscount: boolean;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
interface InvoiceData {
|
|
@@ -67,16 +73,15 @@ interface InvoiceData {
|
|
|
67
73
|
InvoiceDate?: string;
|
|
68
74
|
DueDate?: string;
|
|
69
75
|
Notes?: string;
|
|
76
|
+
Id: string;
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
const isDataEmpty = (data: InvoiceData | null | undefined): boolean => {
|
|
73
80
|
if (!data) return true;
|
|
74
|
-
|
|
75
81
|
const hasNoLineItems =
|
|
76
82
|
!data.SalesInvoiceLine || data.SalesInvoiceLine.length === 0;
|
|
77
83
|
const hasNoBasicInfo = !data.CompanyName && !data.Name && !data.InvoiceDate;
|
|
78
84
|
const hasNoAmounts = !data.Amount && data.Amount !== 0;
|
|
79
|
-
|
|
80
85
|
return hasNoLineItems && hasNoBasicInfo && hasNoAmounts;
|
|
81
86
|
};
|
|
82
87
|
|
|
@@ -85,18 +90,28 @@ export const InvoiceDetailsTemplate = ({
|
|
|
85
90
|
statusColorMap,
|
|
86
91
|
statusTextMap,
|
|
87
92
|
loading = false,
|
|
93
|
+
actions,
|
|
94
|
+
resourceName = "invoice",
|
|
88
95
|
}: DetailsTemplateProps) => {
|
|
96
|
+
const theme = useTheme();
|
|
97
|
+
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
|
98
|
+
|
|
89
99
|
const invoice = data as InvoiceData;
|
|
90
100
|
|
|
101
|
+
// Responsive width
|
|
102
|
+
const containerWidth = isMobile ? "95%" : "75%";
|
|
103
|
+
|
|
91
104
|
if (!loading && (!invoice || isDataEmpty(invoice))) {
|
|
92
105
|
return (
|
|
93
|
-
<Box sx={{ p: 3, width:
|
|
94
|
-
<Card
|
|
106
|
+
<Box sx={{ p: { xs: 2, sm: 3 }, width: containerWidth, mx: "auto" }}>
|
|
107
|
+
<Card
|
|
108
|
+
sx={{ p: { xs: 3, sm: 6 }, borderRadius: 3, textAlign: "center" }}
|
|
109
|
+
>
|
|
95
110
|
<Typography variant="h6" color="text.secondary" gutterBottom>
|
|
96
111
|
No Data Available
|
|
97
112
|
</Typography>
|
|
98
113
|
<Typography variant="body2" color="text.secondary">
|
|
99
|
-
|
|
114
|
+
We couldn't find any details for selected invoice.
|
|
100
115
|
</Typography>
|
|
101
116
|
</Card>
|
|
102
117
|
</Box>
|
|
@@ -108,56 +123,80 @@ export const InvoiceDetailsTemplate = ({
|
|
|
108
123
|
const tax = invoice?.TotalTaxAmount ?? 0;
|
|
109
124
|
const total = invoice?.Amount ?? 0;
|
|
110
125
|
|
|
126
|
+
// Fixed column definitions with specific widths
|
|
111
127
|
const columns = [
|
|
112
|
-
{ label: "#",
|
|
113
|
-
{ label: "Product",
|
|
114
|
-
{ label: "Qty",
|
|
115
|
-
{ label: "Unit",
|
|
116
|
-
{ label: "Unit Price",
|
|
117
|
-
{ label: "
|
|
118
|
-
{ label: "Line Total",
|
|
119
|
-
{ label: "",
|
|
128
|
+
{ label: "#", width: 60 },
|
|
129
|
+
{ label: "Product", width: 200 },
|
|
130
|
+
{ label: "Qty", width: 60 },
|
|
131
|
+
{ label: "Unit", width: 60 },
|
|
132
|
+
{ label: "Unit Price", width: 120 },
|
|
133
|
+
{ label: "Discount", width: 100 },
|
|
134
|
+
{ label: "Line Total", width: 120 },
|
|
135
|
+
{ label: "", width: 40 },
|
|
120
136
|
];
|
|
121
137
|
|
|
138
|
+
// Calculate total width for table container
|
|
139
|
+
const totalTableWidth = columns.reduce((sum, col) => sum + col.width, 0);
|
|
140
|
+
|
|
122
141
|
if (loading) {
|
|
123
142
|
return (
|
|
124
|
-
<Box sx={{ p: 3, width:
|
|
143
|
+
<Box sx={{ p: { xs: 2, sm: 3 }, width: containerWidth, mx: "auto" }}>
|
|
125
144
|
<Box display="flex" justifyContent="space-between" mb={3}>
|
|
126
145
|
<Skeleton variant="rectangular" width={100} height={32} />
|
|
127
146
|
<Skeleton variant="rectangular" width={120} height={40} />
|
|
128
147
|
</Box>
|
|
129
|
-
<Card sx={{ p: 4, borderRadius: 3 }}>
|
|
130
|
-
<Grid2 container spacing={4}>
|
|
148
|
+
<Card sx={{ p: { xs: 2, sm: 4 }, borderRadius: 3 }}>
|
|
149
|
+
<Grid2 container spacing={{ xs: 2, sm: 4 }}>
|
|
131
150
|
{Array.from({ length: 4 }).map((_, i) => (
|
|
132
|
-
<Grid2 size={6} key={i}>
|
|
151
|
+
<Grid2 size={{ xs: 12, sm: 6 }} key={`skeleton-${i}`}>
|
|
133
152
|
<Skeleton width="40%" height={20} />
|
|
134
153
|
<Skeleton width="80%" />
|
|
135
154
|
<Skeleton width="60%" />
|
|
136
155
|
</Grid2>
|
|
137
156
|
))}
|
|
138
157
|
</Grid2>
|
|
139
|
-
<Divider sx={{ my: 3 }} />
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
<Divider sx={{ my: { xs: 2, sm: 3 } }} />
|
|
159
|
+
|
|
160
|
+
{/* Table skeleton with scroll */}
|
|
161
|
+
<TableContainer
|
|
162
|
+
component={Paper}
|
|
163
|
+
sx={{ boxShadow: "none", overflowX: "auto" }}
|
|
164
|
+
>
|
|
165
|
+
<Table sx={{ minWidth: totalTableWidth }}>
|
|
166
|
+
<TableHead>
|
|
167
|
+
<TableRow>
|
|
168
|
+
{columns.map((col) => (
|
|
169
|
+
<TableCell
|
|
170
|
+
key={col.label}
|
|
171
|
+
sx={{ width: col.width, minWidth: col.width }}
|
|
172
|
+
>
|
|
173
|
+
<Skeleton width="80%" />
|
|
174
|
+
</TableCell>
|
|
175
|
+
))}
|
|
176
|
+
</TableRow>
|
|
177
|
+
</TableHead>
|
|
178
|
+
<TableBody>
|
|
179
|
+
{Array.from({ length: 4 }).map((_, idx) => (
|
|
180
|
+
<TableRow key={`skeleton-${idx}`}>
|
|
181
|
+
{columns.map((col) => (
|
|
182
|
+
<TableCell
|
|
183
|
+
key={col.label}
|
|
184
|
+
sx={{ width: col.width, minWidth: col.width }}
|
|
185
|
+
>
|
|
186
|
+
<Skeleton width="80%" />
|
|
187
|
+
</TableCell>
|
|
188
|
+
))}
|
|
189
|
+
</TableRow>
|
|
190
|
+
))}
|
|
191
|
+
</TableBody>
|
|
192
|
+
</Table>
|
|
193
|
+
</TableContainer>
|
|
194
|
+
|
|
156
195
|
<Box mt={3} display="flex" justifyContent="flex-end">
|
|
157
|
-
<Box width={300}>
|
|
196
|
+
<Box width={{ xs: "100%", sm: 300 }}>
|
|
158
197
|
{Array.from({ length: 3 }).map((_, i) => (
|
|
159
198
|
<Box
|
|
160
|
-
key={i}
|
|
199
|
+
key={`skeleton-${i}`}
|
|
161
200
|
display="flex"
|
|
162
201
|
justifyContent="space-between"
|
|
163
202
|
mb={1}
|
|
@@ -174,12 +213,14 @@ export const InvoiceDetailsTemplate = ({
|
|
|
174
213
|
}
|
|
175
214
|
|
|
176
215
|
return (
|
|
177
|
-
<Box sx={{ p: 3, width:
|
|
216
|
+
<Box sx={{ p: { xs: 2, sm: 3 }, width: containerWidth, mx: "auto" }}>
|
|
178
217
|
{/* Header */}
|
|
179
218
|
<Box
|
|
180
219
|
display="flex"
|
|
181
220
|
justifyContent="space-between"
|
|
182
221
|
alignItems="center"
|
|
222
|
+
flexWrap="wrap"
|
|
223
|
+
gap={2}
|
|
183
224
|
mb={3}
|
|
184
225
|
>
|
|
185
226
|
{invoice?.Status !== undefined && (
|
|
@@ -189,16 +230,37 @@ export const InvoiceDetailsTemplate = ({
|
|
|
189
230
|
statusTextMap={statusTextMap}
|
|
190
231
|
/>
|
|
191
232
|
)}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
233
|
+
<Box display="flex" gap={2} alignItems="center">
|
|
234
|
+
{actions?.map((action) => {
|
|
235
|
+
const isPaid = invoice.Status === 3;
|
|
236
|
+
const enabled = !isPaid && action.isEnabled;
|
|
237
|
+
if (!enabled) {
|
|
238
|
+
return (
|
|
239
|
+
<span
|
|
240
|
+
key={action.label}
|
|
241
|
+
style={{
|
|
242
|
+
display: "inline-block",
|
|
243
|
+
width: action.icon ? "34px" : "42px",
|
|
244
|
+
height: "34px",
|
|
245
|
+
}}
|
|
246
|
+
/>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
return (
|
|
250
|
+
<ActionButton
|
|
251
|
+
key={action.label}
|
|
252
|
+
docId={invoice.Id}
|
|
253
|
+
action={action}
|
|
254
|
+
resourceName={resourceName}
|
|
255
|
+
/>
|
|
256
|
+
);
|
|
257
|
+
})}
|
|
258
|
+
</Box>
|
|
197
259
|
</Box>
|
|
198
260
|
|
|
199
261
|
{/* Notes Section */}
|
|
200
262
|
{invoice?.Notes && (
|
|
201
|
-
<Card sx={{ p: 3, borderRadius: 3, mb: 3 }}>
|
|
263
|
+
<Card sx={{ p: { xs: 2, sm: 3 }, borderRadius: 3, mb: 3 }}>
|
|
202
264
|
<Typography variant="subtitle1" color="text.secondary" mb={1}>
|
|
203
265
|
Notes
|
|
204
266
|
</Typography>
|
|
@@ -213,7 +275,7 @@ export const InvoiceDetailsTemplate = ({
|
|
|
213
275
|
)}
|
|
214
276
|
|
|
215
277
|
{/* Main Invoice Card */}
|
|
216
|
-
<Card sx={{ p: 3, borderRadius: 3 }}>
|
|
278
|
+
<Card sx={{ p: { xs: 2, sm: 3 }, borderRadius: 3 }}>
|
|
217
279
|
{/* Details Section */}
|
|
218
280
|
{(invoice?.CompanyName ||
|
|
219
281
|
invoice?.Name ||
|
|
@@ -223,10 +285,9 @@ export const InvoiceDetailsTemplate = ({
|
|
|
223
285
|
<Typography variant="subtitle1" color="text.secondary" mb={2}>
|
|
224
286
|
Details
|
|
225
287
|
</Typography>
|
|
226
|
-
|
|
227
|
-
<Grid2 container spacing={4}>
|
|
288
|
+
<Grid2 container spacing={{ xs: 2, sm: 4 }}>
|
|
228
289
|
{invoice?.CompanyName && (
|
|
229
|
-
<Grid2 size={6}>
|
|
290
|
+
<Grid2 size={{ xs: 12, sm: 6 }}>
|
|
230
291
|
<Typography variant="body2" color="text.secondary">
|
|
231
292
|
From
|
|
232
293
|
</Typography>
|
|
@@ -234,46 +295,45 @@ export const InvoiceDetailsTemplate = ({
|
|
|
234
295
|
{invoice.CompanyName}
|
|
235
296
|
</Typography>
|
|
236
297
|
{invoice.CompanyEmail && (
|
|
237
|
-
<Typography variant="body2">
|
|
298
|
+
<Typography variant="body2" sx={{ wordBreak: "break-all" }}>
|
|
238
299
|
{invoice.CompanyEmail}
|
|
239
300
|
</Typography>
|
|
240
301
|
)}
|
|
241
302
|
{invoice.CompanyAddress && (
|
|
242
|
-
<Typography variant="body2">
|
|
303
|
+
<Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}>
|
|
243
304
|
{invoice.CompanyAddress}
|
|
244
305
|
</Typography>
|
|
245
306
|
)}
|
|
246
307
|
</Grid2>
|
|
247
308
|
)}
|
|
248
|
-
|
|
249
309
|
{invoice?.Name && (
|
|
250
|
-
<Grid2 size={6}>
|
|
310
|
+
<Grid2 size={{ xs: 12, sm: 6 }}>
|
|
251
311
|
<Typography variant="body2" color="text.secondary">
|
|
252
312
|
Bill To
|
|
253
313
|
</Typography>
|
|
254
314
|
<Typography fontWeight={600}>{invoice.Name}</Typography>
|
|
255
315
|
{invoice.Email && (
|
|
256
|
-
<Typography variant="body2"
|
|
316
|
+
<Typography variant="body2" sx={{ wordBreak: "break-all" }}>
|
|
317
|
+
{invoice.Email}
|
|
318
|
+
</Typography>
|
|
257
319
|
)}
|
|
258
320
|
{invoice.BillingAddress && (
|
|
259
|
-
<Typography variant="body2">
|
|
321
|
+
<Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}>
|
|
260
322
|
{invoice.BillingAddress}
|
|
261
323
|
</Typography>
|
|
262
324
|
)}
|
|
263
325
|
</Grid2>
|
|
264
326
|
)}
|
|
265
|
-
|
|
266
327
|
{invoice?.InvoiceDate && (
|
|
267
|
-
<Grid2 size={6}>
|
|
328
|
+
<Grid2 size={{ xs: 12, sm: 6 }}>
|
|
268
329
|
<Typography variant="body2" color="text.secondary">
|
|
269
330
|
Invoice Date
|
|
270
331
|
</Typography>
|
|
271
332
|
<Typography>{formatDate(invoice.InvoiceDate)}</Typography>
|
|
272
333
|
</Grid2>
|
|
273
334
|
)}
|
|
274
|
-
|
|
275
335
|
{invoice?.DueDate && (
|
|
276
|
-
<Grid2 size={6}>
|
|
336
|
+
<Grid2 size={{ xs: 12, sm: 6 }}>
|
|
277
337
|
<Typography variant="body2" color="text.secondary">
|
|
278
338
|
Due Date
|
|
279
339
|
</Typography>
|
|
@@ -281,108 +341,114 @@ export const InvoiceDetailsTemplate = ({
|
|
|
281
341
|
</Grid2>
|
|
282
342
|
)}
|
|
283
343
|
</Grid2>
|
|
284
|
-
|
|
285
|
-
<Divider sx={{ my: 3 }} />
|
|
344
|
+
<Divider sx={{ my: { xs: 2, sm: 3 } }} />
|
|
286
345
|
</>
|
|
287
346
|
)}
|
|
288
347
|
|
|
289
348
|
{/* Line Items Section */}
|
|
290
349
|
{items.length > 0 ? (
|
|
291
350
|
<>
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
</
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
351
|
+
<TableContainer>
|
|
352
|
+
<Table sx={{ minWidth: totalTableWidth }}>
|
|
353
|
+
<TableHead>
|
|
354
|
+
<TableRow>
|
|
355
|
+
{columns.map((col) => (
|
|
356
|
+
<TableCell
|
|
357
|
+
key={col.label}
|
|
358
|
+
sx={{
|
|
359
|
+
width: col.width,
|
|
360
|
+
minWidth: col.width,
|
|
361
|
+
fontWeight: 600,
|
|
362
|
+
backgroundColor: theme.palette.background.paper,
|
|
363
|
+
}}
|
|
364
|
+
>
|
|
365
|
+
<Typography variant="body2" fontWeight={600}>
|
|
366
|
+
{col.label}
|
|
367
|
+
</Typography>
|
|
368
|
+
</TableCell>
|
|
369
|
+
))}
|
|
370
|
+
</TableRow>
|
|
371
|
+
</TableHead>
|
|
372
|
+
<TableBody>
|
|
373
|
+
{items.map((item, idx) => {
|
|
374
|
+
const productName = item.ProductName || "-";
|
|
375
|
+
const unitPrice = item.Price ?? 0;
|
|
376
|
+
const quantity = item.Quantity ?? 0;
|
|
377
|
+
const lineTotal = item.NetAmount ?? unitPrice * quantity;
|
|
378
|
+
|
|
379
|
+
return (
|
|
380
|
+
<TableRow key={`${item.ItemId}-${idx}`}>
|
|
381
|
+
<TableCell sx={{ width: 60, minWidth: 60 }}>
|
|
382
|
+
<Typography variant="body2">
|
|
383
|
+
{item?.ItemId || idx + 1}
|
|
384
|
+
</Typography>
|
|
385
|
+
</TableCell>
|
|
386
|
+
<TableCell sx={{ width: 200, minWidth: 200 }}>
|
|
387
|
+
<Typography variant="body2">{productName}</Typography>
|
|
388
|
+
</TableCell>
|
|
389
|
+
<TableCell sx={{ width: 60, minWidth: 60 }}>
|
|
390
|
+
<Typography variant="body2">{quantity}</Typography>
|
|
391
|
+
</TableCell>
|
|
392
|
+
<TableCell sx={{ width: 60, minWidth: 60 }}>
|
|
393
|
+
<Typography variant="body2">
|
|
394
|
+
{item?.UnitOfMeasure || "—"}
|
|
395
|
+
</Typography>
|
|
396
|
+
</TableCell>
|
|
397
|
+
<TableCell sx={{ width: 120, minWidth: 120 }}>
|
|
398
|
+
<Typography variant="body2">
|
|
399
|
+
{formatCurrency(unitPrice)}
|
|
400
|
+
</Typography>
|
|
401
|
+
</TableCell>
|
|
402
|
+
<TableCell sx={{ width: 100, minWidth: 100 }}>
|
|
403
|
+
<Typography
|
|
404
|
+
variant="body2"
|
|
405
|
+
color={
|
|
406
|
+
item.HasDiscount ? "error.main" : "text.secondary"
|
|
407
|
+
}
|
|
408
|
+
>
|
|
409
|
+
{item.HasDiscount
|
|
410
|
+
? `-${formatCurrency(item.DiscountAmount)}`
|
|
411
|
+
: formatCurrency(0)}
|
|
412
|
+
</Typography>
|
|
413
|
+
</TableCell>
|
|
414
|
+
<TableCell sx={{ width: 100, minWidth: 100 }}>
|
|
415
|
+
<Typography variant="body2" fontWeight={500}>
|
|
416
|
+
{formatCurrency(lineTotal)}
|
|
417
|
+
</Typography>
|
|
418
|
+
</TableCell>
|
|
419
|
+
<TableCell
|
|
420
|
+
sx={{ width: 40, minWidth: 40 }}
|
|
421
|
+
align="center"
|
|
422
|
+
>
|
|
423
|
+
{item.HasDiscount && (
|
|
424
|
+
<Typography color="error.main" fontWeight={700}>
|
|
425
|
+
*
|
|
426
|
+
</Typography>
|
|
427
|
+
)}
|
|
428
|
+
</TableCell>
|
|
429
|
+
</TableRow>
|
|
430
|
+
);
|
|
431
|
+
})}
|
|
432
|
+
</TableBody>
|
|
433
|
+
</Table>
|
|
434
|
+
</TableContainer>
|
|
366
435
|
|
|
367
436
|
{/* Totals */}
|
|
368
437
|
<Box mt={3} display="flex" justifyContent="flex-end">
|
|
369
|
-
<Box width={300}>
|
|
438
|
+
<Box width={{ xs: "100%", sm: 300 }}>
|
|
370
439
|
<Box display="flex" justifyContent="space-between" mb={1}>
|
|
371
440
|
<Typography variant="body2">Subtotal</Typography>
|
|
372
441
|
<Typography variant="body2" fontWeight={500}>
|
|
373
442
|
{formatCurrency(subtotal)}
|
|
374
443
|
</Typography>
|
|
375
444
|
</Box>
|
|
376
|
-
|
|
377
445
|
<Box display="flex" justifyContent="space-between" mb={1}>
|
|
378
446
|
<Typography variant="body2">Tax</Typography>
|
|
379
447
|
<Typography variant="body2" fontWeight={500}>
|
|
380
448
|
{formatCurrency(tax)}
|
|
381
449
|
</Typography>
|
|
382
450
|
</Box>
|
|
383
|
-
|
|
384
451
|
<Divider sx={{ my: 1.5 }} />
|
|
385
|
-
|
|
386
452
|
<Box display="flex" justifyContent="space-between" mt={1}>
|
|
387
453
|
<Typography variant="body1" fontWeight={700}>
|
|
388
454
|
Total
|
package/src/features/table/components/dashboard/table/details/templates/template-registry.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
StatusColorMap,
|
|
3
|
-
StatusTextMap,
|
|
4
|
-
} from "src/features/table/types/schemas";
|
|
1
|
+
import type * as Types from "../../../../../types";
|
|
5
2
|
import { InvoiceDetailsTemplate } from "./invoice-details";
|
|
6
3
|
|
|
7
4
|
/**
|
|
@@ -11,9 +8,11 @@ import { InvoiceDetailsTemplate } from "./invoice-details";
|
|
|
11
8
|
*/
|
|
12
9
|
export interface DetailsTemplateProps {
|
|
13
10
|
data: unknown;
|
|
14
|
-
statusColorMap: StatusColorMap;
|
|
15
|
-
statusTextMap: StatusTextMap;
|
|
11
|
+
statusColorMap: Types.Schemas.StatusColorMap;
|
|
12
|
+
statusTextMap: Types.Schemas.StatusTextMap;
|
|
16
13
|
loading?: boolean;
|
|
14
|
+
actions?: Types.Schemas.UIAction[];
|
|
15
|
+
resourceName?: string;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
/**
|
|
@@ -203,6 +203,24 @@ export default function DocumentTable({
|
|
|
203
203
|
},
|
|
204
204
|
);
|
|
205
205
|
|
|
206
|
+
const resolvedActions = resolvers.resolveActions(
|
|
207
|
+
config?.Actions,
|
|
208
|
+
actionRegistry,
|
|
209
|
+
);
|
|
210
|
+
const resolvedBulkActions = resolvers.resolveBulkActions(
|
|
211
|
+
config?.BulkActions,
|
|
212
|
+
bulkRegistry,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const hasActions =
|
|
216
|
+
configPending ||
|
|
217
|
+
(resolvedActions.length > 0 &&
|
|
218
|
+
rows?.data?.some((row) =>
|
|
219
|
+
resolvedActions.some((a) => !a.isEnabled || a.isEnabled(row)),
|
|
220
|
+
)) ||
|
|
221
|
+
false;
|
|
222
|
+
const hasBulkActions = resolvedBulkActions.length > 0;
|
|
223
|
+
|
|
206
224
|
// --- Render --- //
|
|
207
225
|
if (selectedDocument) {
|
|
208
226
|
const rowSnapshot =
|
|
@@ -224,28 +242,12 @@ export default function DocumentTable({
|
|
|
224
242
|
detailsRenderMap={detailsRenderMap}
|
|
225
243
|
template={hasTemplateSupport ? resource.detailsTemplate : undefined}
|
|
226
244
|
loading={hasTemplateSupport ? detailsPending : false}
|
|
245
|
+
actions={resolvedActions}
|
|
246
|
+
resourceName={resource.name}
|
|
227
247
|
/>
|
|
228
248
|
);
|
|
229
249
|
}
|
|
230
250
|
|
|
231
|
-
const resolvedActions = resolvers.resolveActions(
|
|
232
|
-
config?.Actions,
|
|
233
|
-
actionRegistry,
|
|
234
|
-
);
|
|
235
|
-
const resolvedBulkActions = resolvers.resolveBulkActions(
|
|
236
|
-
config?.BulkActions,
|
|
237
|
-
bulkRegistry,
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
const hasActions =
|
|
241
|
-
configPending ||
|
|
242
|
-
(resolvedActions.length > 0 &&
|
|
243
|
-
rows?.data?.some((row) =>
|
|
244
|
-
resolvedActions.some((a) => !a.isEnabled || a.isEnabled(row)),
|
|
245
|
-
)) ||
|
|
246
|
-
false;
|
|
247
|
-
const hasBulkActions = resolvedBulkActions.length > 0;
|
|
248
|
-
|
|
249
251
|
const baseCols = (visibleCols?.length ?? 0) + 1;
|
|
250
252
|
const colSpan = hasActions ? baseCols + 1 : baseCols;
|
|
251
253
|
const noResults = !rowsPending && !configPending && rows?.data?.length === 0;
|