@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
@@ -1,10 +1,12 @@
1
+ import { Action } from "@evenicanpm/portal-types-schemas";
2
+ import { FlexBox } from "@evenicanpm/ui";
3
+ import Checkbox from "@mui/material/Checkbox";
1
4
  import TableCell from "@mui/material/TableCell";
2
5
  import TableRow from "@mui/material/TableRow";
6
+ import { styled } from "@mui/system";
3
7
  import type React from "react";
8
+ import type { UIAction } from "src/features/table/types/schemas";
4
9
  import type * as Types from "../../../../types";
5
- import { styled } from "@mui/system";
6
- import { Action } from "@evenicanpm/portal-types-schemas";
7
- import { FlexBox } from "@evenicanpm/ui";
8
10
  import { ActionButton } from "./action-button";
9
11
 
10
12
  /**
@@ -38,10 +40,13 @@ export type DocumentTableProps = {
38
40
  /** */
39
41
  handleClick: React.Dispatch<React.SetStateAction<string | number | null>>;
40
42
  /** Actions renderer */
41
- actions: Action[];
43
+ actions: UIAction[];
42
44
  /** Does this view config have actions? */
43
45
  /** The id field for the document (used for knowing what to set selected id to) */
44
- idField: string;
46
+ id: string | number;
47
+ isSelected: boolean;
48
+ onToggleSelected: (id: string | number) => void;
49
+ resourceName: string;
45
50
  };
46
51
 
47
52
  /**
@@ -54,9 +59,15 @@ export default function DataTableRow({
54
59
  columnLogic,
55
60
  handleClick: handleSelectDocument,
56
61
  actions,
57
- idField,
58
- }: Readonly<DocumentTableProps>) {
59
- const id = row?.[idField] as string;
62
+ id,
63
+ isSelected,
64
+ onToggleSelected,
65
+ resourceName,
66
+ }: DocumentTableProps) {
67
+ const visibleActions = actions.filter(
68
+ (a) => !a.isEnabled || a.isEnabled(row),
69
+ );
70
+
60
71
  return (
61
72
  <TableRow
62
73
  hover
@@ -69,6 +80,15 @@ export default function DataTableRow({
69
80
  }
70
81
  }}
71
82
  >
83
+ <TableCell onClick={(e) => e.stopPropagation()}>
84
+ <Checkbox
85
+ checked={isSelected}
86
+ onChange={() => {
87
+ onToggleSelected(id);
88
+ }}
89
+ />
90
+ </TableCell>
91
+
72
92
  {columnLogic?.map((col, i) => {
73
93
  // --- Cell Render --- //
74
94
  const rawValue = row[col.id];
@@ -80,17 +100,45 @@ export default function DataTableRow({
80
100
  </StyledTableCell>
81
101
  );
82
102
  })}
83
- {actions?.map((action) => (
103
+ {visibleActions.length > 0 && (
84
104
  <TableCell
85
- key={action.Name}
86
105
  onClick={(e) => e.stopPropagation()}
87
106
  sx={{ whiteSpace: "nowrap" }}
88
107
  >
89
- <FlexBox justifyContent="flex-start" gap={1}>
90
- <ActionButton docId={id} action={action} resourceName={"invoice"} />
108
+ <FlexBox
109
+ gap={1}
110
+ sx={{
111
+ flexDirection: "row",
112
+ flexWrap: "nowrap",
113
+ alignItems: "center",
114
+ }}
115
+ >
116
+ {actions.map((action) => {
117
+ const enabled = !action.isEnabled || action.isEnabled(row);
118
+ if (!enabled) {
119
+ return (
120
+ <span
121
+ key={action.label}
122
+ style={{
123
+ display: "inline-block",
124
+ width: action.icon ? "34px" : "42px",
125
+ height: "34px",
126
+ }}
127
+ />
128
+ );
129
+ }
130
+ return (
131
+ <ActionButton
132
+ key={action.label}
133
+ docId={String(id)}
134
+ action={action}
135
+ resourceName={resourceName}
136
+ />
137
+ );
138
+ })}
91
139
  </FlexBox>
92
140
  </TableCell>
93
- ))}
141
+ )}
94
142
  </TableRow>
95
143
  );
96
144
  }
@@ -1,7 +1,6 @@
1
1
  import { Skeleton } from "@mui/material";
2
2
  import TableCell from "@mui/material/TableCell";
3
3
  import TableRow from "@mui/material/TableRow";
4
- import type React from "react";
5
4
  import { styled } from "@mui/system";
6
5
 
7
6
  const StyledTableCell = styled(TableCell)({
@@ -9,6 +9,7 @@ import type React from "react";
9
9
  import { useState } from "react";
10
10
  import { resolvers, transformers } from "../../../logic";
11
11
  import type * as Types from "../../../types";
12
+ import { BulkActionButton } from "./bulk-action-button";
12
13
  import { dataMaskMap as dataMaskMapLocal } from "./data-mask-map";
13
14
  import { Details } from "./details";
14
15
  import { filterRenderMapFallback } from "./filter-render-map";
@@ -18,12 +19,15 @@ import TableHeader from "./table-header";
18
19
  import { SearchField } from "./table-input";
19
20
  import DataTableRow from "./table-row/table-row";
20
21
  import TableRowSkeleton from "./table-row-skeleton";
22
+ import { useSelectRows } from "./use-select-rows";
23
+
24
+ type Id = string | number;
21
25
 
22
26
  export type DocumentTableProps = {
23
27
  resource: Types.Schemas.Resource;
24
28
  pageSize: number;
25
29
  setPageSize: React.Dispatch<React.SetStateAction<number>>;
26
- viewConfigQuery: (resourceName: string) => {
30
+ viewConfigQuery?: (resourceName: string) => {
27
31
  queryKey: QueryKey;
28
32
  queryFn: () => Promise<Types.Queries.TableConfig>;
29
33
  };
@@ -46,7 +50,6 @@ export default function DocumentTable({
46
50
  >(null);
47
51
  const [filters, setFilters] = useState<Record<string, string>>({});
48
52
  const [searchTerm, setSearchTerm] = useState("");
49
- const [searchColumn, setSearchColumn] = useState("Title");
50
53
 
51
54
  const {
52
55
  dataMaskMap,
@@ -58,6 +61,9 @@ export default function DocumentTable({
58
61
  noResultsComponent,
59
62
  statusColorMap,
60
63
  statusTextMap,
64
+ viewConfig,
65
+ actions: actionRegistry,
66
+ bulkActions: bulkRegistry,
61
67
  } = resource;
62
68
 
63
69
  /**
@@ -72,7 +78,7 @@ export default function DocumentTable({
72
78
  */
73
79
  const resolvedFilterRenderMap = {
74
80
  ...filterRenderMapFallback,
75
- ...(filterRenderMap ?? {}),
81
+ ...filterRenderMap,
76
82
  };
77
83
 
78
84
  const cellRenderMapDefaults = {
@@ -92,6 +98,7 @@ export default function DocumentTable({
92
98
  ) => {
93
99
  setPageSize(Number.parseInt(event.target.value, 10));
94
100
  setPage(0);
101
+ clearSelection();
95
102
  };
96
103
 
97
104
  const handleChangePage = (
@@ -99,6 +106,7 @@ export default function DocumentTable({
99
106
  newPage: number,
100
107
  ) => {
101
108
  setPage(newPage);
109
+ clearSelection();
102
110
  };
103
111
 
104
112
  const handleBack = () => {
@@ -122,22 +130,34 @@ export default function DocumentTable({
122
130
  */
123
131
 
124
132
  // --- Fetch --- //
125
- const { data: config, isPending: configPending } = useQuery(
126
- viewConfigQuery(resource.name),
127
- );
128
- const { data: rows, isPending: rowsPending } = useQuery(
129
- resource.rowsQueryFn({ page, pageSize, filters, searchColumn, searchTerm }),
130
- );
133
+ const { data: fetchedConfig, isPending: fetchedConfigPending } = useQuery({
134
+ ...(viewConfigQuery?.(resource.name) ?? {
135
+ queryKey: [],
136
+ queryFn: () => Promise.resolve(undefined),
137
+ }),
138
+ enabled: !!viewConfigQuery && !viewConfig,
139
+ });
140
+
141
+ const config = viewConfig ?? fetchedConfig;
142
+
143
+ const configPending = !viewConfig ? fetchedConfigPending : false;
131
144
 
132
145
  const columns = config?.Columns ?? [];
133
- const actions = config?.Actions ?? [];
134
- const hasActions = actions.length > 0;
135
146
 
136
147
  // --- Transformer --- //
137
148
  const searchableCols = transformers.getSearchableColumns(columns);
138
149
  const filterReqs = transformers.getActiveFilters(columns);
139
150
  const visibleCols = transformers.getVisibleColumns(columns);
140
151
 
152
+ // Depends on searchableCols, must be declared after transformer
153
+ const [searchColumn, setSearchColumn] = useState(
154
+ () => searchableCols?.[0]?.Data ?? "Title",
155
+ );
156
+
157
+ const { data: rows, isPending: rowsPending } = useQuery(
158
+ resource.rowsQueryFn({ page, pageSize, filters, searchColumn, searchTerm }),
159
+ );
160
+
141
161
  // --- Resolver --- //
142
162
  const filterLogic = filterReqs?.map((req) => ({
143
163
  req,
@@ -150,28 +170,84 @@ export default function DocumentTable({
150
170
  cellFn: resolvers.resolveCellUI(col, cellRenderMapDefaults),
151
171
  }));
152
172
 
173
+ const getRowId = (row: Types.Queries.Row, field: string): Id | null => {
174
+ const v = row?.[field];
175
+ if (typeof v === "string" || typeof v === "number") return v;
176
+ return null;
177
+ };
178
+
179
+ const currentPageIds: Id[] =
180
+ rows?.data
181
+ ?.map((r) => getRowId(r, idField))
182
+ .filter((v): v is Id => v !== null) ?? [];
183
+
184
+ const {
185
+ selectedIds,
186
+ clearSelection,
187
+ toggleOne,
188
+ toggleAllOnPage,
189
+ isAllOnPageSelected,
190
+ isSomeOnPageSelected,
191
+ } = useSelectRows({ currentPageIds });
192
+
193
+ const hasTemplateSupport =
194
+ !!resource.detailsQueryFn && !!resource.detailsTemplate;
195
+
196
+ const { data: detailsData, isPending: detailsPending } = useQuery(
197
+ hasTemplateSupport && selectedDocument
198
+ ? resource.detailsQueryFn!(String(selectedDocument))
199
+ : {
200
+ queryKey: [],
201
+ queryFn: async () => undefined,
202
+ enabled: false,
203
+ },
204
+ );
205
+
153
206
  // --- Render --- //
154
207
  if (selectedDocument) {
155
- const selectedDocDetails =
156
- rows?.data.find((doc) => doc?.[idField] === selectedDocument) || {};
157
- // Build the logic necessary for rendering
208
+ const rowSnapshot =
209
+ rows?.data.find((doc) => doc?.[idField] === selectedDocument) ?? {};
210
+
211
+ const finalDetailsData =
212
+ hasTemplateSupport && detailsData ? detailsData : rowSnapshot;
213
+
158
214
  return (
159
215
  <Details
160
216
  documentTypeLabel={label}
161
217
  id={selectedDocument}
162
- documentData={selectedDocDetails}
218
+ documentData={finalDetailsData}
163
219
  handleBack={handleBack}
164
- columns={columns || []}
220
+ columns={columns}
165
221
  dataMaskMap={resolvedDataMaskMap}
166
222
  statusColorMap={statusColorMap}
167
223
  statusTextMap={statusTextMap}
168
224
  detailsRenderMap={detailsRenderMap}
225
+ template={hasTemplateSupport ? resource.detailsTemplate : undefined}
226
+ loading={hasTemplateSupport ? detailsPending : false}
169
227
  />
170
228
  );
171
229
  }
172
230
 
173
- // Table body data calculations
174
- const colSpan = visibleCols?.length ? visibleCols.length + 1 : 4;
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
+ const baseCols = (visibleCols?.length ?? 0) + 1;
250
+ const colSpan = hasActions ? baseCols + 1 : baseCols;
175
251
  const noResults = !rowsPending && !configPending && rows?.data?.length === 0;
176
252
 
177
253
  return (
@@ -180,7 +256,7 @@ export default function DocumentTable({
180
256
  <FlexBox
181
257
  justifyContent={"flex-end"}
182
258
  width="100%"
183
- height={65}
259
+ height="fit-content"
184
260
  gap={2}
185
261
  pt={2}
186
262
  >
@@ -196,10 +272,33 @@ export default function DocumentTable({
196
272
  key={filter.req?.filterName}
197
273
  value={filters}
198
274
  setValue={setFilters}
199
- resetPagination={() => setPage(0)}
275
+ resetPagination={() => {
276
+ setPage(0);
277
+ clearSelection();
278
+ }}
200
279
  {...filter.req}
201
280
  />
202
281
  ))}
282
+
283
+ {hasBulkActions &&
284
+ resolvedBulkActions.map((action) => {
285
+ const selectedRows =
286
+ rows?.data?.filter((row) => {
287
+ const id = getRowId(row, idField);
288
+ return id !== null && selectedIds.has(id);
289
+ }) ?? [];
290
+
291
+ return (
292
+ <BulkActionButton
293
+ key={action.label}
294
+ action={action}
295
+ resourceName={resource.name}
296
+ selectedIds={Array.from(selectedIds)}
297
+ selectedRows={selectedRows}
298
+ clearSelection={clearSelection}
299
+ />
300
+ );
301
+ })}
203
302
  </FlexBox>
204
303
  <TableContainer>
205
304
  <Table aria-label="simple table" sx={{ tableLayout: { md: "fixed" } }}>
@@ -207,7 +306,11 @@ export default function DocumentTable({
207
306
  columns={visibleCols}
208
307
  loading={configPending}
209
308
  hasActions={hasActions}
309
+ selectAllChecked={isAllOnPageSelected}
310
+ selectAllIndeterminate={isSomeOnPageSelected}
311
+ onToggleAll={toggleAllOnPage}
210
312
  />
313
+
211
314
  <TableBody>
212
315
  {noResults ? (
213
316
  <TableRow>
@@ -222,16 +325,24 @@ export default function DocumentTable({
222
325
  ) : rowsPending ? (
223
326
  <TableRowSkeleton pageSize={pageSize} colSpan={colSpan} />
224
327
  ) : (
225
- rows?.data?.map((row, i) => (
226
- <DataTableRow
227
- key={`data-table-row-${i + 1}`}
228
- columnLogic={columnLogic}
229
- row={row}
230
- handleClick={setSelectedDocument}
231
- idField={idField}
232
- actions={actions}
233
- />
234
- ))
328
+ rows?.data?.map((row) => {
329
+ const id = getRowId(row, idField);
330
+ if (id === null) return null;
331
+
332
+ return (
333
+ <DataTableRow
334
+ key={id}
335
+ columnLogic={columnLogic}
336
+ row={row}
337
+ handleClick={setSelectedDocument}
338
+ id={id}
339
+ actions={resolvedActions}
340
+ isSelected={selectedIds.has(id)}
341
+ onToggleSelected={(docId) => toggleOne(docId)}
342
+ resourceName={resource.name}
343
+ />
344
+ );
345
+ })
235
346
  )}
236
347
  </TableBody>
237
348
  </Table>
@@ -0,0 +1,53 @@
1
+ import { useState } from "react";
2
+
3
+ type Id = string | number;
4
+
5
+ type Input = {
6
+ currentPageIds: Id[];
7
+ };
8
+
9
+ export const useSelectRows = (input: Input) => {
10
+ const { currentPageIds } = input;
11
+
12
+ const [selectedIds, setSelectedIds] = useState<Set<Id>>(new Set());
13
+
14
+ const clearSelection = () => setSelectedIds(new Set());
15
+
16
+ const toggleOne = (id: Id) => {
17
+ setSelectedIds((prev) => {
18
+ const next = new Set(prev);
19
+ if (next.has(id)) next.delete(id);
20
+ else next.add(id);
21
+ return next;
22
+ });
23
+ };
24
+
25
+ const isAllOnPageSelected =
26
+ currentPageIds.length > 0 &&
27
+ currentPageIds.every((id) => selectedIds.has(id));
28
+
29
+ const isSomeOnPageSelected =
30
+ currentPageIds.some((id) => selectedIds.has(id)) && !isAllOnPageSelected;
31
+
32
+ const toggleAllOnPage = (checked: boolean) => {
33
+ setSelectedIds((prev) => {
34
+ const next = new Set(prev);
35
+ if (checked) {
36
+ for (const id of currentPageIds) next.add(id);
37
+ } else {
38
+ for (const id of currentPageIds) next.delete(id);
39
+ }
40
+ return next;
41
+ });
42
+ };
43
+
44
+ return {
45
+ selectedIds,
46
+ setSelectedIds,
47
+ clearSelection,
48
+ toggleOne,
49
+ toggleAllOnPage,
50
+ isAllOnPageSelected,
51
+ isSomeOnPageSelected,
52
+ };
53
+ };
@@ -3,10 +3,10 @@ import TabList from "@mui/lab/TabList";
3
3
  import TabPanel from "@mui/lab/TabPanel";
4
4
  import Box from "@mui/material/Box";
5
5
  import Tab from "@mui/material/Tab";
6
+ import type { QueryKey } from "@tanstack/react-query";
6
7
  import { type SyntheticEvent, useState } from "react";
7
8
  import type * as Types from "../../types";
8
9
  import { Table } from "./table";
9
- import { QueryKey } from "@tanstack/react-query";
10
10
 
11
11
  /**
12
12
  * The main props for top-level table entry-point
@@ -14,7 +14,7 @@ import { QueryKey } from "@tanstack/react-query";
14
14
  *
15
15
  */
16
16
  export interface TableProps {
17
- viewConfigQuery: (resourceName: string) => {
17
+ viewConfigQuery?: (resourceName: string) => {
18
18
  queryKey: QueryKey;
19
19
  queryFn: () => Promise<Types.Queries.TableConfig>;
20
20
  };
@@ -1,6 +1,8 @@
1
1
  import { method, multi } from "@arrows/multimethod";
2
2
  import type { Column } from "@evenicanpm/portal-types-schemas";
3
3
  import type * as Types from "../types";
4
+ import type { TableConfig } from "../types/queries";
5
+ import type { UIAction, UIBulkAction } from "../types/schemas";
4
6
  import type { FilterRequest } from "./types";
5
7
 
6
8
  /** MULTIMETHOD FOR FILTERS
@@ -66,3 +68,44 @@ export const resolveCellUI = (
66
68
 
67
69
  return (value: string | number) => value;
68
70
  };
71
+
72
+ export function resolveActions(
73
+ configActions: TableConfig["Actions"] | undefined,
74
+ registry: Record<string, UIAction> | undefined,
75
+ ): UIAction[] {
76
+ if (!configActions || !registry) return [];
77
+
78
+ return configActions
79
+ .filter((a) => a.IsEnabled)
80
+ .flatMap((a) => {
81
+ const action = registry[a.Type];
82
+ if (!action) {
83
+ console.warn(`Unknown action type: ${a.Type}`);
84
+ return [];
85
+ }
86
+ return [
87
+ {
88
+ ...action,
89
+ label: a.Name,
90
+ },
91
+ ];
92
+ });
93
+ }
94
+
95
+ export function resolveBulkActions(
96
+ configBulkActions: TableConfig["BulkActions"] | undefined,
97
+ registry: Record<string, UIBulkAction> | undefined,
98
+ ): UIBulkAction[] {
99
+ if (!configBulkActions || !registry) return [];
100
+
101
+ return configBulkActions
102
+ .filter((a) => a.IsEnabled)
103
+ .flatMap((a) => {
104
+ const action = registry[a.Type] as UIBulkAction;
105
+ if (!action) {
106
+ console.warn(`Unknown bulk action type: ${a.Type}`);
107
+ return [];
108
+ }
109
+ return [{ ...action, label: a.Name }];
110
+ });
111
+ }
@@ -1,4 +1,8 @@
1
1
  import type { TableViewParams } from "@evenicanpm/portal-types-schemas";
2
+ import type { SvgIconComponent } from "@mui/icons-material";
3
+ import type { UseMutationOptions } from "@tanstack/react-query";
4
+ import type { Table } from "..";
5
+ import type { DetailsTemplateKey } from "../components/dashboard/table/details/templates/template-registry";
2
6
  import type {
3
7
  CellRenderMap,
4
8
  DataMaskMap,
@@ -21,6 +25,27 @@ export type StatusColor = "success" | "primary" | "warning" | "error";
21
25
 
22
26
  export type StatusColorMap = Record<number | string, StatusColor>;
23
27
 
28
+ type BaseAction = {
29
+ label?: string;
30
+ icon?: SvgIconComponent;
31
+ };
32
+
33
+ export type UIAction = BaseAction & {
34
+ isEnabled?: (row: Record<string, unknown>) => boolean;
35
+ mutation: (args: {
36
+ resourceName: string;
37
+ docId: string | number;
38
+ }) => UseMutationOptions<any, any, any, any>;
39
+ };
40
+
41
+ export type UIBulkAction = BaseAction & {
42
+ isAllowed?: (rows: Record<string, unknown>[]) => boolean;
43
+ mutation: (args: {
44
+ resourceName: string;
45
+ selectedIds: (string | number)[];
46
+ }) => UseMutationOptions<any, any, any, any>;
47
+ };
48
+
24
49
  /**
25
50
  * Resource Definition
26
51
  */
@@ -44,7 +69,21 @@ export interface Resource {
44
69
  // Optional Details view
45
70
  detailsRenderMap?: DetailsRenderMap;
46
71
 
72
+ detailsQueryFn?: (id: string) => {
73
+ queryKey: readonly unknown[];
74
+ queryFn: () => Promise<unknown>;
75
+ };
76
+
77
+ detailsTemplate?: DetailsTemplateKey;
78
+
47
79
  // State Visuals
48
80
  statusTextMap: StatusTextMap;
49
81
  statusColorMap: StatusColorMap;
82
+
83
+ // View Config Value
84
+ viewConfig?: Table.Queries.TableConfig;
85
+
86
+ // Actions and Bulk Actions
87
+ actions?: Record<string, UIAction>;
88
+ bulkActions?: Record<string, UIBulkAction>;
50
89
  }
package/src/index.ts CHANGED
@@ -60,6 +60,5 @@
60
60
  */
61
61
 
62
62
  export type { Session } from "./auth/auth-context";
63
- export { Portal, type PortalProps } from "./portal";
64
-
65
63
  export { Table } from "./features/table";
64
+ export { Portal, type PortalProps } from "./portal";
@@ -1,36 +0,0 @@
1
- import { type PropsWithChildren } from "react";
2
- /**
3
- * Powers the auth provider that portal is wrapped in
4
- * Aggregates user and session related data that is needed
5
- * within the portal
6
- */
7
- interface AuthProviderProps {
8
- /** The user details returned from auth framework eg. next-auth **/
9
- session: Session;
10
- /** how to handle logout **/
11
- logout: () => void;
12
- }
13
- /**
14
- * The context value
15
- */
16
- interface AuthContext {
17
- session: Session | null;
18
- /** how to handle logout **/
19
- logout: (() => void) | null;
20
- isAuthenticated: boolean | null;
21
- }
22
- interface Session {
23
- email: string | null | undefined;
24
- name: string | null | undefined;
25
- token: string | null | undefined;
26
- }
27
- declare const AuthContext: import("react").Context<AuthContext>;
28
- /**
29
- * Used to access auth details and callback handlers within the portal auth context
30
- */
31
- declare const useAuth: () => AuthContext;
32
- /**
33
- *
34
- */
35
- declare const PortalAuthProvider: ({ session, logout, children, }: PropsWithChildren<AuthProviderProps>) => import("react").JSX.Element;
36
- export { PortalAuthProvider, useAuth, type AuthProviderProps, AuthContext, type Session, };
@@ -1,2 +0,0 @@
1
- declare const PortalSettings: () => import("react").JSX.Element;
2
- export { PortalSettings };
@@ -1 +0,0 @@
1
- export { TableDashboard } from "./table-dashboard";
@@ -1,14 +0,0 @@
1
- import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
2
- import { UseMutationOptions } from "@tanstack/react-query";
3
- /**
4
- *
5
- * Map of action types -> MUI Icons
6
- *
7
- */
8
- export declare const actionIconMap: Record<string, typeof FileDownloadOutlinedIcon>;
9
- type MutationFactory = (resourceName: string, docId: string) => UseMutationOptions<any, any, any, any>;
10
- /**
11
- *
12
- */
13
- export declare const actionMutationMap: Record<string, MutationFactory>;
14
- export {};
@@ -1,2 +0,0 @@
1
- import * as Table from "../../../types/";
2
- export declare const dataMaskMap: Table.Handlers.DataMaskMap;