@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.
Files changed (67) hide show
  1. package/package.json +6 -5
  2. package/src/auth/auth-context.tsx +92 -0
  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 +26 -0
  5. package/src/features/table/components/dashboard/table/details/details.tsx +185 -0
  6. package/src/features/table/components/dashboard/table/details/label.tsx +26 -0
  7. package/src/features/table/components/dashboard/table/details/rows/downloads.tsx +25 -0
  8. package/src/features/table/components/dashboard/table/details/rows/primitive.tsx +25 -0
  9. package/src/features/table/components/dashboard/table/details/rows/table.tsx +73 -0
  10. package/{dist/features/table/components/dashboard/table/details/styles.d.ts → src/features/table/components/dashboard/table/details/styles.ts} +10 -2
  11. package/src/features/table/components/dashboard/table/details/templates/invoice-details.tsx +407 -0
  12. package/src/features/table/components/dashboard/table/details/templates/template-registry.ts +36 -0
  13. package/src/features/table/components/dashboard/table/filter-render-map.tsx +10 -0
  14. package/src/features/table/components/dashboard/table/filters/date-filter.tsx +31 -0
  15. package/src/features/table/components/dashboard/table/filters/status-filter.tsx +36 -0
  16. package/src/features/table/components/dashboard/table/no-results.tsx +26 -0
  17. package/src/features/table/components/dashboard/table/status-pill.tsx +18 -0
  18. package/src/features/table/components/dashboard/table/table-header.tsx +98 -0
  19. package/src/features/table/components/dashboard/table/table-input.tsx +94 -0
  20. package/src/features/table/components/dashboard/table/table-row/action-button.tsx +47 -0
  21. package/src/features/table/components/dashboard/table/table-row/table-row.tsx +144 -0
  22. package/src/features/table/components/dashboard/table/table-row-skeleton.tsx +35 -0
  23. package/src/features/table/components/dashboard/table/table.tsx +361 -0
  24. package/src/features/table/components/dashboard/table/use-select-rows.ts +53 -0
  25. package/src/features/table/components/dashboard/table-dashboard.tsx +71 -0
  26. package/{dist/features/table/index.d.ts → src/features/table/index.ts} +7 -1
  27. package/src/features/table/logic/resolvers.ts +111 -0
  28. package/src/features/table/logic/transformers.ts +56 -0
  29. package/src/features/table/logic/types.ts +8 -0
  30. package/{dist/features/table/types/handlers.d.ts → src/features/table/types/handlers.ts} +30 -12
  31. package/{dist/features/table/types/index.d.ts → src/features/table/types/index.ts} +1 -0
  32. package/{dist/features/table/types/queries.d.ts → src/features/table/types/queries.ts} +10 -3
  33. package/src/features/table/types/schemas.ts +89 -0
  34. package/{dist/index.d.ts → src/index.ts} +2 -1
  35. package/src/portal.tsx +68 -0
  36. package/dist/auth/auth-context.d.ts +0 -36
  37. package/dist/features/settings/index.d.ts +0 -2
  38. package/dist/features/table/components/dashboard/table/actions.d.ts +0 -14
  39. package/dist/features/table/components/dashboard/table/data-mask-map.d.ts +0 -2
  40. package/dist/features/table/components/dashboard/table/details/details.d.ts +0 -27
  41. package/dist/features/table/components/dashboard/table/details/label.d.ts +0 -5
  42. package/dist/features/table/components/dashboard/table/details/rows/downloads.d.ts +0 -6
  43. package/dist/features/table/components/dashboard/table/details/rows/primitive.d.ts +0 -6
  44. package/dist/features/table/components/dashboard/table/details/rows/table.d.ts +0 -17
  45. package/dist/features/table/components/dashboard/table/filter-render-map.d.ts +0 -2
  46. package/dist/features/table/components/dashboard/table/filters/date-filter.d.ts +0 -2
  47. package/dist/features/table/components/dashboard/table/filters/status-filter.d.ts +0 -2
  48. package/dist/features/table/components/dashboard/table/no-results.d.ts +0 -2
  49. package/dist/features/table/components/dashboard/table/status-pill.d.ts +0 -8
  50. package/dist/features/table/components/dashboard/table/table-header.d.ts +0 -16
  51. package/dist/features/table/components/dashboard/table/table-input.d.ts +0 -11
  52. package/dist/features/table/components/dashboard/table/table-row/action-button.d.ts +0 -8
  53. package/dist/features/table/components/dashboard/table/table-row/table-row.d.ts +0 -32
  54. package/dist/features/table/components/dashboard/table/table-row-skeleton.d.ts +0 -6
  55. package/dist/features/table/components/dashboard/table/table.d.ts +0 -18
  56. package/dist/features/table/components/dashboard/table-dashboard.d.ts +0 -22
  57. package/dist/features/table/logic/resolvers.d.ts +0 -21
  58. package/dist/features/table/logic/transformers.d.ts +0 -35
  59. package/dist/features/table/logic/types.d.ts +0 -8
  60. package/dist/features/table/types/schemas.d.ts +0 -31
  61. package/dist/portal.d.ts +0 -38
  62. package/dist/tsconfig.tsbuildinfo +0 -1
  63. /package/{dist/features/table/components/dashboard/index.d.ts → src/features/table/components/dashboard/index.tsx} +0 -0
  64. /package/{dist/features/table/components/dashboard/table/details/index.d.ts → src/features/table/components/dashboard/table/details/index.ts} +0 -0
  65. /package/{dist/features/table/components/dashboard/table/details/rows/index.d.ts → src/features/table/components/dashboard/table/details/rows/index.ts} +0 -0
  66. /package/{dist/features/table/components/dashboard/table/index.d.ts → src/features/table/components/dashboard/table/index.ts} +0 -0
  67. /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.5.0",
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.5.0",
44
- "@evenicanpm/ui": "^1.4.0",
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": "5c5e3fe19b92a17cbaf897b4cee20ee59331603f"
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
- declare const DetailsRowBox: StyledComponent<BoxProps>;
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 };