@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.
- package/package.json +3 -3
- package/src/auth/auth-context.tsx +15 -9
- package/src/features/table/components/dashboard/table/bulk-action-button.tsx +77 -0
- package/src/features/table/components/dashboard/table/data-mask-map.ts +1 -1
- package/src/features/table/components/dashboard/table/details/details.tsx +56 -35
- package/src/features/table/components/dashboard/table/details/rows/downloads.tsx +0 -1
- package/src/features/table/components/dashboard/table/details/rows/primitive.tsx +0 -1
- package/src/features/table/components/dashboard/table/details/rows/table.tsx +0 -1
- package/src/features/table/components/dashboard/table/details/templates/invoice-details.tsx +407 -0
- package/src/features/table/components/dashboard/table/details/templates/template-registry.ts +36 -0
- package/src/features/table/components/dashboard/table/filter-render-map.tsx +2 -1
- package/src/features/table/components/dashboard/table/filters/date-filter.tsx +5 -5
- package/src/features/table/components/dashboard/table/filters/status-filter.tsx +2 -2
- package/src/features/table/components/dashboard/table/no-results.tsx +1 -1
- package/src/features/table/components/dashboard/table/table-header.tsx +41 -5
- package/src/features/table/components/dashboard/table/table-row/action-button.tsx +32 -24
- package/src/features/table/components/dashboard/table/table-row/table-row.tsx +61 -13
- package/src/features/table/components/dashboard/table/table-row-skeleton.tsx +0 -1
- package/src/features/table/components/dashboard/table/table.tsx +141 -30
- package/src/features/table/components/dashboard/table/use-select-rows.ts +53 -0
- package/src/features/table/components/dashboard/table-dashboard.tsx +2 -2
- package/src/features/table/logic/resolvers.ts +43 -0
- package/src/features/table/types/schemas.ts +39 -0
- package/src/index.ts +1 -2
- package/dist/auth/auth-context.d.ts +0 -36
- package/dist/features/settings/index.d.ts +0 -2
- package/dist/features/table/components/dashboard/index.d.ts +0 -1
- package/dist/features/table/components/dashboard/table/actions.d.ts +0 -14
- package/dist/features/table/components/dashboard/table/data-mask-map.d.ts +0 -2
- package/dist/features/table/components/dashboard/table/details/details.d.ts +0 -27
- package/dist/features/table/components/dashboard/table/details/index.d.ts +0 -1
- package/dist/features/table/components/dashboard/table/details/label.d.ts +0 -5
- package/dist/features/table/components/dashboard/table/details/rows/downloads.d.ts +0 -6
- package/dist/features/table/components/dashboard/table/details/rows/index.d.ts +0 -3
- package/dist/features/table/components/dashboard/table/details/rows/primitive.d.ts +0 -6
- package/dist/features/table/components/dashboard/table/details/rows/table.d.ts +0 -17
- package/dist/features/table/components/dashboard/table/details/styles.d.ts +0 -10
- package/dist/features/table/components/dashboard/table/filter-render-map.d.ts +0 -2
- package/dist/features/table/components/dashboard/table/filters/date-filter.d.ts +0 -2
- package/dist/features/table/components/dashboard/table/filters/status-filter.d.ts +0 -2
- package/dist/features/table/components/dashboard/table/index.d.ts +0 -2
- package/dist/features/table/components/dashboard/table/no-results.d.ts +0 -2
- package/dist/features/table/components/dashboard/table/status-pill.d.ts +0 -8
- package/dist/features/table/components/dashboard/table/table-header.d.ts +0 -16
- package/dist/features/table/components/dashboard/table/table-input.d.ts +0 -11
- package/dist/features/table/components/dashboard/table/table-row/action-button.d.ts +0 -8
- package/dist/features/table/components/dashboard/table/table-row/table-row.d.ts +0 -32
- package/dist/features/table/components/dashboard/table/table-row-skeleton.d.ts +0 -6
- package/dist/features/table/components/dashboard/table/table.d.ts +0 -18
- package/dist/features/table/components/dashboard/table-dashboard.d.ts +0 -22
- package/dist/features/table/index.d.ts +0 -8
- package/dist/features/table/logic/index.d.ts +0 -23
- package/dist/features/table/logic/resolvers.d.ts +0 -21
- package/dist/features/table/logic/transformers.d.ts +0 -35
- package/dist/features/table/logic/types.d.ts +0 -8
- package/dist/features/table/types/handlers.d.ts +0 -53
- package/dist/features/table/types/index.d.ts +0 -69
- package/dist/features/table/types/queries.d.ts +0 -27
- package/dist/features/table/types/schemas.d.ts +0 -31
- package/dist/index.d.ts +0 -63
- package/dist/portal.d.ts +0 -38
- package/dist/tsconfig.tsbuildinfo +0 -1
- 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:
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
{
|
|
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
|
|
90
|
-
|
|
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
|
}
|
|
@@ -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
|
|
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
|
-
...
|
|
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:
|
|
126
|
-
viewConfigQuery(resource.name)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
156
|
-
rows?.data.find((doc) => doc?.[idField] === selectedDocument)
|
|
157
|
-
|
|
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={
|
|
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
|
-
|
|
174
|
-
|
|
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=
|
|
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={() =>
|
|
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
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
|
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
|
@@ -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 +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 {};
|