@evenicanpm/portal-table-ui 1.5.0 → 1.6.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 (37) hide show
  1. package/package.json +6 -5
  2. package/src/auth/auth-context.tsx +86 -0
  3. package/src/features/table/components/dashboard/index.tsx +1 -0
  4. package/src/features/table/components/dashboard/table/actions.ts +59 -0
  5. package/src/features/table/components/dashboard/table/data-mask-map.ts +26 -0
  6. package/src/features/table/components/dashboard/table/details/details.tsx +164 -0
  7. package/src/features/table/components/dashboard/table/details/index.ts +1 -0
  8. package/src/features/table/components/dashboard/table/details/label.tsx +26 -0
  9. package/src/features/table/components/dashboard/table/details/rows/downloads.tsx +26 -0
  10. package/src/features/table/components/dashboard/table/details/rows/index.ts +3 -0
  11. package/src/features/table/components/dashboard/table/details/rows/primitive.tsx +26 -0
  12. package/src/features/table/components/dashboard/table/details/rows/table.tsx +74 -0
  13. package/src/features/table/components/dashboard/table/details/styles.ts +18 -0
  14. package/src/features/table/components/dashboard/table/filter-render-map.tsx +9 -0
  15. package/src/features/table/components/dashboard/table/filters/date-filter.tsx +31 -0
  16. package/src/features/table/components/dashboard/table/filters/status-filter.tsx +36 -0
  17. package/src/features/table/components/dashboard/table/index.ts +2 -0
  18. package/src/features/table/components/dashboard/table/no-results.tsx +26 -0
  19. package/src/features/table/components/dashboard/table/status-pill.tsx +18 -0
  20. package/src/features/table/components/dashboard/table/table-header.tsx +62 -0
  21. package/src/features/table/components/dashboard/table/table-input.tsx +94 -0
  22. package/src/features/table/components/dashboard/table/table-row/action-button.tsx +39 -0
  23. package/src/features/table/components/dashboard/table/table-row/table-row.tsx +96 -0
  24. package/src/features/table/components/dashboard/table/table-row-skeleton.tsx +36 -0
  25. package/src/features/table/components/dashboard/table/table.tsx +250 -0
  26. package/src/features/table/components/dashboard/table-dashboard.tsx +71 -0
  27. package/src/features/table/index.ts +14 -0
  28. package/src/features/table/logic/index.ts +23 -0
  29. package/src/features/table/logic/resolvers.ts +68 -0
  30. package/src/features/table/logic/transformers.ts +56 -0
  31. package/src/features/table/logic/types.ts +8 -0
  32. package/src/features/table/types/handlers.ts +71 -0
  33. package/src/features/table/types/index.ts +70 -0
  34. package/src/features/table/types/queries.ts +34 -0
  35. package/src/features/table/types/schemas.ts +50 -0
  36. package/src/index.ts +65 -0
  37. package/src/portal.tsx +68 -0
@@ -0,0 +1,62 @@
1
+ import { Skeleton } from "@mui/material";
2
+ import { styled } from "@mui/material/styles";
3
+ import TableCell from "@mui/material/TableCell";
4
+ import TableHead from "@mui/material/TableHead";
5
+ import TableRow from "@mui/material/TableRow";
6
+ import type React from "react";
7
+ import { Column } from "@evenicanpm/portal-types-schemas";
8
+
9
+ // STYLED COMPONENTS
10
+ const StyledTableCell = styled(TableCell)(({ theme }) => ({
11
+ fontWeight: 600,
12
+ padding: "16px 20px",
13
+ color: theme.palette.grey[900],
14
+ }));
15
+
16
+ interface TableHeaderProps {
17
+ hasActions: boolean;
18
+ columns: (Column & { ColumnOrder: number })[] | undefined;
19
+ loading: boolean;
20
+ }
21
+
22
+ /**
23
+ *
24
+ * Table handles display and caches it's own data.
25
+ *
26
+ */
27
+ export default function TableHeader({
28
+ hasActions,
29
+ columns,
30
+ loading,
31
+ }: Readonly<TableHeaderProps>) {
32
+ if (loading) {
33
+ return (
34
+ <TableHead>
35
+ <TableRow>
36
+ {Array.from({ length: 4 })?.map((_col, i) => (
37
+ <StyledTableCell key={String(`skeleton-${i + 1}`)}>
38
+ <Skeleton
39
+ variant="rounded"
40
+ width={100}
41
+ height={25}
42
+ sx={{ bgcolor: "rgba(255,255,255,0.1)" }}
43
+ />
44
+ </StyledTableCell>
45
+ ))}
46
+ </TableRow>
47
+ </TableHead>
48
+ );
49
+ }
50
+ return (
51
+ <TableHead>
52
+ <TableRow>
53
+ {columns?.map((column, i) => (
54
+ <StyledTableCell key={column.Label + String(i)}>
55
+ {column.Label}
56
+ </StyledTableCell>
57
+ ))}
58
+ {hasActions && <StyledTableCell>Actions</StyledTableCell>}
59
+ </TableRow>
60
+ </TableHead>
61
+ );
62
+ }
@@ -0,0 +1,94 @@
1
+ import type { Column } from "@evenicanpm/portal-types-schemas";
2
+ import { SearchInput } from "@evenicanpm/ui";
3
+ import { MenuItem, Select } from "@mui/material";
4
+ import React from "react";
5
+
6
+ interface SearchProps {
7
+ searchTerm: string;
8
+ setSearchTerm: React.Dispatch<React.SetStateAction<string>>;
9
+ setColumn: React.Dispatch<React.SetStateAction<string>>;
10
+ selectedColumn: string;
11
+ searchCols: Column[];
12
+ }
13
+
14
+ const ColumnSelect = ({
15
+ selected,
16
+ handleChange,
17
+ searchCols,
18
+ }: {
19
+ selected: string;
20
+ handleChange: React.Dispatch<React.SetStateAction<string>>;
21
+ searchCols: Column[];
22
+ }) => {
23
+ return (
24
+ <Select
25
+ id="demo-simple-select"
26
+ value={selected}
27
+ sx={{
28
+ maxWidth: "150px",
29
+ "& .MuiOutlinedInput-notchedOutline": { border: "none" },
30
+ "& .MuiSelect-select": {
31
+ paddingTop: 0,
32
+ paddingBottom: 0,
33
+ display: "flex",
34
+ alignItems: "center",
35
+ },
36
+ "&:hover": { bgcolor: "rgba(0, 0, 0, 0.04)" }, // Subtle hover state
37
+ }}
38
+ onChange={(e) => handleChange(e.target.value)}
39
+ >
40
+ {searchCols?.map((col) => (
41
+ <MenuItem key={col?.Data} value={col?.Data}>
42
+ {col?.Label}
43
+ </MenuItem>
44
+ ))}
45
+ </Select>
46
+ );
47
+ };
48
+
49
+ const SearchField = React.memo(
50
+ ({
51
+ searchTerm,
52
+ setSearchTerm,
53
+ searchCols,
54
+ selectedColumn,
55
+ setColumn,
56
+ }: SearchProps) => {
57
+ return (
58
+ <SearchInput
59
+ sx={{
60
+ width: "100%",
61
+ pr: 0,
62
+ boderRight: "none",
63
+ "& .MuiOutlinedInput-notchedOutline": {
64
+ borderRight: "none",
65
+ borderTopRightRadius: 0,
66
+ borderBottomRightRadius: 0,
67
+ },
68
+ // Target the hover state too, otherwise the thick line reappears on hover
69
+ "&:hover .MuiOutlinedInput-notchedOutline": {
70
+ borderRight: "none",
71
+ },
72
+ // Target focus state
73
+ "&.Mui-focused .MuiOutlinedInput-notchedOutline": {
74
+ borderRight: "none",
75
+ },
76
+ }}
77
+ value={searchTerm}
78
+ onChange={(e) => {
79
+ setSearchTerm(e.target.value);
80
+ }}
81
+ placeholder={`Seach by ${selectedColumn}`}
82
+ endAdornment={
83
+ <ColumnSelect
84
+ selected={selectedColumn}
85
+ searchCols={searchCols}
86
+ handleChange={setColumn}
87
+ />
88
+ }
89
+ />
90
+ );
91
+ },
92
+ );
93
+
94
+ export { SearchField };
@@ -0,0 +1,39 @@
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";
5
+ import { useMutation } from "@tanstack/react-query";
6
+
7
+ interface ActionButtonProps {
8
+ docId: string;
9
+ action: Action;
10
+ resourceName: string;
11
+ }
12
+
13
+ export function ActionButton({
14
+ action,
15
+ resourceName,
16
+ 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);
23
+
24
+ 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
+ );
39
+ }
@@ -0,0 +1,96 @@
1
+ import TableCell from "@mui/material/TableCell";
2
+ import TableRow from "@mui/material/TableRow";
3
+ import type React from "react";
4
+ 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
+ import { ActionButton } from "./action-button";
9
+
10
+ /**
11
+ * This is what we transorm the column data into for convenient rendering.
12
+ */
13
+ type ColumnLogic =
14
+ | {
15
+ id: string;
16
+ mask: string | undefined;
17
+ maskFn: any;
18
+ cellFn:
19
+ | ((
20
+ value: string | number | string[],
21
+ ) => React.ReactElement<
22
+ any,
23
+ string | React.JSXElementConstructor<any>
24
+ >)
25
+ | ((value: string | number) => string | number);
26
+ }[]
27
+ | undefined;
28
+
29
+ const StyledTableCell = styled(TableCell)({
30
+ height: "75px",
31
+ });
32
+
33
+ export type DocumentTableProps = {
34
+ /** The row to render */
35
+ row: Types.Queries.Row;
36
+ /** The render logic for each column ie data mask functions, cell maps*/
37
+ columnLogic: ColumnLogic;
38
+ /** */
39
+ handleClick: React.Dispatch<React.SetStateAction<string | number | null>>;
40
+ /** Actions renderer */
41
+ actions: Action[];
42
+ /** Does this view config have actions? */
43
+ /** The id field for the document (used for knowing what to set selected id to) */
44
+ idField: string;
45
+ };
46
+
47
+ /**
48
+ * A single row (document) in the table. Takes the pre-constructed column logic
49
+ * and executes all of the cell renders and data map functions. Also handles
50
+ * rendering the action icons.
51
+ */
52
+ export default function DataTableRow({
53
+ row,
54
+ columnLogic,
55
+ handleClick: handleSelectDocument,
56
+ actions,
57
+ idField,
58
+ }: Readonly<DocumentTableProps>) {
59
+ const id = row?.[idField] as string;
60
+ return (
61
+ <TableRow
62
+ hover
63
+ sx={{ cursor: "pointer" }}
64
+ onClick={() => {
65
+ // type narrowing to make sure we don't use a complex object
66
+ // as the id in state
67
+ if (typeof id === "string" || typeof id === "number") {
68
+ handleSelectDocument(id);
69
+ }
70
+ }}
71
+ >
72
+ {columnLogic?.map((col, i) => {
73
+ // --- Cell Render --- //
74
+ const rawValue = row[col.id];
75
+ const maskedValue = col.maskFn(rawValue, col?.mask);
76
+ const cellValue = col.cellFn(maskedValue);
77
+ return (
78
+ <StyledTableCell key={rawValue + String(i)}>
79
+ {cellValue}
80
+ </StyledTableCell>
81
+ );
82
+ })}
83
+ {actions?.map((action) => (
84
+ <TableCell
85
+ key={action.Name}
86
+ onClick={(e) => e.stopPropagation()}
87
+ sx={{ whiteSpace: "nowrap" }}
88
+ >
89
+ <FlexBox justifyContent="flex-start" gap={1}>
90
+ <ActionButton docId={id} action={action} resourceName={"invoice"} />
91
+ </FlexBox>
92
+ </TableCell>
93
+ ))}
94
+ </TableRow>
95
+ );
96
+ }
@@ -0,0 +1,36 @@
1
+ import { Skeleton } from "@mui/material";
2
+ import TableCell from "@mui/material/TableCell";
3
+ import TableRow from "@mui/material/TableRow";
4
+ import type React from "react";
5
+ import { styled } from "@mui/system";
6
+
7
+ const StyledTableCell = styled(TableCell)({
8
+ height: "75px",
9
+ });
10
+
11
+ export type TableRowSkeletonProps = {
12
+ pageSize: number;
13
+ colSpan: number;
14
+ };
15
+
16
+ export default function TableRowSkeleton({
17
+ pageSize,
18
+ colSpan,
19
+ }: Readonly<TableRowSkeletonProps>) {
20
+ const rowKeys = Array.from({ length: pageSize }, (_, i) => `row-${i}`);
21
+ const cellKeys = Array.from({ length: colSpan }, (_, i) => `cell-${i}`);
22
+
23
+ return (
24
+ <>
25
+ {rowKeys.map((rowKey) => (
26
+ <TableRow key={rowKey}>
27
+ {cellKeys.map((cellKey) => (
28
+ <StyledTableCell key={`${rowKey}-${cellKey}`}>
29
+ <Skeleton />
30
+ </StyledTableCell>
31
+ ))}
32
+ </TableRow>
33
+ ))}
34
+ </>
35
+ );
36
+ }
@@ -0,0 +1,250 @@
1
+ import { FlexBox, ResponsiveTablePagination } from "@evenicanpm/ui";
2
+ import Table from "@mui/material/Table";
3
+ import TableBody from "@mui/material/TableBody";
4
+ import TableCell from "@mui/material/TableCell";
5
+ import TableContainer from "@mui/material/TableContainer";
6
+ import TableRow from "@mui/material/TableRow";
7
+ import { type QueryKey, useQuery } from "@tanstack/react-query";
8
+ import type React from "react";
9
+ import { useState } from "react";
10
+ import { resolvers, transformers } from "../../../logic";
11
+ import type * as Types from "../../../types";
12
+ import { dataMaskMap as dataMaskMapLocal } from "./data-mask-map";
13
+ import { Details } from "./details";
14
+ import { filterRenderMapFallback } from "./filter-render-map";
15
+ import { NoResultsComponent as NoResultsComponentLocal } from "./no-results";
16
+ import { StatusPill } from "./status-pill";
17
+ import TableHeader from "./table-header";
18
+ import { SearchField } from "./table-input";
19
+ import DataTableRow from "./table-row/table-row";
20
+ import TableRowSkeleton from "./table-row-skeleton";
21
+
22
+ export type DocumentTableProps = {
23
+ resource: Types.Schemas.Resource;
24
+ pageSize: number;
25
+ setPageSize: React.Dispatch<React.SetStateAction<number>>;
26
+ viewConfigQuery: (resourceName: string) => {
27
+ queryKey: QueryKey;
28
+ queryFn: () => Promise<Types.Queries.TableConfig>;
29
+ };
30
+ };
31
+
32
+ /**
33
+ *
34
+ * Table handles display and caches it's own data.
35
+ *
36
+ */
37
+ export default function DocumentTable({
38
+ resource,
39
+ pageSize,
40
+ setPageSize,
41
+ viewConfigQuery,
42
+ }: Readonly<DocumentTableProps>) {
43
+ const [page, setPage] = useState(0);
44
+ const [selectedDocument, setSelectedDocument] = useState<
45
+ string | number | null
46
+ >(null);
47
+ const [filters, setFilters] = useState<Record<string, string>>({});
48
+ const [searchTerm, setSearchTerm] = useState("");
49
+ const [searchColumn, setSearchColumn] = useState("Title");
50
+
51
+ const {
52
+ dataMaskMap,
53
+ cellRenderMap: columnRenderMap,
54
+ filterRenderMap,
55
+ detailsRenderMap,
56
+ idField,
57
+ label,
58
+ noResultsComponent,
59
+ statusColorMap,
60
+ statusTextMap,
61
+ } = resource;
62
+
63
+ /**
64
+ *
65
+ * This is not simply a default map because it passes special props
66
+ * specific to status... Once we have more of these types of
67
+ * functions that return special renders that are not general
68
+ * resolvers we can create a new file.
69
+ *
70
+ * Render would be something like... If no column resolver -> try
71
+ * resolving these as opposed to just overwriting column renders
72
+ */
73
+ const resolvedFilterRenderMap = {
74
+ ...filterRenderMapFallback,
75
+ ...(filterRenderMap ?? {}),
76
+ };
77
+
78
+ const cellRenderMapDefaults = {
79
+ ...columnRenderMap,
80
+ Status: (value: string | number | string[]) => (
81
+ <StatusPill
82
+ status={value}
83
+ statusColorMap={statusColorMap}
84
+ statusTextMap={statusTextMap}
85
+ />
86
+ ),
87
+ };
88
+
89
+ // --- State Handlers --- //
90
+ const handleChangePageSize = (
91
+ event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
92
+ ) => {
93
+ setPageSize(Number.parseInt(event.target.value, 10));
94
+ setPage(0);
95
+ };
96
+
97
+ const handleChangePage = (
98
+ _event: React.MouseEvent<HTMLButtonElement> | null,
99
+ newPage: number,
100
+ ) => {
101
+ setPage(newPage);
102
+ };
103
+
104
+ const handleBack = () => {
105
+ setSelectedDocument(null);
106
+ };
107
+
108
+ const resolvedNoResults = (noResultsComponent ?? NoResultsComponentLocal)({
109
+ label,
110
+ });
111
+
112
+ const resolvedDataMaskMap = dataMaskMap ?? dataMaskMapLocal;
113
+
114
+ /**
115
+ * Data pipeline:
116
+ * FETCH -> TRANFORMER -> RESOLVER
117
+ * Keep the JSX dumb.
118
+ *
119
+ * Tested performance with console.time,
120
+ * calculation rendering around 0.02,
121
+ * no need to memoize yet.
122
+ */
123
+
124
+ // --- 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
+ );
131
+
132
+ const columns = config?.Columns ?? [];
133
+ const actions = config?.Actions ?? [];
134
+ const hasActions = actions.length > 0;
135
+
136
+ // --- Transformer --- //
137
+ const searchableCols = transformers.getSearchableColumns(columns);
138
+ const filterReqs = transformers.getActiveFilters(columns);
139
+ const visibleCols = transformers.getVisibleColumns(columns);
140
+
141
+ // --- Resolver --- //
142
+ const filterLogic = filterReqs?.map((req) => ({
143
+ req,
144
+ FilterComponent: resolvers.resolveFilterUI(req, resolvedFilterRenderMap),
145
+ }));
146
+ const columnLogic = visibleCols?.map((col) => ({
147
+ id: col.Data,
148
+ mask: col?.DataMask,
149
+ maskFn: resolvers.resolveDataMask(col, resolvedDataMaskMap),
150
+ cellFn: resolvers.resolveCellUI(col, cellRenderMapDefaults),
151
+ }));
152
+
153
+ // --- Render --- //
154
+ if (selectedDocument) {
155
+ const selectedDocDetails =
156
+ rows?.data.find((doc) => doc?.[idField] === selectedDocument) || {};
157
+ // Build the logic necessary for rendering
158
+ return (
159
+ <Details
160
+ documentTypeLabel={label}
161
+ id={selectedDocument}
162
+ documentData={selectedDocDetails}
163
+ handleBack={handleBack}
164
+ columns={columns || []}
165
+ dataMaskMap={resolvedDataMaskMap}
166
+ statusColorMap={statusColorMap}
167
+ statusTextMap={statusTextMap}
168
+ detailsRenderMap={detailsRenderMap}
169
+ />
170
+ );
171
+ }
172
+
173
+ // Table body data calculations
174
+ const colSpan = visibleCols?.length ? visibleCols.length + 1 : 4;
175
+ const noResults = !rowsPending && !configPending && rows?.data?.length === 0;
176
+
177
+ return (
178
+ <>
179
+ <h1>{label}</h1>
180
+ <FlexBox
181
+ justifyContent={"flex-end"}
182
+ width="100%"
183
+ height={65}
184
+ gap={2}
185
+ pt={2}
186
+ >
187
+ <SearchField
188
+ searchTerm={searchTerm}
189
+ setSearchTerm={setSearchTerm}
190
+ setColumn={setSearchColumn}
191
+ searchCols={searchableCols || []}
192
+ selectedColumn={searchColumn}
193
+ />
194
+ {filterLogic?.map((filter) => (
195
+ <filter.FilterComponent
196
+ key={filter.req?.filterName}
197
+ value={filters}
198
+ setValue={setFilters}
199
+ resetPagination={() => setPage(0)}
200
+ {...filter.req}
201
+ />
202
+ ))}
203
+ </FlexBox>
204
+ <TableContainer>
205
+ <Table aria-label="simple table" sx={{ tableLayout: { md: "fixed" } }}>
206
+ <TableHeader
207
+ columns={visibleCols}
208
+ loading={configPending}
209
+ hasActions={hasActions}
210
+ />
211
+ <TableBody>
212
+ {noResults ? (
213
+ <TableRow>
214
+ <TableCell
215
+ colSpan={colSpan || 1}
216
+ align="center"
217
+ sx={{ py: 6, color: "text.secondary" }}
218
+ >
219
+ {resolvedNoResults}
220
+ </TableCell>
221
+ </TableRow>
222
+ ) : rowsPending ? (
223
+ <TableRowSkeleton pageSize={pageSize} colSpan={colSpan} />
224
+ ) : (
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
+ ))
235
+ )}
236
+ </TableBody>
237
+ </Table>
238
+ </TableContainer>
239
+ <ResponsiveTablePagination
240
+ count={rows?.items || 0}
241
+ page={page}
242
+ component="div"
243
+ rowsPerPage={pageSize}
244
+ rowsPerPageOptions={[5, 10]}
245
+ onPageChange={handleChangePage}
246
+ onRowsPerPageChange={handleChangePageSize}
247
+ />
248
+ </>
249
+ );
250
+ }
@@ -0,0 +1,71 @@
1
+ import TabContext from "@mui/lab/TabContext";
2
+ import TabList from "@mui/lab/TabList";
3
+ import TabPanel from "@mui/lab/TabPanel";
4
+ import Box from "@mui/material/Box";
5
+ import Tab from "@mui/material/Tab";
6
+ import { type SyntheticEvent, useState } from "react";
7
+ import type * as Types from "../../types";
8
+ import { Table } from "./table";
9
+ import { QueryKey } from "@tanstack/react-query";
10
+
11
+ /**
12
+ * The main props for top-level table entry-point
13
+ *
14
+ *
15
+ */
16
+ export interface TableProps {
17
+ viewConfigQuery: (resourceName: string) => {
18
+ queryKey: QueryKey;
19
+ queryFn: () => Promise<Types.Queries.TableConfig>;
20
+ };
21
+ resources: Types.Schemas.Resource[];
22
+ }
23
+
24
+ /**
25
+ *
26
+ * The TableDashboard is the main view component for the table feature.
27
+ * Holds state for the tabs and global pagination for all tables.
28
+ *
29
+ * @params props
30
+ */
31
+ export const TableDashboard = ({ resources, viewConfigQuery }: TableProps) => {
32
+ const [value, setValue] = useState(resources[0]?.name);
33
+ const [pageSize, setPageSize] = useState(5); // better if this is global to not reset when switching tables
34
+
35
+ const handleChange = (_event: SyntheticEvent, newValue: string) => {
36
+ setValue(newValue);
37
+ };
38
+
39
+ return (
40
+ <Box sx={{ width: "100%", minHeight: "70vh" }}>
41
+ <TabContext value={value}>
42
+ <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
43
+ <TabList onChange={handleChange}>
44
+ {resources?.map((resource, i) => (
45
+ <Tab
46
+ key={resource?.name + String(i)}
47
+ label={resource?.label}
48
+ value={resource?.name}
49
+ />
50
+ ))}
51
+ </TabList>
52
+ </Box>
53
+ {resources?.map((resource, i) => (
54
+ <Box key={resource.name + String(i)}>
55
+ <TabPanel
56
+ value={resource?.name}
57
+ sx={{ padding: "25px 0", margin: 0 }}
58
+ >
59
+ <Table
60
+ viewConfigQuery={viewConfigQuery}
61
+ resource={resource}
62
+ pageSize={pageSize}
63
+ setPageSize={setPageSize}
64
+ />
65
+ </TabPanel>
66
+ </Box>
67
+ ))}
68
+ </TabContext>
69
+ </Box>
70
+ );
71
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @author Sam Forderer
3
+
4
+ * @packageDocumentation
5
+ */
6
+
7
+ export type { DocumentTableProps } from "./components/dashboard/table/table";
8
+ export {
9
+ TableDashboard,
10
+ type TableProps,
11
+ } from "./components/dashboard/table-dashboard";
12
+
13
+ // SCHEMAS
14
+ export * as Table from "./types";
@@ -0,0 +1,23 @@
1
+ /**
2
+ *
3
+ * Purpose of resolvers is to resolve the handlers
4
+ * (filters, datamasks, and cell renders) these
5
+ * resolvers allow multiple ways of calling handlers
6
+ *
7
+ * eg:
8
+ * datamasks -> 1. column name
9
+ * 2. datatype
10
+ *
11
+ * {
12
+ * Status: () => value
13
+ * DateTime: () => value
14
+ * }
15
+ *
16
+ * Keeps system flexible
17
+ *
18
+ * Row Value -> Resolver -> UI
19
+ *
20
+ * @packageDocumentation
21
+ */
22
+ export * as resolvers from "./resolvers";
23
+ export * as transformers from "./transformers";