@evenicanpm/portal-table-ui 1.5.0 → 1.7.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.
Files changed (67) hide show
  1. package/package.json +6 -5
  2. package/src/auth/auth-context.tsx +92 -0
  3. package/src/features/table/components/dashboard/table/bulk-action-button.tsx +77 -0
  4. package/src/features/table/components/dashboard/table/data-mask-map.ts +26 -0
  5. package/src/features/table/components/dashboard/table/details/details.tsx +185 -0
  6. package/src/features/table/components/dashboard/table/details/label.tsx +26 -0
  7. package/src/features/table/components/dashboard/table/details/rows/downloads.tsx +25 -0
  8. package/src/features/table/components/dashboard/table/details/rows/primitive.tsx +25 -0
  9. package/src/features/table/components/dashboard/table/details/rows/table.tsx +73 -0
  10. package/{dist/features/table/components/dashboard/table/details/styles.d.ts → src/features/table/components/dashboard/table/details/styles.ts} +10 -2
  11. package/src/features/table/components/dashboard/table/details/templates/invoice-details.tsx +407 -0
  12. package/src/features/table/components/dashboard/table/details/templates/template-registry.ts +36 -0
  13. package/src/features/table/components/dashboard/table/filter-render-map.tsx +10 -0
  14. package/src/features/table/components/dashboard/table/filters/date-filter.tsx +31 -0
  15. package/src/features/table/components/dashboard/table/filters/status-filter.tsx +36 -0
  16. package/src/features/table/components/dashboard/table/no-results.tsx +26 -0
  17. package/src/features/table/components/dashboard/table/status-pill.tsx +18 -0
  18. package/src/features/table/components/dashboard/table/table-header.tsx +98 -0
  19. package/src/features/table/components/dashboard/table/table-input.tsx +94 -0
  20. package/src/features/table/components/dashboard/table/table-row/action-button.tsx +47 -0
  21. package/src/features/table/components/dashboard/table/table-row/table-row.tsx +144 -0
  22. package/src/features/table/components/dashboard/table/table-row-skeleton.tsx +35 -0
  23. package/src/features/table/components/dashboard/table/table.tsx +361 -0
  24. package/src/features/table/components/dashboard/table/use-select-rows.ts +53 -0
  25. package/src/features/table/components/dashboard/table-dashboard.tsx +71 -0
  26. package/{dist/features/table/index.d.ts → src/features/table/index.ts} +7 -1
  27. package/src/features/table/logic/resolvers.ts +111 -0
  28. package/src/features/table/logic/transformers.ts +56 -0
  29. package/src/features/table/logic/types.ts +8 -0
  30. package/{dist/features/table/types/handlers.d.ts → src/features/table/types/handlers.ts} +30 -12
  31. package/{dist/features/table/types/index.d.ts → src/features/table/types/index.ts} +1 -0
  32. package/{dist/features/table/types/queries.d.ts → src/features/table/types/queries.ts} +10 -3
  33. package/src/features/table/types/schemas.ts +89 -0
  34. package/{dist/index.d.ts → src/index.ts} +2 -1
  35. package/src/portal.tsx +68 -0
  36. package/dist/auth/auth-context.d.ts +0 -36
  37. package/dist/features/settings/index.d.ts +0 -2
  38. package/dist/features/table/components/dashboard/table/actions.d.ts +0 -14
  39. package/dist/features/table/components/dashboard/table/data-mask-map.d.ts +0 -2
  40. package/dist/features/table/components/dashboard/table/details/details.d.ts +0 -27
  41. package/dist/features/table/components/dashboard/table/details/label.d.ts +0 -5
  42. package/dist/features/table/components/dashboard/table/details/rows/downloads.d.ts +0 -6
  43. package/dist/features/table/components/dashboard/table/details/rows/primitive.d.ts +0 -6
  44. package/dist/features/table/components/dashboard/table/details/rows/table.d.ts +0 -17
  45. package/dist/features/table/components/dashboard/table/filter-render-map.d.ts +0 -2
  46. package/dist/features/table/components/dashboard/table/filters/date-filter.d.ts +0 -2
  47. package/dist/features/table/components/dashboard/table/filters/status-filter.d.ts +0 -2
  48. package/dist/features/table/components/dashboard/table/no-results.d.ts +0 -2
  49. package/dist/features/table/components/dashboard/table/status-pill.d.ts +0 -8
  50. package/dist/features/table/components/dashboard/table/table-header.d.ts +0 -16
  51. package/dist/features/table/components/dashboard/table/table-input.d.ts +0 -11
  52. package/dist/features/table/components/dashboard/table/table-row/action-button.d.ts +0 -8
  53. package/dist/features/table/components/dashboard/table/table-row/table-row.d.ts +0 -32
  54. package/dist/features/table/components/dashboard/table/table-row-skeleton.d.ts +0 -6
  55. package/dist/features/table/components/dashboard/table/table.d.ts +0 -18
  56. package/dist/features/table/components/dashboard/table-dashboard.d.ts +0 -22
  57. package/dist/features/table/logic/resolvers.d.ts +0 -21
  58. package/dist/features/table/logic/transformers.d.ts +0 -35
  59. package/dist/features/table/logic/types.d.ts +0 -8
  60. package/dist/features/table/types/schemas.d.ts +0 -31
  61. package/dist/portal.d.ts +0 -38
  62. package/dist/tsconfig.tsbuildinfo +0 -1
  63. /package/{dist/features/table/components/dashboard/index.d.ts → src/features/table/components/dashboard/index.tsx} +0 -0
  64. /package/{dist/features/table/components/dashboard/table/details/index.d.ts → src/features/table/components/dashboard/table/details/index.ts} +0 -0
  65. /package/{dist/features/table/components/dashboard/table/details/rows/index.d.ts → src/features/table/components/dashboard/table/details/rows/index.ts} +0 -0
  66. /package/{dist/features/table/components/dashboard/table/index.d.ts → src/features/table/components/dashboard/table/index.ts} +0 -0
  67. /package/{dist/features/table/logic/index.d.ts → src/features/table/logic/index.ts} +0 -0
@@ -0,0 +1,407 @@
1
+ "use client";
2
+ import {
3
+ Box,
4
+ Button,
5
+ Card,
6
+ Divider,
7
+ Skeleton,
8
+ Typography,
9
+ } from "@mui/material";
10
+ import Grid2 from "@mui/material/Grid2";
11
+ import { StatusPill } from "../../status-pill";
12
+ import type { DetailsTemplateProps } from "./template-registry";
13
+
14
+ // Formatting utilities
15
+ const formatCurrency = (amount?: number): string => {
16
+ if (amount === undefined || amount === null) return "$0.00";
17
+ return new Intl.NumberFormat("en-US", {
18
+ style: "currency",
19
+ currency: "USD",
20
+ minimumFractionDigits: 2,
21
+ maximumFractionDigits: 2,
22
+ }).format(amount);
23
+ };
24
+
25
+ const formatDate = (dateString?: string): string => {
26
+ if (!dateString) return "—";
27
+
28
+ try {
29
+ const date = new Date(dateString);
30
+ // Check if date is valid
31
+ if (isNaN(date.getTime())) return dateString;
32
+
33
+ return new Intl.DateTimeFormat("en-US", {
34
+ year: "numeric",
35
+ month: "long",
36
+ day: "numeric",
37
+ }).format(date);
38
+ } catch {
39
+ return dateString;
40
+ }
41
+ };
42
+
43
+ interface InvoiceLine {
44
+ Description?: string;
45
+ ItemId?: string;
46
+ Quantity?: number;
47
+ Price?: number;
48
+ LineAmount?: number;
49
+ UnitOfMeasure?: string;
50
+ FullDescription?: string;
51
+ Savings?: number;
52
+ IsOnSale?: boolean;
53
+ }
54
+
55
+ interface InvoiceData {
56
+ Status?: string | number;
57
+ SalesInvoiceLine?: InvoiceLine[];
58
+ InvoiceSubtotal?: number;
59
+ TotalTaxAmount?: number;
60
+ Amount?: number;
61
+ CompanyName?: string;
62
+ CompanyEmail?: string;
63
+ CompanyAddress?: string;
64
+ Name?: string;
65
+ Email?: string;
66
+ BillingAddress?: string;
67
+ InvoiceDate?: string;
68
+ DueDate?: string;
69
+ Notes?: string;
70
+ }
71
+
72
+ const isDataEmpty = (data: InvoiceData | null | undefined): boolean => {
73
+ if (!data) return true;
74
+
75
+ const hasNoLineItems =
76
+ !data.SalesInvoiceLine || data.SalesInvoiceLine.length === 0;
77
+ const hasNoBasicInfo = !data.CompanyName && !data.Name && !data.InvoiceDate;
78
+ const hasNoAmounts = !data.Amount && data.Amount !== 0;
79
+
80
+ return hasNoLineItems && hasNoBasicInfo && hasNoAmounts;
81
+ };
82
+
83
+ export const InvoiceDetailsTemplate = ({
84
+ data,
85
+ statusColorMap,
86
+ statusTextMap,
87
+ loading = false,
88
+ }: DetailsTemplateProps) => {
89
+ const invoice = data as InvoiceData;
90
+
91
+ if (!loading && (!invoice || isDataEmpty(invoice))) {
92
+ return (
93
+ <Box sx={{ p: 3, width: "75%", mx: "auto" }}>
94
+ <Card sx={{ p: 6, borderRadius: 3, textAlign: "center" }}>
95
+ <Typography variant="h6" color="text.secondary" gutterBottom>
96
+ No Data Available
97
+ </Typography>
98
+ <Typography variant="body2" color="text.secondary">
99
+ This invoice template has no data to display.
100
+ </Typography>
101
+ </Card>
102
+ </Box>
103
+ );
104
+ }
105
+
106
+ const items = invoice?.SalesInvoiceLine ?? [];
107
+ const subtotal = invoice?.InvoiceSubtotal ?? 0;
108
+ const tax = invoice?.TotalTaxAmount ?? 0;
109
+ const total = invoice?.Amount ?? 0;
110
+
111
+ const columns = [
112
+ { label: "#", size: 1 },
113
+ { label: "Product", size: 3 },
114
+ { label: "Qty", size: 1 },
115
+ { label: "Unit", size: 2 },
116
+ { label: "Unit Price", size: 1.5 },
117
+ { label: "Savings", size: 1.5 },
118
+ { label: "Line Total", size: 1.5 },
119
+ { label: "", size: 0.5 },
120
+ ];
121
+
122
+ if (loading) {
123
+ return (
124
+ <Box sx={{ p: 3, width: "75%", mx: "auto" }}>
125
+ <Box display="flex" justifyContent="space-between" mb={3}>
126
+ <Skeleton variant="rectangular" width={100} height={32} />
127
+ <Skeleton variant="rectangular" width={120} height={40} />
128
+ </Box>
129
+ <Card sx={{ p: 4, borderRadius: 3 }}>
130
+ <Grid2 container spacing={4}>
131
+ {Array.from({ length: 4 }).map((_, i) => (
132
+ <Grid2 size={6} key={i}>
133
+ <Skeleton width="40%" height={20} />
134
+ <Skeleton width="80%" />
135
+ <Skeleton width="60%" />
136
+ </Grid2>
137
+ ))}
138
+ </Grid2>
139
+ <Divider sx={{ my: 3 }} />
140
+ <Grid2 container sx={{ mb: 2 }}>
141
+ {columns.map((col, i) => (
142
+ <Grid2 key={i} size={col.size}>
143
+ <Skeleton width="60%" />
144
+ </Grid2>
145
+ ))}
146
+ </Grid2>
147
+ {Array.from({ length: 4 }).map((_, idx) => (
148
+ <Grid2 container key={idx} sx={{ py: 1.5 }}>
149
+ {columns.map((col, i) => (
150
+ <Grid2 key={i} size={col.size}>
151
+ <Skeleton width="80%" />
152
+ </Grid2>
153
+ ))}
154
+ </Grid2>
155
+ ))}
156
+ <Box mt={3} display="flex" justifyContent="flex-end">
157
+ <Box width={300}>
158
+ {Array.from({ length: 3 }).map((_, i) => (
159
+ <Box
160
+ key={i}
161
+ display="flex"
162
+ justifyContent="space-between"
163
+ mb={1}
164
+ >
165
+ <Skeleton width="40%" />
166
+ <Skeleton width="30%" />
167
+ </Box>
168
+ ))}
169
+ </Box>
170
+ </Box>
171
+ </Card>
172
+ </Box>
173
+ );
174
+ }
175
+
176
+ return (
177
+ <Box sx={{ p: 3, width: "75%", mx: "auto" }}>
178
+ {/* Header */}
179
+ <Box
180
+ display="flex"
181
+ justifyContent="space-between"
182
+ alignItems="center"
183
+ mb={3}
184
+ >
185
+ {invoice?.Status !== undefined && (
186
+ <StatusPill
187
+ status={invoice.Status}
188
+ statusColorMap={statusColorMap}
189
+ statusTextMap={statusTextMap}
190
+ />
191
+ )}
192
+ {invoice?.Status !== 3 && invoice?.Status !== undefined && (
193
+ <Button variant="contained" color="warning">
194
+ Pay Now
195
+ </Button>
196
+ )}
197
+ </Box>
198
+
199
+ {/* Notes Section */}
200
+ {invoice?.Notes && (
201
+ <Card sx={{ p: 3, borderRadius: 3, mb: 3 }}>
202
+ <Typography variant="subtitle1" color="text.secondary" mb={1}>
203
+ Notes
204
+ </Typography>
205
+ <Typography
206
+ variant="body2"
207
+ color="text.secondary"
208
+ whiteSpace="pre-line"
209
+ >
210
+ {invoice.Notes}
211
+ </Typography>
212
+ </Card>
213
+ )}
214
+
215
+ {/* Main Invoice Card */}
216
+ <Card sx={{ p: 3, borderRadius: 3 }}>
217
+ {/* Details Section */}
218
+ {(invoice?.CompanyName ||
219
+ invoice?.Name ||
220
+ invoice?.InvoiceDate ||
221
+ invoice?.DueDate) && (
222
+ <>
223
+ <Typography variant="subtitle1" color="text.secondary" mb={2}>
224
+ Details
225
+ </Typography>
226
+
227
+ <Grid2 container spacing={4}>
228
+ {invoice?.CompanyName && (
229
+ <Grid2 size={6}>
230
+ <Typography variant="body2" color="text.secondary">
231
+ From
232
+ </Typography>
233
+ <Typography fontWeight={600}>
234
+ {invoice.CompanyName}
235
+ </Typography>
236
+ {invoice.CompanyEmail && (
237
+ <Typography variant="body2">
238
+ {invoice.CompanyEmail}
239
+ </Typography>
240
+ )}
241
+ {invoice.CompanyAddress && (
242
+ <Typography variant="body2">
243
+ {invoice.CompanyAddress}
244
+ </Typography>
245
+ )}
246
+ </Grid2>
247
+ )}
248
+
249
+ {invoice?.Name && (
250
+ <Grid2 size={6}>
251
+ <Typography variant="body2" color="text.secondary">
252
+ Bill To
253
+ </Typography>
254
+ <Typography fontWeight={600}>{invoice.Name}</Typography>
255
+ {invoice.Email && (
256
+ <Typography variant="body2">{invoice.Email}</Typography>
257
+ )}
258
+ {invoice.BillingAddress && (
259
+ <Typography variant="body2">
260
+ {invoice.BillingAddress}
261
+ </Typography>
262
+ )}
263
+ </Grid2>
264
+ )}
265
+
266
+ {invoice?.InvoiceDate && (
267
+ <Grid2 size={6}>
268
+ <Typography variant="body2" color="text.secondary">
269
+ Invoice Date
270
+ </Typography>
271
+ <Typography>{formatDate(invoice.InvoiceDate)}</Typography>
272
+ </Grid2>
273
+ )}
274
+
275
+ {invoice?.DueDate && (
276
+ <Grid2 size={6}>
277
+ <Typography variant="body2" color="text.secondary">
278
+ Due Date
279
+ </Typography>
280
+ <Typography>{formatDate(invoice.DueDate)}</Typography>
281
+ </Grid2>
282
+ )}
283
+ </Grid2>
284
+
285
+ <Divider sx={{ my: 3 }} />
286
+ </>
287
+ )}
288
+
289
+ {/* Line Items Section */}
290
+ {items.length > 0 ? (
291
+ <>
292
+ {/* Table Header */}
293
+ <Grid2 container sx={{ mb: 1 }}>
294
+ {columns.map((col, i) => (
295
+ <Grid2 key={i} size={col.size}>
296
+ <Typography variant="body2" fontWeight={600}>
297
+ {col.label}
298
+ </Typography>
299
+ </Grid2>
300
+ ))}
301
+ </Grid2>
302
+
303
+ {/* Line Items */}
304
+ {items.map((item, idx) => (
305
+ <Grid2
306
+ container
307
+ key={idx}
308
+ sx={{ py: 1.5, borderBottom: "1px solid #eee" }}
309
+ >
310
+ <Grid2 size={1}>
311
+ <Typography variant="body2">{item?.ItemId || "—"}</Typography>
312
+ </Grid2>
313
+
314
+ <Grid2 size={3}>
315
+ <Typography variant="body2">
316
+ {item?.Description || "—"}
317
+ </Typography>
318
+ </Grid2>
319
+
320
+ <Grid2 size={1}>
321
+ <Typography variant="body2">
322
+ {item?.Quantity ?? "—"}
323
+ </Typography>
324
+ </Grid2>
325
+
326
+ <Grid2 size={1.5}>
327
+ <Typography variant="body2">
328
+ {item?.UnitOfMeasure || "—"}
329
+ </Typography>
330
+ </Grid2>
331
+
332
+ <Grid2 size={2}>
333
+ <Typography variant="body2">
334
+ {formatCurrency(item?.Price)}
335
+ </Typography>
336
+ </Grid2>
337
+
338
+ <Grid2 size={1.5}>
339
+ <Typography variant="body2">
340
+ {item?.Savings
341
+ ? formatCurrency(item.Savings)
342
+ : formatCurrency(0)}
343
+ </Typography>
344
+ </Grid2>
345
+
346
+ <Grid2 size={1.5}>
347
+ <Typography variant="body2">
348
+ {formatCurrency(item?.LineAmount)}
349
+ </Typography>
350
+ </Grid2>
351
+
352
+ <Grid2
353
+ size={0.5}
354
+ display="flex"
355
+ alignItems="center"
356
+ justifyContent="center"
357
+ >
358
+ {item?.IsOnSale && (
359
+ <Typography color="error.main" fontWeight={700}>
360
+ *
361
+ </Typography>
362
+ )}
363
+ </Grid2>
364
+ </Grid2>
365
+ ))}
366
+
367
+ {/* Totals */}
368
+ <Box mt={3} display="flex" justifyContent="flex-end">
369
+ <Box width={300}>
370
+ <Box display="flex" justifyContent="space-between" mb={1}>
371
+ <Typography variant="body2">Subtotal</Typography>
372
+ <Typography variant="body2" fontWeight={500}>
373
+ {formatCurrency(subtotal)}
374
+ </Typography>
375
+ </Box>
376
+
377
+ <Box display="flex" justifyContent="space-between" mb={1}>
378
+ <Typography variant="body2">Tax</Typography>
379
+ <Typography variant="body2" fontWeight={500}>
380
+ {formatCurrency(tax)}
381
+ </Typography>
382
+ </Box>
383
+
384
+ <Divider sx={{ my: 1.5 }} />
385
+
386
+ <Box display="flex" justifyContent="space-between" mt={1}>
387
+ <Typography variant="body1" fontWeight={700}>
388
+ Total
389
+ </Typography>
390
+ <Typography variant="body1" fontWeight={700}>
391
+ {formatCurrency(total)}
392
+ </Typography>
393
+ </Box>
394
+ </Box>
395
+ </Box>
396
+ </>
397
+ ) : (
398
+ <Box sx={{ textAlign: "center", py: 4 }}>
399
+ <Typography variant="body2" color="text.secondary">
400
+ No line items to display
401
+ </Typography>
402
+ </Box>
403
+ )}
404
+ </Card>
405
+ </Box>
406
+ );
407
+ };
@@ -0,0 +1,36 @@
1
+ import type {
2
+ StatusColorMap,
3
+ StatusTextMap,
4
+ } from "src/features/table/types/schemas";
5
+ import { InvoiceDetailsTemplate } from "./invoice-details";
6
+
7
+ /**
8
+ * Shared props that every details template must accept.
9
+ * Add optional fields here as the surface area grows; templates
10
+ * that don't need a field can simply ignore it.
11
+ */
12
+ export interface DetailsTemplateProps {
13
+ data: unknown;
14
+ statusColorMap: StatusColorMap;
15
+ statusTextMap: StatusTextMap;
16
+ loading?: boolean;
17
+ }
18
+
19
+ /**
20
+ * Registry of all available detail templates.
21
+ *
22
+ * To add a new template:
23
+ * 1. Create the component file, implementing DetailsTemplateProps.
24
+ * 2. Import it here.
25
+ * 3. Add a key -> component entry below.
26
+ *
27
+ * The key is what consumers pass as `detailsTemplate` on the resource definition.
28
+ */
29
+ export const detailsTemplateRegistry: Record<
30
+ string,
31
+ React.ComponentType<DetailsTemplateProps>
32
+ > = {
33
+ invoice: InvoiceDetailsTemplate,
34
+ };
35
+
36
+ export type DetailsTemplateKey = keyof typeof detailsTemplateRegistry;
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import type * as Table from "../../../types/";
3
+ import { DateFilter } from "./filters/date-filter";
4
+ import { StatusFilter } from "./filters/status-filter";
5
+
6
+ export const filterRenderMapFallback: Table.Handlers.FilterRenderMap = {
7
+ select: (props: Table.Handlers.FilterProps) => <StatusFilter {...props} />,
8
+ date: (props: Table.Handlers.FilterProps) => <DateFilter {...props} />,
9
+ datetime: (props: Table.Handlers.FilterProps) => <DateFilter {...props} />,
10
+ };
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import MenuItem from "@mui/material/MenuItem";
3
+ import Select, { type SelectChangeEvent } from "@mui/material/Select";
4
+ import type * as Table from "../../../../types";
5
+
6
+ export const DateFilter = (props: Table.Handlers.FilterProps) => {
7
+ const { value, filterName, filterOptions, setValue } = props;
8
+ return (
9
+ <Select
10
+ labelId="demo-simple-select-label"
11
+ id="demo-simple-select"
12
+ value={value?.[filterName] || "all"}
13
+ label={filterName}
14
+ sx={{
15
+ minHeight: "100%",
16
+ border: "none",
17
+ backgroundColor: "background.paper",
18
+ }}
19
+ onChange={(e: SelectChangeEvent) => {
20
+ setValue((prev) => ({ ...prev, [filterName]: e.target.value }));
21
+ }}
22
+ >
23
+ <MenuItem value={"all"}>All Months</MenuItem>
24
+ {filterOptions?.map((value) => (
25
+ <MenuItem key={value?.value} value={value.value}>
26
+ {value.label}
27
+ </MenuItem>
28
+ ))}
29
+ </Select>
30
+ );
31
+ };
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import MenuItem from "@mui/material/MenuItem";
3
+ import Select from "@mui/material/Select";
4
+ import type * as Table from "../../../../types/";
5
+
6
+ export const StatusFilter = (props: Table.Handlers.FilterProps) => {
7
+ const { value, filterName, setValue, resetPagination, filterOptions } = props;
8
+
9
+ const selectedValue = value?.[filterName] ?? "all";
10
+
11
+ return (
12
+ <Select
13
+ value={selectedValue}
14
+ sx={{
15
+ minHight: "100%",
16
+ border: "none",
17
+ backgroundColor: "background.paper",
18
+ }}
19
+ onChange={(e) => {
20
+ resetPagination();
21
+ setValue((prev) => ({
22
+ ...prev,
23
+ [filterName]: e.target.value,
24
+ }));
25
+ }}
26
+ >
27
+ <MenuItem value="all">All Statuses</MenuItem>
28
+
29
+ {filterOptions.map((status) => (
30
+ <MenuItem key={status.label} value={String(status.value)}>
31
+ {status.label}
32
+ </MenuItem>
33
+ ))}
34
+ </Select>
35
+ );
36
+ };
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import ArticleOutlinedIcon from "@mui/icons-material/ArticleOutlined";
3
+ import { Typography } from "@mui/material";
4
+ import type * as Table from "../../../types/";
5
+
6
+ export const NoResultsComponent: Table.Handlers.NoResultsComponent = ({
7
+ label,
8
+ }: {
9
+ label: string;
10
+ }) => {
11
+ return (
12
+ <Typography
13
+ variant="body2"
14
+ color="text.secondary"
15
+ sx={{
16
+ display: "flex",
17
+ alignItems: "center",
18
+ justifyContent: "center",
19
+ gap: 1,
20
+ }}
21
+ >
22
+ <ArticleOutlinedIcon fontSize="small" />
23
+ No {label.toLowerCase()} found matching your criteria.
24
+ </Typography>
25
+ );
26
+ };
@@ -0,0 +1,18 @@
1
+ import { Chip } from "@mui/material";
2
+ import type { StatusColorMap, StatusTextMap } from "../../../types/schemas";
3
+
4
+ interface Props {
5
+ status: number | string | string[];
6
+ statusTextMap: StatusTextMap;
7
+ statusColorMap: StatusColorMap;
8
+ }
9
+
10
+ const StatusPill = (props: Props) => {
11
+ const { statusColorMap, statusTextMap, status } = props;
12
+ if (typeof status !== "string" && typeof status !== "number") return null;
13
+ return (
14
+ <Chip label={statusTextMap?.[status]} color={statusColorMap[status]} />
15
+ );
16
+ };
17
+
18
+ export { StatusPill };
@@ -0,0 +1,98 @@
1
+ import type { Column } from "@evenicanpm/portal-types-schemas";
2
+ import { Skeleton } from "@mui/material";
3
+ import Checkbox from "@mui/material/Checkbox";
4
+ import { styled } from "@mui/material/styles";
5
+ import TableCell from "@mui/material/TableCell";
6
+ import TableHead from "@mui/material/TableHead";
7
+ import TableRow from "@mui/material/TableRow";
8
+ import type React from "react";
9
+
10
+ // STYLED COMPONENTS
11
+ const StyledTableCell = styled(TableCell)(({ theme }) => ({
12
+ fontWeight: 600,
13
+ padding: "16px 20px",
14
+ color: theme.palette.grey[900],
15
+ minWidth: 140,
16
+ }));
17
+
18
+ interface TableHeaderProps {
19
+ hasActions: boolean;
20
+ columns: (Column & { ColumnOrder: number })[] | undefined;
21
+ loading: boolean;
22
+ selectAllChecked: boolean;
23
+ selectAllIndeterminate: boolean;
24
+ onToggleAll: (checked: boolean) => void;
25
+ }
26
+
27
+ /**
28
+ *
29
+ * Table handles display and caches it's own data.
30
+ *
31
+ */
32
+ export default function TableHeader({
33
+ hasActions,
34
+ columns,
35
+ loading,
36
+ selectAllChecked,
37
+ selectAllIndeterminate,
38
+ onToggleAll,
39
+ }: TableHeaderProps) {
40
+ if (loading) {
41
+ return (
42
+ <TableHead>
43
+ <TableRow>
44
+ <StyledTableCell padding="checkbox" sx={{ width: 56 }}>
45
+ <Skeleton
46
+ variant="rounded"
47
+ width={24}
48
+ height={24}
49
+ sx={{ bgcolor: "rgba(255,255,255,0.1)" }}
50
+ />
51
+ </StyledTableCell>
52
+ {Array.from({ length: 6 })?.map((_col, i) => (
53
+ <StyledTableCell key={String(i)}>
54
+ <Skeleton
55
+ variant="rounded"
56
+ width={100}
57
+ height={25}
58
+ sx={{ bgcolor: "rgba(255,255,255,0.1)" }}
59
+ />
60
+ </StyledTableCell>
61
+ ))}
62
+ {hasActions && (
63
+ <StyledTableCell>
64
+ <Skeleton variant="rounded" width={100} height={25} />
65
+ </StyledTableCell>
66
+ )}
67
+ </TableRow>
68
+ </TableHead>
69
+ );
70
+ }
71
+ return (
72
+ <TableHead>
73
+ <TableRow>
74
+ <StyledTableCell sx={{ padding: "16px 16px", width: 72 }}>
75
+ <Checkbox
76
+ checked={selectAllChecked}
77
+ indeterminate={selectAllIndeterminate}
78
+ onChange={(_e, checked) => onToggleAll(checked)}
79
+ inputProps={{ "aria-label": "select all rows" }}
80
+ />
81
+ </StyledTableCell>
82
+
83
+ {columns?.map((column, i) => (
84
+ <StyledTableCell
85
+ key={column.Label + String(i)}
86
+ sx={{
87
+ whiteSpace: "normal",
88
+ wordBreak: "break-word",
89
+ }}
90
+ >
91
+ {column.Label}
92
+ </StyledTableCell>
93
+ ))}
94
+ {hasActions && <StyledTableCell>Actions</StyledTableCell>}
95
+ </TableRow>
96
+ </TableHead>
97
+ );
98
+ }