@hitachivantara/uikit-cli 6.0.1

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 (109) hide show
  1. package/README.md +46 -0
  2. package/package.json +68 -0
  3. package/src/app-shell.js +106 -0
  4. package/src/baselines/app-shell/vite/_gitignore +30 -0
  5. package/src/baselines/app-shell/vite/_oxlintrc.json +5 -0
  6. package/src/baselines/app-shell/vite/_package.json +55 -0
  7. package/src/baselines/app-shell/vite/public/locales/en/example.json +8 -0
  8. package/src/baselines/app-shell/vite/src/lib/data/config.ts +15 -0
  9. package/src/baselines/app-shell/vite/src/lib/i18n.ts +44 -0
  10. package/src/baselines/app-shell/vite/src/pages/Example/index.tsx +25 -0
  11. package/src/baselines/app-shell/vite/src/providers/Provider.tsx +31 -0
  12. package/src/baselines/app-shell/vite/src/tests/mocks.ts +1 -0
  13. package/src/baselines/app-shell/vite/src/tests/providers.tsx +13 -0
  14. package/src/baselines/app-shell/vite/src/tests/setupTests.ts +24 -0
  15. package/src/baselines/app-shell/vite/src/types/theme.d.ts +8 -0
  16. package/src/baselines/app-shell/vite/src/types/vite-env.d.ts +1 -0
  17. package/src/baselines/app-shell/vite/tsconfig.json +10 -0
  18. package/src/baselines/app-shell/vite/tsconfig.node.json +9 -0
  19. package/src/baselines/app-shell/vite/uno.config.ts +6 -0
  20. package/src/baselines/app-shell/vite/vite.config.ts +45 -0
  21. package/src/baselines/vite/_gitignore +30 -0
  22. package/src/baselines/vite/_oxlintrc.json +5 -0
  23. package/src/baselines/vite/_package.json +53 -0
  24. package/src/baselines/vite/index.html +18 -0
  25. package/src/baselines/vite/public/favicon.ico +0 -0
  26. package/src/baselines/vite/public/locales/en/common.json +16 -0
  27. package/src/baselines/vite/public/locales/en/home.json +4 -0
  28. package/src/baselines/vite/public/logo192.png +0 -0
  29. package/src/baselines/vite/src/App.tsx +31 -0
  30. package/src/baselines/vite/src/assets/HitachiLogo.tsx +27 -0
  31. package/src/baselines/vite/src/components/common/Loading/Loading.test.tsx +18 -0
  32. package/src/baselines/vite/src/components/common/Loading/Loading.tsx +15 -0
  33. package/src/baselines/vite/src/components/common/Loading/index.ts +1 -0
  34. package/src/baselines/vite/src/context/NavigationContext.tsx +67 -0
  35. package/src/baselines/vite/src/lib/i18n.ts +29 -0
  36. package/src/baselines/vite/src/main.tsx +12 -0
  37. package/src/baselines/vite/src/pages/Home/index.tsx +13 -0
  38. package/src/baselines/vite/src/pages/NotFound/NotFound.tsx +39 -0
  39. package/src/baselines/vite/src/pages/NotFound/index.tsx +1 -0
  40. package/src/baselines/vite/src/pages/layout/navigation.tsx +82 -0
  41. package/src/baselines/vite/src/routes.tsx +14 -0
  42. package/src/baselines/vite/src/tests/mocks.ts +1 -0
  43. package/src/baselines/vite/src/tests/providers.tsx +13 -0
  44. package/src/baselines/vite/src/tests/setupTests.ts +24 -0
  45. package/src/baselines/vite/src/types/theme.d.ts +8 -0
  46. package/src/baselines/vite/src/vite-env.d.ts +1 -0
  47. package/src/baselines/vite/tsconfig.json +10 -0
  48. package/src/baselines/vite/tsconfig.node.json +9 -0
  49. package/src/baselines/vite/uno.config.ts +6 -0
  50. package/src/baselines/vite/vite.config.ts +31 -0
  51. package/src/contents.js +63 -0
  52. package/src/create.js +172 -0
  53. package/src/index.js +22 -0
  54. package/src/navigation.js +21 -0
  55. package/src/package.js +37 -0
  56. package/src/plop-templates/README.md.hbs +10 -0
  57. package/src/plop-templates/app-shell/app-shell.config.ts.hbs +54 -0
  58. package/src/plop-templates/app-shell/index.html.hbs +15 -0
  59. package/src/plopfile.js +61 -0
  60. package/src/templates/AssetInventory/CardView.tsx +167 -0
  61. package/src/templates/AssetInventory/ListView.tsx +56 -0
  62. package/src/templates/AssetInventory/data.tsx +255 -0
  63. package/src/templates/AssetInventory/index.tsx +198 -0
  64. package/src/templates/AssetInventory/usePaginationData.ts +158 -0
  65. package/src/templates/Canvas/Context.tsx +49 -0
  66. package/src/templates/Canvas/ListView.tsx +189 -0
  67. package/src/templates/Canvas/Node.tsx +203 -0
  68. package/src/templates/Canvas/Sidebar.tsx +51 -0
  69. package/src/templates/Canvas/StatusEdge.tsx +75 -0
  70. package/src/templates/Canvas/StickyNode.tsx +475 -0
  71. package/src/templates/Canvas/Table.tsx +202 -0
  72. package/src/templates/Canvas/TreeView.tsx +211 -0
  73. package/src/templates/Canvas/dependencies.json +7 -0
  74. package/src/templates/Canvas/index.tsx +363 -0
  75. package/src/templates/Canvas/styles.tsx +41 -0
  76. package/src/templates/Canvas/utils.tsx +70 -0
  77. package/src/templates/Dashboard/GridPanel.tsx +33 -0
  78. package/src/templates/Dashboard/Kpi.tsx +107 -0
  79. package/src/templates/Dashboard/Map.styles.ts +681 -0
  80. package/src/templates/Dashboard/Map.tsx +71 -0
  81. package/src/templates/Dashboard/data.ts +67 -0
  82. package/src/templates/Dashboard/dependencies.json +11 -0
  83. package/src/templates/Dashboard/index.tsx +173 -0
  84. package/src/templates/DetailsView/KPIs.tsx +70 -0
  85. package/src/templates/DetailsView/MetadataItem.tsx +35 -0
  86. package/src/templates/DetailsView/Properties.tsx +127 -0
  87. package/src/templates/DetailsView/Table.tsx +104 -0
  88. package/src/templates/DetailsView/data.ts +67 -0
  89. package/src/templates/DetailsView/index.tsx +102 -0
  90. package/src/templates/DetailsView/usePaginationData.ts +155 -0
  91. package/src/templates/DetailsView/utils.ts +51 -0
  92. package/src/templates/Form/index.tsx +107 -0
  93. package/src/templates/KanbanBoard/ColumnContainer.tsx +89 -0
  94. package/src/templates/KanbanBoard/TaskCard.tsx +130 -0
  95. package/src/templates/KanbanBoard/data.tsx +140 -0
  96. package/src/templates/KanbanBoard/dependencies.json +6 -0
  97. package/src/templates/KanbanBoard/index.tsx +179 -0
  98. package/src/templates/KanbanBoard/styles.tsx +76 -0
  99. package/src/templates/KanbanBoard/types.ts +21 -0
  100. package/src/templates/ListView/Indicator.tsx +42 -0
  101. package/src/templates/ListView/Kpi.tsx +120 -0
  102. package/src/templates/ListView/Table.tsx +55 -0
  103. package/src/templates/ListView/data.tsx +179 -0
  104. package/src/templates/ListView/dependencies.json +5 -0
  105. package/src/templates/ListView/index.tsx +245 -0
  106. package/src/templates/ListView/usePaginationData.ts +158 -0
  107. package/src/templates/Welcome/index.tsx +101 -0
  108. package/src/templates/package.json +30 -0
  109. package/src/utils.js +37 -0
@@ -0,0 +1,255 @@
1
+ import {
2
+ HvBulkActionsProps,
3
+ HvColor,
4
+ HvControlsProps,
5
+ HvRightControlProps,
6
+ HvSkeleton,
7
+ HvTableColumnConfig,
8
+ HvTooltip,
9
+ } from "@hitachivantara/uikit-react-core";
10
+ import {
11
+ Add,
12
+ Cards,
13
+ Delete,
14
+ Level0Good,
15
+ Level1,
16
+ Level2Average,
17
+ Level3Bad,
18
+ List,
19
+ Preview,
20
+ } from "@hitachivantara/uikit-react-icons";
21
+
22
+ import {
23
+ DataObject,
24
+ ServerPaginationProps,
25
+ useServerPagination,
26
+ } from "./usePaginationData";
27
+
28
+ // --- Table data utils ---
29
+
30
+ export interface AssetInventoryEntry extends DataObject {
31
+ id?: string;
32
+ name?: string;
33
+ eventType?: string;
34
+ status?: string;
35
+ severity?: string;
36
+ priority?: string;
37
+ time?: string;
38
+ temperature?: string;
39
+ statusColor?: HvColor;
40
+ image?: string;
41
+ }
42
+
43
+ const images = [
44
+ "https://images.unsplash.com/photo-1513828583688-c52646db42da?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=870&q=80",
45
+ "https://images.unsplash.com/photo-1589320011103-48e428abcbae?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=870&q=80",
46
+ "https://images.unsplash.com/photo-1567789884554-0b844b597180?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=870&q=80",
47
+ "https://images.unsplash.com/photo-1647427060118-4911c9821b82?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=870&q=80",
48
+ "https://images.unsplash.com/photo-1612685180313-bdfe1d6896cd?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1190&q=80",
49
+ "https://images.unsplash.com/photo-1566930665082-4ae9dbbb5b6b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=873&q=80",
50
+ "https://images.unsplash.com/photo-1513828742140-ccaa28f3eda0?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=870&q=80",
51
+ "https://images.unsplash.com/photo-1622534376374-fe4480328daa?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=774&q=80",
52
+ "https://images.unsplash.com/photo-1618840626133-54463084a141?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=872&q=80",
53
+ "https://images.unsplash.com/photo-1600715502630-c9300abe78a3?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=870&q=80",
54
+ ];
55
+
56
+ export const getStatusIcon = (
57
+ color?: AssetInventoryEntry["statusColor"] | string,
58
+ ) => {
59
+ switch (color) {
60
+ case "positive":
61
+ return <Level0Good color="positive" />;
62
+ case "warning":
63
+ return <Level2Average color="warning" />;
64
+ case "negative":
65
+ return <Level3Bad color="negative" />;
66
+ case "info":
67
+ default:
68
+ return <Level1 color="info" />;
69
+ }
70
+ };
71
+
72
+ const getStatusMessage = (status?: AssetInventoryEntry["statusColor"]) => {
73
+ switch (status) {
74
+ case "positive":
75
+ return "Success";
76
+ case "warning":
77
+ return "Open";
78
+ case "negative":
79
+ return "Error";
80
+ case "info":
81
+ default:
82
+ return "Unassigned";
83
+ }
84
+ };
85
+
86
+ export const getColumns = (
87
+ loading?: boolean,
88
+ ): HvTableColumnConfig<AssetInventoryEntry, string>[] => [
89
+ {
90
+ Header: "Status",
91
+ accessor: "statusColor",
92
+ style: { width: 60 },
93
+ Cell: ({ value }: { value: any }) =>
94
+ loading ? (
95
+ <HvSkeleton width={32} height={32} variant="circle" animation="wave" />
96
+ ) : (
97
+ <HvTooltip title={getStatusMessage(value)}>
98
+ {getStatusIcon(value)}
99
+ </HvTooltip>
100
+ ),
101
+ },
102
+ {
103
+ Header: "Asset",
104
+ accessor: "image",
105
+ style: { maxWidth: 60 },
106
+ Cell: ({ value, row }: { value: any; row: any }) => {
107
+ return loading ? (
108
+ <HvSkeleton width={60} height={40} variant="square" animation="wave" />
109
+ ) : (
110
+ <HvTooltip
111
+ placement="right"
112
+ title={
113
+ <div style={{ width: 400 }}>
114
+ <img alt={row.original.name} src={value} />
115
+ </div>
116
+ }
117
+ >
118
+ <div style={{ maxWidth: 60 }}>
119
+ <img alt={row.original.name} src={value} />
120
+ </div>
121
+ </HvTooltip>
122
+ );
123
+ },
124
+ },
125
+ {
126
+ Header: "Title",
127
+ accessor: "name",
128
+ style: { minWidth: 140 },
129
+ Cell: ({ value }: { value: any }) => {
130
+ return loading ? <HvSkeleton animation="wave" /> : <span>{value}</span>;
131
+ },
132
+ },
133
+ {
134
+ Header: "Event Type",
135
+ accessor: "eventType",
136
+ style: { minWidth: 100 },
137
+ Cell: ({ value }: { value: any }) => {
138
+ return loading ? <HvSkeleton animation="wave" /> : <span>{value}</span>;
139
+ },
140
+ },
141
+ {
142
+ Header: "Severity",
143
+ accessor: "severity",
144
+ Cell: ({ value }: { value: any }) => {
145
+ return loading ? <HvSkeleton animation="wave" /> : <span>{value}</span>;
146
+ },
147
+ },
148
+ {
149
+ Header: "Priority",
150
+ accessor: "priority",
151
+ Cell: ({ value }: { value: any }) => {
152
+ return loading ? <HvSkeleton animation="wave" /> : <span>{value}</span>;
153
+ },
154
+ },
155
+ {
156
+ Header: "Time",
157
+ accessor: "time",
158
+ Cell: ({ value }: { value: any }) => {
159
+ return loading ? <HvSkeleton animation="wave" /> : <span>{value}</span>;
160
+ },
161
+ },
162
+ {
163
+ Header: "Temperature",
164
+ accessor: "temperature",
165
+ Cell: ({ value }: { value: any }) => {
166
+ return loading ? <HvSkeleton animation="wave" /> : <span>{value}</span>;
167
+ },
168
+ },
169
+ ];
170
+
171
+ export const actions: HvBulkActionsProps["actions"] = [
172
+ { id: "add", label: "Add", icon: <Add /> },
173
+ { id: "delete", label: "Delete", icon: <Delete /> },
174
+ { id: "put", label: "Preview", icon: <Preview /> },
175
+ ];
176
+
177
+ export const views: HvControlsProps["views"] = [
178
+ { id: "card", "aria-label": "Select card view", icon: <Cards /> },
179
+ { id: "list", "aria-label": "Select list view", icon: <List /> },
180
+ ];
181
+
182
+ export const rightControlValues: HvRightControlProps["values"] = [
183
+ {
184
+ id: "nameAsc",
185
+ accessor: "name",
186
+ label: "Name Ascending",
187
+ desc: false,
188
+ },
189
+ {
190
+ id: "nameDesc",
191
+ accessor: "name",
192
+ label: "Name Descending",
193
+ desc: true,
194
+ },
195
+ {
196
+ id: "eventTypeAsc",
197
+ accessor: "eventType",
198
+ label: "Event Type Ascending",
199
+ desc: false,
200
+ },
201
+ {
202
+ id: "eventTypeDesc",
203
+ accessor: "eventType",
204
+ label: "Event Type Descending",
205
+ desc: true,
206
+ },
207
+ {
208
+ id: "severityAsc",
209
+ accessor: "severity",
210
+ label: "Severity Ascending",
211
+ desc: false,
212
+ },
213
+ {
214
+ id: "severityDesc",
215
+ accessor: "severity",
216
+ label: "Severity Descending",
217
+ desc: true,
218
+ },
219
+ ];
220
+
221
+ const getOption = <T,>(opts: T[], i: number) => opts[i % opts.length];
222
+
223
+ const getTime = (priority: string, index: number) => {
224
+ let i = priority === "High" ? index + 4 : index + 3;
225
+ i = priority === "Medium" ? i + 30 : index + 20;
226
+
227
+ return `${i % 12}:${i % 60}:${i % 60}`;
228
+ };
229
+
230
+ const getPriority = (i: number) => getOption(["High", "Medium", "Low"], i);
231
+
232
+ export const createEntry = (i: number): AssetInventoryEntry => {
233
+ return {
234
+ id: `${i + 1}`,
235
+ name: `Event ${i + 1}`,
236
+ eventType: `Anomaly detection ${i % 4}`,
237
+ status: getOption(["Closed", "Open"], i),
238
+ severity: getOption(["Critical", "Major", "Average", "Minor"], i),
239
+ priority: getPriority(i),
240
+ time: getTime(getPriority(i), i),
241
+ temperature: `${i + 35}º C`,
242
+ statusColor: getOption(["info", "positive", "negative", "warning"], i),
243
+ image: getOption(images, i),
244
+ };
245
+ };
246
+
247
+ // --- Data & Endpoint ---
248
+ const db = [...Array(50).keys()].map(createEntry);
249
+
250
+ export interface PaginationDataProps
251
+ extends Omit<ServerPaginationProps<AssetInventoryEntry>, "endpoint" | "db"> {}
252
+
253
+ export const usePaginationData = (props: PaginationDataProps) => {
254
+ return useServerPagination({ endpoint: "/events", db, ...props });
255
+ };
@@ -0,0 +1,198 @@
1
+ import { useEffect, useId, useMemo, useState } from "react";
2
+ import { css } from "@emotion/css";
3
+ import {
4
+ HvActionsGenericProps,
5
+ HvBulkActions,
6
+ HvControls,
7
+ HvLeftControl,
8
+ HvLeftControlProps,
9
+ HvPagination,
10
+ HvRightControl,
11
+ theme,
12
+ useHvBulkActions,
13
+ useHvData,
14
+ useHvGlobalFilter,
15
+ useHvPagination,
16
+ useHvRowSelection,
17
+ useHvSortBy,
18
+ } from "@hitachivantara/uikit-react-core";
19
+
20
+ import { CardView } from "./CardView";
21
+ import {
22
+ actions,
23
+ AssetInventoryEntry,
24
+ getColumns,
25
+ PaginationDataProps,
26
+ rightControlValues,
27
+ usePaginationData,
28
+ views,
29
+ } from "./data";
30
+ import { ListView } from "./ListView";
31
+
32
+ const classes = {
33
+ root: css({
34
+ display: "grid",
35
+ gap: theme.space.sm,
36
+ }),
37
+ };
38
+
39
+ const PAGE_OPTIONS = [6, 12, 18];
40
+
41
+ const AssetInventory = () => {
42
+ const [currentView, setCurrentView] = useState("card");
43
+ const [params, setParams] = useState<PaginationDataProps>({
44
+ limit: PAGE_OPTIONS[0],
45
+ skip: 0,
46
+ });
47
+
48
+ const cardViewId = useId();
49
+ const listViewId = useId();
50
+
51
+ const {
52
+ data: { pages, data },
53
+ loading,
54
+ deleteEntries,
55
+ } = usePaginationData(params);
56
+
57
+ const columns = useMemo(() => getColumns(loading), [loading]);
58
+
59
+ const instance = useHvData<AssetInventoryEntry, string>(
60
+ {
61
+ data,
62
+ columns,
63
+ manualPagination: true,
64
+ manualSortBy: true,
65
+ autoResetPage: false,
66
+ autoResetSortBy: true,
67
+ pageCount: pages,
68
+ initialState: { pageSize: PAGE_OPTIONS[0] },
69
+ },
70
+ useHvGlobalFilter,
71
+ useHvSortBy,
72
+ useHvPagination,
73
+ useHvRowSelection,
74
+ useHvBulkActions,
75
+ );
76
+
77
+ useEffect(() => {
78
+ const { pageSize = PAGE_OPTIONS[0], pageIndex = 0 } = instance.state;
79
+
80
+ setParams((prev) => ({
81
+ ...prev,
82
+ ...(instance.state.sortBy &&
83
+ instance.state.sortBy.length > 0 && {
84
+ order: instance.state.sortBy?.[0].desc ? "desc" : "asc",
85
+ sort: instance.state.sortBy?.[0].id,
86
+ }),
87
+ limit: pageSize,
88
+ skip: pageSize * pageIndex,
89
+ }));
90
+ }, [instance.state]);
91
+
92
+ const bulkActionProps = instance.getHvBulkActionsProps?.();
93
+
94
+ const handleSearch: HvLeftControlProps["onSearch"] = (e, value) => {
95
+ instance.gotoPage?.(0);
96
+ setParams((prev) => ({
97
+ ...prev,
98
+ skip: 0,
99
+ search: value,
100
+ }));
101
+ };
102
+
103
+ const handleAction: HvActionsGenericProps["onAction"] = async (
104
+ event,
105
+ action,
106
+ ) => {
107
+ if (action.id === "put" || action.id === "add") {
108
+ alert(
109
+ `Callback for action ${
110
+ action.label
111
+ } on items ${instance.selectedFlatRows
112
+ .map((r) => r.original.id)
113
+ .join(", ")}`,
114
+ );
115
+ } else if (action.id === "delete") {
116
+ const ids = instance.selectedFlatRows.map((r) => r.original.id || "");
117
+
118
+ await deleteEntries(ids);
119
+
120
+ if (
121
+ ids.length === data.length &&
122
+ instance.state.pageIndex === pages - 1
123
+ ) {
124
+ // Go back
125
+ instance.gotoPage?.((instance.state.pageIndex as number) - 1);
126
+ setParams((prev) => ({
127
+ ...prev,
128
+ skip: prev.skip - prev.limit,
129
+ }));
130
+ }
131
+ }
132
+ };
133
+
134
+ return (
135
+ <div className={classes.root}>
136
+ <HvControls
137
+ views={views}
138
+ callbacks={instance}
139
+ defaultView="card"
140
+ onViewChange={(evt, id) => setCurrentView(id)}
141
+ >
142
+ <HvLeftControl
143
+ placeholder="Search"
144
+ onSearch={handleSearch}
145
+ searchProps={{
146
+ inputProps: {
147
+ "aria-label": "Search",
148
+ "aria-controls": `${cardViewId} ${listViewId}`,
149
+ },
150
+ }}
151
+ />
152
+ <HvRightControl
153
+ values={rightControlValues}
154
+ sortProps={{
155
+ "aria-label": "Sort by",
156
+ "aria-controls": `${cardViewId} ${listViewId}`,
157
+ }}
158
+ />
159
+ </HvControls>
160
+
161
+ <HvBulkActions
162
+ {...bulkActionProps}
163
+ numTotal={data.length}
164
+ numSelected={instance.selectedFlatRows.length}
165
+ maxVisibleActions={2}
166
+ onSelectAll={() => bulkActionProps?.onSelectAll()}
167
+ onSelectAllPages={() => bulkActionProps?.onSelectAllPages()}
168
+ actions={actions}
169
+ onAction={handleAction}
170
+ checkboxProps={{
171
+ "aria-controls": `${cardViewId} ${listViewId}`,
172
+ }}
173
+ />
174
+
175
+ {currentView === "card" && (
176
+ <CardView id={cardViewId} instance={instance} loading={loading} />
177
+ )}
178
+
179
+ {currentView === "list" && (
180
+ <ListView id={listViewId} instance={instance} columns={columns} />
181
+ )}
182
+
183
+ {instance.page?.length ? (
184
+ <HvPagination
185
+ {...instance.getHvPaginationProps?.()}
186
+ pageSizeOptions={PAGE_OPTIONS}
187
+ labels={{
188
+ pageSizeEntryName: currentView === "card" ? "cards" : "rows",
189
+ }}
190
+ />
191
+ ) : undefined}
192
+ </div>
193
+ );
194
+ };
195
+
196
+ export { AssetInventory as Component };
197
+
198
+ export default AssetInventory;
@@ -0,0 +1,158 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import useSWR from "swr";
3
+
4
+ // --- Types ---
5
+
6
+ export type DataObject = Record<string, string | number | undefined>;
7
+
8
+ export interface ServerPaginationProps<T extends DataObject = DataObject> {
9
+ endpoint: string;
10
+ db: T[];
11
+ limit: number;
12
+ skip: number;
13
+ sort?: string;
14
+ order?: "desc" | "asc";
15
+ filter?: { id: string; value: string | number };
16
+ search?: string;
17
+ }
18
+
19
+ export interface PaginationData<T extends DataObject = DataObject> {
20
+ data: T[];
21
+ total: number;
22
+ pages: number;
23
+ }
24
+
25
+ // --- Utils ---
26
+
27
+ export const delay = (ms = 500) =>
28
+ new Promise((resolve) => {
29
+ setTimeout(() => resolve("Time passed"), ms);
30
+ });
31
+
32
+ const searchObj = (entry: DataObject, search: string) => {
33
+ return Object.keys(entry)
34
+ .filter((key) => key !== "id" && key !== "statusColor")
35
+ .find(
36
+ (key) =>
37
+ entry[key]?.toString().toLowerCase().search(search.toLowerCase()) !==
38
+ -1,
39
+ )?.length;
40
+ };
41
+
42
+ // --- Pagination hook ---
43
+
44
+ export function useServerPagination<T extends DataObject = DataObject>({
45
+ endpoint,
46
+ db,
47
+ limit,
48
+ skip,
49
+ sort,
50
+ order = "desc",
51
+ filter,
52
+ search,
53
+ }: ServerPaginationProps<T>) {
54
+ const initialState: PaginationData<T> = {
55
+ data: [],
56
+ pages: 0,
57
+ total: 0,
58
+ };
59
+ const [currData, setCurrData] = useState(initialState);
60
+ const [deleting, setDeleting] = useState(false);
61
+ const [allData, setAllData] = useState<T[]>(db);
62
+
63
+ const fetcher = useCallback(async () => {
64
+ // Loading
65
+ await delay(800);
66
+
67
+ const items =
68
+ search || filter
69
+ ? allData.filter((entry) => {
70
+ let keep = true;
71
+
72
+ if (search) {
73
+ keep = !!searchObj(
74
+ entry as Record<string, string | number>,
75
+ search,
76
+ );
77
+ }
78
+ if (filter && keep) {
79
+ keep =
80
+ entry[filter.id]?.toString().toLowerCase() ===
81
+ filter.value.toString().toLowerCase();
82
+ }
83
+
84
+ return keep;
85
+ })
86
+ : [...allData];
87
+
88
+ return {
89
+ data: items
90
+ .sort(
91
+ sort
92
+ ? (a, b) => {
93
+ return order === "desc"
94
+ ? (b[sort] || "")
95
+ .toString()
96
+ .localeCompare((a[sort] || "").toString(), undefined, {
97
+ numeric: true,
98
+ sensitivity: "base",
99
+ })
100
+ : (a[sort] || "")
101
+ .toString()
102
+ .localeCompare((b[sort] || "").toString(), undefined, {
103
+ numeric: true,
104
+ sensitivity: "base",
105
+ });
106
+ }
107
+ : undefined,
108
+ )
109
+ .slice(skip, skip + limit),
110
+ total: items.length,
111
+ pages: Math.ceil(items.length / limit),
112
+ };
113
+ }, [allData, filter, limit, order, search, skip, sort]);
114
+
115
+ const { data, isLoading, mutate } = useSWR<PaginationData<T>>(() => {
116
+ const params = new URLSearchParams({
117
+ skip: skip.toString(),
118
+ limit: limit.toString(),
119
+ ...(sort && { sort, order }),
120
+ ...(search && { search }),
121
+ ...(filter && { [filter.id]: filter.value }),
122
+ });
123
+
124
+ return `${endpoint}?${params.toString()}`;
125
+ }, fetcher);
126
+
127
+ // Prevents data from updating to undefined when loading (this crashes useHvData)
128
+ useEffect(() => {
129
+ if (!isLoading && data) {
130
+ setCurrData(data);
131
+ }
132
+ }, [isLoading, data]);
133
+
134
+ const deleteEntries = useCallback(
135
+ async (ids: string[]) => {
136
+ setDeleting(true);
137
+
138
+ setAllData((prev) =>
139
+ prev.filter((entry: any) => entry.id && !ids.includes(entry.id)),
140
+ );
141
+
142
+ const refreshedData = await fetcher();
143
+
144
+ mutate(refreshedData);
145
+
146
+ await delay(800);
147
+
148
+ setDeleting(false);
149
+ },
150
+ [fetcher, mutate],
151
+ );
152
+
153
+ return {
154
+ deleteEntries,
155
+ data: currData,
156
+ loading: isLoading || deleting,
157
+ };
158
+ }
@@ -0,0 +1,49 @@
1
+ import {
2
+ createContext,
3
+ Dispatch,
4
+ SetStateAction,
5
+ useContext,
6
+ useMemo,
7
+ useState,
8
+ } from "react";
9
+
10
+ interface SelectedTable {
11
+ id: string;
12
+ label: React.ReactNode;
13
+ }
14
+
15
+ interface CanvasContextValue {
16
+ selectedTable: string;
17
+ setSelectedTable?: Dispatch<SetStateAction<string>>;
18
+ openedTables?: SelectedTable[];
19
+ setOpenedTables?: Dispatch<SetStateAction<SelectedTable[] | undefined>>;
20
+ }
21
+
22
+ const CanvasContext = createContext<CanvasContextValue>({
23
+ selectedTable: "none",
24
+ });
25
+
26
+ interface CanvasProviderProps {
27
+ children?: React.ReactNode;
28
+ }
29
+
30
+ export const CanvasProvider = ({ children }: CanvasProviderProps) => {
31
+ const [openedTables, setOpenedTables] = useState<SelectedTable[]>();
32
+ const [selectedTable, setSelectedTable] = useState<string>("none");
33
+
34
+ const value = useMemo(
35
+ () => ({
36
+ openedTables,
37
+ setOpenedTables,
38
+ selectedTable,
39
+ setSelectedTable,
40
+ }),
41
+ [openedTables, selectedTable],
42
+ );
43
+
44
+ return (
45
+ <CanvasContext.Provider value={value}>{children}</CanvasContext.Provider>
46
+ );
47
+ };
48
+
49
+ export const useCanvasContext = () => useContext(CanvasContext);