@evenicanpm/portal-table-ui 1.5.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 +6 -5
- package/src/auth/auth-context.tsx +92 -0
- package/src/features/table/components/dashboard/table/bulk-action-button.tsx +77 -0
- package/src/features/table/components/dashboard/table/data-mask-map.ts +26 -0
- package/src/features/table/components/dashboard/table/details/details.tsx +185 -0
- package/src/features/table/components/dashboard/table/details/label.tsx +26 -0
- package/src/features/table/components/dashboard/table/details/rows/downloads.tsx +25 -0
- package/src/features/table/components/dashboard/table/details/rows/primitive.tsx +25 -0
- package/src/features/table/components/dashboard/table/details/rows/table.tsx +73 -0
- package/{dist/features/table/components/dashboard/table/details/styles.d.ts → src/features/table/components/dashboard/table/details/styles.ts} +10 -2
- 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 +10 -0
- package/src/features/table/components/dashboard/table/filters/date-filter.tsx +31 -0
- package/src/features/table/components/dashboard/table/filters/status-filter.tsx +36 -0
- package/src/features/table/components/dashboard/table/no-results.tsx +26 -0
- package/src/features/table/components/dashboard/table/status-pill.tsx +18 -0
- package/src/features/table/components/dashboard/table/table-header.tsx +98 -0
- package/src/features/table/components/dashboard/table/table-input.tsx +94 -0
- package/src/features/table/components/dashboard/table/table-row/action-button.tsx +47 -0
- package/src/features/table/components/dashboard/table/table-row/table-row.tsx +144 -0
- package/src/features/table/components/dashboard/table/table-row-skeleton.tsx +35 -0
- package/src/features/table/components/dashboard/table/table.tsx +361 -0
- package/src/features/table/components/dashboard/table/use-select-rows.ts +53 -0
- package/src/features/table/components/dashboard/table-dashboard.tsx +71 -0
- package/{dist/features/table/index.d.ts → src/features/table/index.ts} +7 -1
- package/src/features/table/logic/resolvers.ts +111 -0
- package/src/features/table/logic/transformers.ts +56 -0
- package/src/features/table/logic/types.ts +8 -0
- package/{dist/features/table/types/handlers.d.ts → src/features/table/types/handlers.ts} +30 -12
- package/{dist/features/table/types/index.d.ts → src/features/table/types/index.ts} +1 -0
- package/{dist/features/table/types/queries.d.ts → src/features/table/types/queries.ts} +10 -3
- package/src/features/table/types/schemas.ts +89 -0
- package/{dist/index.d.ts → src/index.ts} +2 -1
- package/src/portal.tsx +68 -0
- package/dist/auth/auth-context.d.ts +0 -36
- package/dist/features/settings/index.d.ts +0 -2
- 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/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/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/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/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/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/schemas.d.ts +0 -31
- package/dist/portal.d.ts +0 -38
- package/dist/tsconfig.tsbuildinfo +0 -1
- /package/{dist/features/table/components/dashboard/index.d.ts → src/features/table/components/dashboard/index.tsx} +0 -0
- /package/{dist/features/table/components/dashboard/table/details/index.d.ts → src/features/table/components/dashboard/table/details/index.ts} +0 -0
- /package/{dist/features/table/components/dashboard/table/details/rows/index.d.ts → src/features/table/components/dashboard/table/details/rows/index.ts} +0 -0
- /package/{dist/features/table/components/dashboard/table/index.d.ts → src/features/table/components/dashboard/table/index.ts} +0 -0
- /package/{dist/features/table/logic/index.d.ts → src/features/table/logic/index.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evenicanpm/portal-table-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"files": [
|
|
19
|
-
"dist"
|
|
19
|
+
"dist",
|
|
20
|
+
"src"
|
|
20
21
|
],
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"@emotion/react": "^11.11.3",
|
|
@@ -40,11 +41,11 @@
|
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@arrows/multimethod": "^2.1.0",
|
|
43
|
-
"@evenicanpm/portal-types-schemas": "^1.
|
|
44
|
-
"@evenicanpm/ui": "^1.
|
|
44
|
+
"@evenicanpm/portal-types-schemas": "^1.7.0",
|
|
45
|
+
"@evenicanpm/ui": "^1.6.0",
|
|
45
46
|
"@mui/lab": "^7.0.1-beta.19",
|
|
46
47
|
"@tanstack/react-query": "^5.90.11",
|
|
47
48
|
"date-fns": "^4.1.0"
|
|
48
49
|
},
|
|
49
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "2e25d32705901a4ff2dd43aa420fde931568ad93"
|
|
50
51
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
type PropsWithChildren,
|
|
4
|
+
useContext,
|
|
5
|
+
useMemo,
|
|
6
|
+
} from "react";
|
|
7
|
+
|
|
8
|
+
// --- 1. Types --- //
|
|
9
|
+
/**
|
|
10
|
+
* Powers the auth provider that portal is wrapped in
|
|
11
|
+
* Aggregates user and session related data that is needed
|
|
12
|
+
* within the portal
|
|
13
|
+
*/
|
|
14
|
+
interface AuthProviderProps {
|
|
15
|
+
/** The user details returned from auth framework eg. next-auth **/
|
|
16
|
+
session: Session;
|
|
17
|
+
/** how to handle logout **/
|
|
18
|
+
logout: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The context value
|
|
23
|
+
*/
|
|
24
|
+
interface AuthContext {
|
|
25
|
+
session: Session | null;
|
|
26
|
+
/** how to handle logout **/
|
|
27
|
+
logout: (() => void) | null;
|
|
28
|
+
isAuthenticated: boolean | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Session details returned from authentication
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
interface Session {
|
|
36
|
+
email: string | null | undefined;
|
|
37
|
+
name: string | null | undefined;
|
|
38
|
+
token: string | null | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// --- 2. Context --- //
|
|
42
|
+
const AuthContext = createContext<AuthContext>({
|
|
43
|
+
session: null,
|
|
44
|
+
logout: null,
|
|
45
|
+
isAuthenticated: null,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// --- 3. Hook --- //
|
|
49
|
+
/**
|
|
50
|
+
* Used to access auth details and callback handlers within the portal auth context
|
|
51
|
+
*/
|
|
52
|
+
const useAuth = () => {
|
|
53
|
+
const context = useContext(AuthContext);
|
|
54
|
+
if (!context) {
|
|
55
|
+
// This error helps developers debug if they forget to wrap the component.
|
|
56
|
+
throw new Error("useAuth must be used within a PortalAuthProvider");
|
|
57
|
+
}
|
|
58
|
+
return context;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// --- Provider --- //
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
*/
|
|
65
|
+
const PortalAuthProvider = ({
|
|
66
|
+
session,
|
|
67
|
+
logout,
|
|
68
|
+
children,
|
|
69
|
+
}: PropsWithChildren<AuthProviderProps>) => {
|
|
70
|
+
const isAuthenticated = !!session?.email;
|
|
71
|
+
|
|
72
|
+
const authValue = useMemo(
|
|
73
|
+
() => ({
|
|
74
|
+
isAuthenticated,
|
|
75
|
+
session,
|
|
76
|
+
logout,
|
|
77
|
+
}),
|
|
78
|
+
[isAuthenticated, session, logout],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<AuthContext.Provider value={authValue}>{children}</AuthContext.Provider>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
PortalAuthProvider,
|
|
88
|
+
useAuth,
|
|
89
|
+
type AuthProviderProps,
|
|
90
|
+
AuthContext,
|
|
91
|
+
type Session,
|
|
92
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Badge, Button, CircularProgress, IconButton } from "@mui/material";
|
|
2
|
+
import { useMutation } from "@tanstack/react-query";
|
|
3
|
+
import type { Row } from "src/features/table/types/queries";
|
|
4
|
+
import type { UIBulkAction } from "src/features/table/types/schemas";
|
|
5
|
+
|
|
6
|
+
interface BulkActionButtonProps {
|
|
7
|
+
action: UIBulkAction;
|
|
8
|
+
resourceName: string;
|
|
9
|
+
selectedIds: (string | number)[];
|
|
10
|
+
clearSelection: () => void;
|
|
11
|
+
selectedRows: Row[];
|
|
12
|
+
}
|
|
13
|
+
export function BulkActionButton({
|
|
14
|
+
action,
|
|
15
|
+
resourceName,
|
|
16
|
+
selectedIds,
|
|
17
|
+
clearSelection,
|
|
18
|
+
selectedRows,
|
|
19
|
+
}: BulkActionButtonProps) {
|
|
20
|
+
const mutationOptions = action.mutation({
|
|
21
|
+
resourceName,
|
|
22
|
+
selectedIds,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const allowed = !action.isAllowed || action.isAllowed(selectedRows);
|
|
26
|
+
const { mutate, isPending } = useMutation(mutationOptions);
|
|
27
|
+
|
|
28
|
+
const handleClick = () => {
|
|
29
|
+
mutate(undefined, {
|
|
30
|
+
onSuccess: () => {
|
|
31
|
+
clearSelection();
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const Icon = action.icon;
|
|
37
|
+
|
|
38
|
+
if (Icon) {
|
|
39
|
+
return (
|
|
40
|
+
<IconButton
|
|
41
|
+
size="small"
|
|
42
|
+
onClick={handleClick}
|
|
43
|
+
disabled={isPending || selectedIds.length === 0 || !allowed}
|
|
44
|
+
sx={{
|
|
45
|
+
border: 1,
|
|
46
|
+
borderRadius: 1,
|
|
47
|
+
paddingInline: 2,
|
|
48
|
+
minHeight: "fit-content",
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
{isPending ? (
|
|
52
|
+
<CircularProgress size={26} />
|
|
53
|
+
) : (
|
|
54
|
+
<Badge badgeContent={selectedIds.length} color="primary">
|
|
55
|
+
<Icon />
|
|
56
|
+
</Badge>
|
|
57
|
+
)}
|
|
58
|
+
</IconButton>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Button
|
|
64
|
+
variant="contained"
|
|
65
|
+
onClick={handleClick}
|
|
66
|
+
size="large"
|
|
67
|
+
sx={{ textWrap: "nowrap", minHeight: "fit-content" }}
|
|
68
|
+
disabled={isPending || selectedIds.length === 0 || !allowed}
|
|
69
|
+
>
|
|
70
|
+
{isPending ? (
|
|
71
|
+
<CircularProgress size={22} />
|
|
72
|
+
) : (
|
|
73
|
+
`${action.label} (${selectedIds.length})`
|
|
74
|
+
)}
|
|
75
|
+
</Button>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { format } from "date-fns";
|
|
3
|
+
import type * as Table from "../../../types/";
|
|
4
|
+
|
|
5
|
+
export const dataMaskMap: Table.Handlers.DataMaskMap = {
|
|
6
|
+
// DATE TIME HAS SPECIFIC MASK HANDLING
|
|
7
|
+
datetime: (value: string | number, _dataMask: string) => {
|
|
8
|
+
return format(new Date(value), "MM/dd/yyyy");
|
|
9
|
+
},
|
|
10
|
+
date: (value: string | number, _dataMask: string) => {
|
|
11
|
+
return format(new Date(value), "MM/dd/yyyy");
|
|
12
|
+
},
|
|
13
|
+
numeric: (value: string | number, dataMask: string) => {
|
|
14
|
+
// IS CURRENCY?
|
|
15
|
+
if (dataMask.includes("$")) {
|
|
16
|
+
// Very generic stub... project fill out.
|
|
17
|
+
return new Intl.NumberFormat("en-US", {
|
|
18
|
+
style: "currency",
|
|
19
|
+
currency: "USD",
|
|
20
|
+
}).format(Number(value));
|
|
21
|
+
}
|
|
22
|
+
// IS ...X?
|
|
23
|
+
// IS ...Y?
|
|
24
|
+
return String(value);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { method, multi } from "@arrows/multimethod";
|
|
3
|
+
import type { Column } from "@evenicanpm/portal-types-schemas";
|
|
4
|
+
import { FlexBox, H6, Subtitle } from "@evenicanpm/ui";
|
|
5
|
+
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
|
6
|
+
import { Box, Button } from "@mui/material";
|
|
7
|
+
import Grid from "@mui/material/Grid2";
|
|
8
|
+
import { resolvers, transformers } from "../../../../logic";
|
|
9
|
+
import type * as Types from "../../../../types";
|
|
10
|
+
import type { StatusColorMap, StatusTextMap } from "../../../../types/schemas";
|
|
11
|
+
import { StatusPill } from "../status-pill";
|
|
12
|
+
import { DetailsRowLabel } from "./label";
|
|
13
|
+
import { DetailsDownloads, DetailsPrimitive, DetailsTable } from "./rows";
|
|
14
|
+
import { detailsTemplateRegistry } from "./templates/template-registry";
|
|
15
|
+
|
|
16
|
+
interface DetailsProps {
|
|
17
|
+
/** What do we do when use clicks back button? */
|
|
18
|
+
handleBack: () => void;
|
|
19
|
+
/** What is the id field name */
|
|
20
|
+
id: string | number;
|
|
21
|
+
/** The actual pojo of data from the API */
|
|
22
|
+
documentData: Types.Queries.Row | unknown;
|
|
23
|
+
/* the label of the document type ie 'INVOICES' */
|
|
24
|
+
documentTypeLabel: string;
|
|
25
|
+
/** Special instructions for how to render certain properties */
|
|
26
|
+
detailsRenderMap?: Types.Handlers.DetailsRenderMap;
|
|
27
|
+
/** Data mask map */
|
|
28
|
+
dataMaskMap: Types.Handlers.DataMaskMap;
|
|
29
|
+
/** Table columns **/
|
|
30
|
+
columns: Column[];
|
|
31
|
+
/** */
|
|
32
|
+
statusColorMap: StatusColorMap;
|
|
33
|
+
/** */
|
|
34
|
+
statusTextMap: StatusTextMap;
|
|
35
|
+
template?: string;
|
|
36
|
+
loading?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The Details Page
|
|
41
|
+
*/
|
|
42
|
+
const Details = ({
|
|
43
|
+
id,
|
|
44
|
+
documentTypeLabel,
|
|
45
|
+
documentData,
|
|
46
|
+
handleBack,
|
|
47
|
+
columns,
|
|
48
|
+
dataMaskMap,
|
|
49
|
+
statusColorMap,
|
|
50
|
+
statusTextMap,
|
|
51
|
+
template,
|
|
52
|
+
loading = false,
|
|
53
|
+
}: DetailsProps) => {
|
|
54
|
+
// Resolve the template component once — undefined when the key is absent or
|
|
55
|
+
// not found in the registry, which gracefully falls back to the generic view.
|
|
56
|
+
const TemplateComponent = template
|
|
57
|
+
? detailsTemplateRegistry[template]
|
|
58
|
+
: undefined;
|
|
59
|
+
|
|
60
|
+
const visibleDetails = transformers.getVisibleDetails(columns);
|
|
61
|
+
|
|
62
|
+
// Pre-compute primitive items only when the generic grid is needed
|
|
63
|
+
const primitiveItems = TemplateComponent
|
|
64
|
+
? undefined
|
|
65
|
+
: visibleDetails?.map((col) => ({
|
|
66
|
+
key: col.Data,
|
|
67
|
+
value: (documentData as Types.Queries.Row)?.[col.Data],
|
|
68
|
+
maskFn: resolvers.resolveDataMask(col, dataMaskMap),
|
|
69
|
+
mask: col.DataMask,
|
|
70
|
+
col,
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Box>
|
|
75
|
+
<Box height={48} sx={{ borderBottom: 1, borderColor: "divider" }}>
|
|
76
|
+
<FlexBox
|
|
77
|
+
alignItems="center"
|
|
78
|
+
justifyContent="space-between"
|
|
79
|
+
mb={2}
|
|
80
|
+
height="100%"
|
|
81
|
+
>
|
|
82
|
+
<Button onClick={handleBack} startIcon={<ArrowBackIcon />}>
|
|
83
|
+
{documentTypeLabel}
|
|
84
|
+
</Button>
|
|
85
|
+
<H6 color="grey.800">
|
|
86
|
+
{documentTypeLabel}-{id}
|
|
87
|
+
</H6>
|
|
88
|
+
<Subtitle>DETAILS</Subtitle>
|
|
89
|
+
</FlexBox>
|
|
90
|
+
</Box>
|
|
91
|
+
|
|
92
|
+
<Box mt={2}>
|
|
93
|
+
{TemplateComponent ? (
|
|
94
|
+
<TemplateComponent
|
|
95
|
+
data={documentData}
|
|
96
|
+
statusColorMap={statusColorMap}
|
|
97
|
+
statusTextMap={statusTextMap}
|
|
98
|
+
loading={loading}
|
|
99
|
+
/>
|
|
100
|
+
) : (
|
|
101
|
+
<Grid container spacing={2}>
|
|
102
|
+
{primitiveItems?.map((col) => {
|
|
103
|
+
const maskedValue = col.maskFn(col.value, col.mask);
|
|
104
|
+
const Component = getRowComponent(maskedValue, col.key);
|
|
105
|
+
const sx = getRowLayout(maskedValue, col.key);
|
|
106
|
+
return (
|
|
107
|
+
<Grid key={col.key} {...sx}>
|
|
108
|
+
<DetailsRowLabel label={col.col.Label} />
|
|
109
|
+
<Component
|
|
110
|
+
value={maskedValue}
|
|
111
|
+
label={col.key}
|
|
112
|
+
column={col.col}
|
|
113
|
+
dataMaskMap={dataMaskMap}
|
|
114
|
+
statusColorMap={statusColorMap}
|
|
115
|
+
statusTextMap={statusTextMap}
|
|
116
|
+
status={col.value}
|
|
117
|
+
/>
|
|
118
|
+
</Grid>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</Grid>
|
|
122
|
+
)}
|
|
123
|
+
</Box>
|
|
124
|
+
</Box>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Default renders.
|
|
130
|
+
*
|
|
131
|
+
* -- What do we render? --
|
|
132
|
+
*
|
|
133
|
+
*/
|
|
134
|
+
const detailsRenders = {
|
|
135
|
+
DOCUMENTS: DetailsDownloads,
|
|
136
|
+
PRIMITIVE: DetailsPrimitive,
|
|
137
|
+
OBJECT: DetailsTable,
|
|
138
|
+
STATUS: StatusPill,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Multi-method to resolve the renders.
|
|
143
|
+
*
|
|
144
|
+
* -- How do we know what to render? --
|
|
145
|
+
*
|
|
146
|
+
*/
|
|
147
|
+
const detailTypeDispatch = (detail: Types.Queries.Row, col: string) => {
|
|
148
|
+
if (col === "Documents") return "DOCUMENTS";
|
|
149
|
+
if (col === "Status") return "STATUS";
|
|
150
|
+
if (typeof detail === "string" || typeof detail === "number")
|
|
151
|
+
return "PRIMITIVE";
|
|
152
|
+
if (Array.isArray(detail)) return "OBJECT";
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Multi-method to resolve the renders.
|
|
157
|
+
*
|
|
158
|
+
* -- what do we render? --
|
|
159
|
+
*
|
|
160
|
+
*/
|
|
161
|
+
const getRowComponent = multi(
|
|
162
|
+
detailTypeDispatch,
|
|
163
|
+
method("PRIMITIVE", () => detailsRenders.PRIMITIVE),
|
|
164
|
+
method("DOCUMENTS", () => detailsRenders.DOCUMENTS),
|
|
165
|
+
method("OBJECT", () => detailsRenders.OBJECT),
|
|
166
|
+
method("STATUS", () => detailsRenders.STATUS),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Multi-method to resolve layout styles
|
|
171
|
+
*
|
|
172
|
+
* -- what do we render? --
|
|
173
|
+
*/
|
|
174
|
+
const getRowLayout = multi(
|
|
175
|
+
detailTypeDispatch,
|
|
176
|
+
method("PRIMITIVE", () => ({ size: 6, display: "flex" })),
|
|
177
|
+
method("STATUS", () => ({ size: 6, display: "flex" })),
|
|
178
|
+
method("DOCUMENTS", () => ({ size: 12 })),
|
|
179
|
+
method("OBJECT", () => ({ size: 12 })),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Note: another way to dispatch table is
|
|
183
|
+
// if the column has a children property
|
|
184
|
+
|
|
185
|
+
export { Details };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Typography } from "@mui/material";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const DetailsRowLabel = (props: Props) => {
|
|
10
|
+
const { label } = props;
|
|
11
|
+
return (
|
|
12
|
+
<Typography
|
|
13
|
+
variant="subtitle2"
|
|
14
|
+
color="text.secondary"
|
|
15
|
+
sx={{
|
|
16
|
+
minWidth: 150,
|
|
17
|
+
fontWeight: 600,
|
|
18
|
+
alignSelf: "flex-start",
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
{label}:
|
|
22
|
+
</Typography>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { DetailsRowLabel };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
|
|
2
|
+
import { Button, Stack } from "@mui/material";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
value: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const DetailsDownloads = ({ value }: Props) => (
|
|
9
|
+
<Stack spacing={1} sx={{ mt: 1 }}>
|
|
10
|
+
{value?.map((doc: string) => (
|
|
11
|
+
<Button
|
|
12
|
+
key={doc}
|
|
13
|
+
variant="outlined"
|
|
14
|
+
size="small"
|
|
15
|
+
startIcon={<FileDownloadOutlinedIcon />}
|
|
16
|
+
onClick={() => window.open(doc, "_blank")}
|
|
17
|
+
sx={{ justifyContent: "flex-start" }}
|
|
18
|
+
>
|
|
19
|
+
Download Document
|
|
20
|
+
</Button>
|
|
21
|
+
))}
|
|
22
|
+
</Stack>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export { DetailsDownloads };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Box } from "@mui/material";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
value: string | number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const DetailsPrimitive = (props: Props) => {
|
|
10
|
+
const { value } = props;
|
|
11
|
+
return (
|
|
12
|
+
<Box
|
|
13
|
+
sx={{
|
|
14
|
+
mb: 2,
|
|
15
|
+
display: "flex",
|
|
16
|
+
alignItems: "flex-start",
|
|
17
|
+
gap: 2,
|
|
18
|
+
}}
|
|
19
|
+
>
|
|
20
|
+
<Box sx={{ flex: 1 }}>{value}</Box>
|
|
21
|
+
</Box>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export { DetailsPrimitive };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { Column } from "@evenicanpm/portal-types-schemas";
|
|
4
|
+
import {
|
|
5
|
+
Table,
|
|
6
|
+
TableBody,
|
|
7
|
+
TableCell,
|
|
8
|
+
TableContainer,
|
|
9
|
+
TableHead,
|
|
10
|
+
TableRow,
|
|
11
|
+
} from "@mui/material";
|
|
12
|
+
import { resolvers } from "../../../../../logic";
|
|
13
|
+
import type { DataMaskMap } from "../../../../../types/handlers";
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
column: Column;
|
|
17
|
+
value: Record<string, string>[];
|
|
18
|
+
dataMaskMap: DataMaskMap;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Simple table that takes an array of records,
|
|
23
|
+
* builds array of columns from first index and then loops
|
|
24
|
+
* builds table values from rest of the indexes.
|
|
25
|
+
* @param props
|
|
26
|
+
* @returns
|
|
27
|
+
*/
|
|
28
|
+
const DetailsTable = (props: Props) => {
|
|
29
|
+
const { value: rows, dataMaskMap, column } = props;
|
|
30
|
+
|
|
31
|
+
// call resolvers ahead of time on each column once
|
|
32
|
+
// then they are pre-resolved and ready to be used
|
|
33
|
+
// in render body.
|
|
34
|
+
const preparedCols = column?.Children?.map((col) => ({
|
|
35
|
+
key: col?.Data,
|
|
36
|
+
maskFn: resolvers.resolveDataMask(col, dataMaskMap),
|
|
37
|
+
mask: col?.DataMask,
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<TableContainer sx={{ my: 2 }}>
|
|
42
|
+
<Table size="small" sx={{ mt: 1, p: 0 }}>
|
|
43
|
+
<TableHead>
|
|
44
|
+
<TableRow>
|
|
45
|
+
{Object.keys(rows[0])?.map((key) => (
|
|
46
|
+
<TableCell key={key} sx={{ fontWeight: 600 }}>
|
|
47
|
+
{key}
|
|
48
|
+
</TableCell>
|
|
49
|
+
))}
|
|
50
|
+
</TableRow>
|
|
51
|
+
</TableHead>
|
|
52
|
+
<TableBody>
|
|
53
|
+
{rows.map((row) => {
|
|
54
|
+
return (
|
|
55
|
+
<TableRow key={row?.key + row?.value}>
|
|
56
|
+
{preparedCols?.map((col) => (
|
|
57
|
+
<TableCell
|
|
58
|
+
key={col?.key + row?.key + row?.value}
|
|
59
|
+
align={"left"}
|
|
60
|
+
>
|
|
61
|
+
{col.maskFn(row?.[col?.key], col.mask)}
|
|
62
|
+
</TableCell>
|
|
63
|
+
))}
|
|
64
|
+
</TableRow>
|
|
65
|
+
);
|
|
66
|
+
})}
|
|
67
|
+
</TableBody>
|
|
68
|
+
</Table>
|
|
69
|
+
</TableContainer>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export { DetailsTable };
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { StyledComponent } from "@emotion/styled";
|
|
2
|
-
import { type BoxProps } from "@mui/material";
|
|
2
|
+
import { Box, type BoxProps } from "@mui/material";
|
|
3
|
+
import { styled } from "@mui/material/styles";
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Explicit type annotation is required here to prevent TS2742.
|
|
5
7
|
* Without it, TypeScript tries to infer complex internal types from nested
|
|
6
8
|
* node_modules (@mui/system), which leads to non-portable type definitions
|
|
7
9
|
* in monorepo or library builds.
|
|
8
10
|
*/
|
|
9
|
-
|
|
11
|
+
const DetailsRowBox: StyledComponent<BoxProps> = styled(Box)({
|
|
12
|
+
mb: 2,
|
|
13
|
+
display: "flex",
|
|
14
|
+
alignItems: "flex-start",
|
|
15
|
+
gap: 2,
|
|
16
|
+
});
|
|
17
|
+
|
|
10
18
|
export { DetailsRowBox };
|