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