@evenicanpm/portal-table-ui 1.6.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 (63) hide show
  1. package/package.json +3 -3
  2. package/src/auth/auth-context.tsx +15 -9
  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 +1 -1
  5. package/src/features/table/components/dashboard/table/details/details.tsx +56 -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 +407 -0
  10. package/src/features/table/components/dashboard/table/details/templates/template-registry.ts +36 -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 +61 -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 +141 -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,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;
@@ -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
  ))}
@@ -1,12 +1,10 @@
1
- import { actionIconMap, actionMutationMap } from "../actions";
2
- import { FlexBox } from "@evenicanpm/ui";
3
- import { IconButton, CircularProgress } from "@mui/material";
4
- import { Action } from "@evenicanpm/portal-types-schemas";
1
+ import { Button, CircularProgress, IconButton } from "@mui/material";
5
2
  import { useMutation } from "@tanstack/react-query";
3
+ import type { UIAction } from "src/features/table/types/schemas";
6
4
 
7
5
  interface ActionButtonProps {
8
6
  docId: string;
9
- action: Action;
7
+ action: UIAction;
10
8
  resourceName: string;
11
9
  }
12
10
 
@@ -14,26 +12,36 @@ export function ActionButton({
14
12
  action,
15
13
  resourceName,
16
14
  docId,
17
- }: Readonly<ActionButtonProps>) {
18
- // Maybe move to a resolver??
19
- // currently we only use the action type to resolve
20
- const Icon = actionIconMap?.[action.Type];
21
- const mutation = actionMutationMap?.[action.Type](resourceName, docId);
22
- const { mutate, isPending } = useMutation(mutation);
15
+ }: ActionButtonProps) {
16
+ const { mutate, isPending } = useMutation(
17
+ action.mutation({
18
+ resourceName,
19
+ docId,
20
+ }),
21
+ );
22
+
23
+ const handleClick = (e: React.MouseEvent) => {
24
+ e.stopPropagation();
25
+ mutate({ docId });
26
+ };
27
+
28
+ if (action.icon) {
29
+ const Icon = action.icon;
30
+ return (
31
+ <IconButton size="small" onClick={handleClick} disabled={isPending}>
32
+ {isPending ? <CircularProgress size={26} /> : Icon ? <Icon /> : null}
33
+ </IconButton>
34
+ );
35
+ }
23
36
 
24
37
  return (
25
- <FlexBox justifyContent="flex-start" gap={1}>
26
- <span key={action.Name}>
27
- <IconButton
28
- size="small"
29
- onClick={(e) => {
30
- e.stopPropagation();
31
- mutate({ docId });
32
- }}
33
- >
34
- {isPending ? <CircularProgress size={26} /> : <Icon />}
35
- </IconButton>
36
- </span>
37
- </FlexBox>
38
+ <Button
39
+ size="small"
40
+ variant="outlined"
41
+ onClick={handleClick}
42
+ disabled={isPending}
43
+ >
44
+ {isPending ? <CircularProgress size={18} /> : action.label}
45
+ </Button>
38
46
  );
39
47
  }