blixify-ui-web-mcp 0.1.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.
- package/README.md +5 -0
- package/lib/components/action/button/README.md +75 -0
- package/lib/components/action/button/button.stories.tsx +138 -0
- package/lib/components/action/buttonGroup/README.md +79 -0
- package/lib/components/action/buttonGroup/buttonGroup.stories.tsx +104 -0
- package/lib/components/action/iconButton/README.md +39 -0
- package/lib/components/action/iconButton/iconButton.stories.tsx +22 -0
- package/lib/components/action/radioList/README.md +98 -0
- package/lib/components/action/radioList/radioList.stories.tsx +181 -0
- package/lib/components/action/slider/README.md +86 -0
- package/lib/components/action/slider/slider.stories.tsx +133 -0
- package/lib/components/action/textButton/README.md +34 -0
- package/lib/components/action/textButton/textButton.stories.tsx +16 -0
- package/lib/components/action/toggle/README.md +39 -0
- package/lib/components/action/toggle/toggle.stories.tsx +23 -0
- package/lib/components/advanced/collapse/README.md +106 -0
- package/lib/components/advanced/collapse/collapse.stories.tsx +165 -0
- package/lib/components/advanced/infoWindow/README.md +69 -0
- package/lib/components/advanced/infoWindow/infoWindow.stories.tsx +112 -0
- package/lib/components/advanced/popUp/README.md +95 -0
- package/lib/components/advanced/popUp/popUp.stories.tsx +189 -0
- package/lib/components/auth/bareSignIn/README.md +78 -0
- package/lib/components/auth/bareSignIn/bareSignIn.stories.tsx +141 -0
- package/lib/components/auth/bareSignUp/README.md +130 -0
- package/lib/components/auth/bareSignUp/bareSignUp.stories.tsx +159 -0
- package/lib/components/auth/signIn/README.md +53 -0
- package/lib/components/auth/signIn/signIn.stories.tsx +120 -0
- package/lib/components/auth/signUp/README.md +136 -0
- package/lib/components/auth/signUp/signUp.stories.tsx +193 -0
- package/lib/components/chart/README.md +13 -0
- package/lib/components/chart/areaChart/README.md +133 -0
- package/lib/components/chart/areaChart/areaChart.stories.tsx +199 -0
- package/lib/components/chart/barChart/README.md +193 -0
- package/lib/components/chart/barChart/barChart.stories.tsx +318 -0
- package/lib/components/chart/boxPlotChart/README.md +124 -0
- package/lib/components/chart/boxPlotChart/boxPlotChart.stories.tsx +104 -0
- package/lib/components/chart/bubbleChart/README.md +166 -0
- package/lib/components/chart/bubbleChart/bubbleChart.stories.tsx +144 -0
- package/lib/components/chart/card/README.md +107 -0
- package/lib/components/chart/card/card.stories.tsx +89 -0
- package/lib/components/chart/ganttChart/README.md +142 -0
- package/lib/components/chart/ganttChart/ganttChart.stories.tsx +138 -0
- package/lib/components/chart/heatmapChart/README.md +125 -0
- package/lib/components/chart/heatmapChart/heatmapChart.stories.tsx +106 -0
- package/lib/components/chart/lineChart/README.md +140 -0
- package/lib/components/chart/lineChart/lineChart.stories.tsx +152 -0
- package/lib/components/chart/pieChart/README.md +117 -0
- package/lib/components/chart/pieChart/pieChart.stories.tsx +180 -0
- package/lib/components/chart/polarAreaChart/README.md +126 -0
- package/lib/components/chart/polarAreaChart/polarAreaChart.stories.tsx +106 -0
- package/lib/components/chart/radarChart/README.md +57 -0
- package/lib/components/chart/radarChart/radarChart.stories.tsx +36 -0
- package/lib/components/chart/radialBar/README.md +152 -0
- package/lib/components/chart/radialBar/radialChart.stories.tsx +136 -0
- package/lib/components/chart/rangeBarChart/README.md +323 -0
- package/lib/components/chart/rangeBarChart/rangeBarChart.stories.tsx +208 -0
- package/lib/components/chart/statsCard/README.md +97 -0
- package/lib/components/chart/statsCard/statsCard.stories.tsx +183 -0
- package/lib/components/chart/treemapChart/README.md +142 -0
- package/lib/components/chart/treemapChart/treemapChart.stories.tsx +122 -0
- package/lib/components/chat/chatAnchor/README.md +113 -0
- package/lib/components/chat/chatAnchor/chatAnchor.stories.tsx +156 -0
- package/lib/components/chat/chatBase/README.md +223 -0
- package/lib/components/chat/chatBase/chatBase.stories.tsx +306 -0
- package/lib/components/chat/chatDashboard/README.md +161 -0
- package/lib/components/chat/chatDashboard/chatDashboard.stories.tsx +154 -0
- package/lib/components/data/dataGraph/README.md +13 -0
- package/lib/components/data/dataGraph/dataGraph.stories.tsx +84 -0
- package/lib/components/data/dataReference/README.md +62 -0
- package/lib/components/data/dataReference/dataReference.stories.tsx +53 -0
- package/lib/components/data/dataTemplate/README.md +903 -0
- package/lib/components/data/dataTemplate/dataModule.stories.tsx +196 -0
- package/lib/components/data/dataTemplate/dataTemplate.stories.tsx +337 -0
- package/lib/components/data/dataWrapper/README.md +1077 -0
- package/lib/components/data/dataWrapper/dataWrapper.stories.tsx +831 -0
- package/lib/components/data/queryForm/README.md +56 -0
- package/lib/components/data/queryForm/queryForm.stories.tsx +85 -0
- package/lib/components/data/readQuery/README.md +282 -0
- package/lib/components/data/readQuery/readQuery.stories.tsx +256 -0
- package/lib/components/data/readQueryHoc/README.md +89 -0
- package/lib/components/data/readQueryHoc/readQueryHoc.stories.tsx +33 -0
- package/lib/components/data/writeQuery/README.md +178 -0
- package/lib/components/data/writeQuery/writeQuery.stories.tsx +209 -0
- package/lib/components/design/actionSection/README.md +47 -0
- package/lib/components/design/actionSection/actionSection.stories.tsx +74 -0
- package/lib/components/design/banner/README.md +43 -0
- package/lib/components/design/banner/banner.stories.tsx +34 -0
- package/lib/components/design/bannerAction/README.md +44 -0
- package/lib/components/design/bannerAction/bannerAction.stories.tsx +25 -0
- package/lib/components/design/blogSection/README.md +111 -0
- package/lib/components/design/blogSection/blogSection.stories.tsx +72 -0
- package/lib/components/design/carouselSection/README.md +82 -0
- package/lib/components/design/carouselSection/carouselSection.stories.tsx +52 -0
- package/lib/components/design/designList/README.md +60 -0
- package/lib/components/design/designList/designList.stories.tsx +44 -0
- package/lib/components/design/faq/README.md +121 -0
- package/lib/components/design/faq/faq.stories.tsx +103 -0
- package/lib/components/design/fileGrid/README.md +87 -0
- package/lib/components/design/fileGrid/fileGrid.stories.tsx +110 -0
- package/lib/components/design/fileViewer/README.md +40 -0
- package/lib/components/design/fileViewer/fileViewer.stories.tsx +28 -0
- package/lib/components/design/footerSection/README.md +62 -0
- package/lib/components/design/footerSection/footerSection.stories.tsx +84 -0
- package/lib/components/design/googleReview/README.md +45 -0
- package/lib/components/design/googleReview/googleReview.stories.tsx +33 -0
- package/lib/components/design/heroSection/README.md +48 -0
- package/lib/components/design/heroSection/heroSection.stories.tsx +85 -0
- package/lib/components/design/iconList/README.md +60 -0
- package/lib/components/design/iconList/iconList.stories.tsx +41 -0
- package/lib/components/design/imageGallery/README.md +56 -0
- package/lib/components/design/imageGallery/imageGallery.stories.tsx +45 -0
- package/lib/components/design/imageZoom/README.md +47 -0
- package/lib/components/design/imageZoom/imageZoom.stories.tsx +30 -0
- package/lib/components/design/leftContent/README.md +47 -0
- package/lib/components/design/leftContent/leftContent.stories.tsx +32 -0
- package/lib/components/design/logoCloud/README.md +42 -0
- package/lib/components/design/logoCloud/logoCloud.stories.tsx +44 -0
- package/lib/components/design/onboarding/README.md +100 -0
- package/lib/components/design/onboarding/onboarding.stories.tsx +113 -0
- package/lib/components/design/pricing/README.md +112 -0
- package/lib/components/design/pricing/pricing.stories.tsx +86 -0
- package/lib/components/design/rightContent/README.md +47 -0
- package/lib/components/design/rightContent/rightContent.stories.tsx +32 -0
- package/lib/components/design/sectionList/README.md +51 -0
- package/lib/components/design/sectionList/sectionList.stories.tsx +35 -0
- package/lib/components/design/teams/README.md +62 -0
- package/lib/components/design/teams/teams.stories.tsx +61 -0
- package/lib/components/display/alert/README.md +82 -0
- package/lib/components/display/alert/alert.stories.tsx +71 -0
- package/lib/components/display/calendar/README.md +113 -0
- package/lib/components/display/calendar/calendar.stories.tsx +159 -0
- package/lib/components/display/clipboard/README.md +36 -0
- package/lib/components/display/clipboard/clipboard.stories.tsx +32 -0
- package/lib/components/display/detailList/README.md +61 -0
- package/lib/components/display/detailList/detailList.stories.tsx +131 -0
- package/lib/components/display/drawer/README.md +74 -0
- package/lib/components/display/drawer/drawer.stories.tsx +66 -0
- package/lib/components/display/emptyState/README.md +38 -0
- package/lib/components/display/emptyState/emptyState.stories.tsx +22 -0
- package/lib/components/display/feeds/README.md +80 -0
- package/lib/components/display/feeds/feeds.stories.tsx +75 -0
- package/lib/components/display/flexTable/README.md +160 -0
- package/lib/components/display/flexTable/flexTable.stories.tsx +313 -0
- package/lib/components/display/grid/README.md +121 -0
- package/lib/components/display/grid/grid.stories.tsx +234 -0
- package/lib/components/display/kanban/README.md +78 -0
- package/lib/components/display/kanban/kanban.stories.tsx +133 -0
- package/lib/components/display/list/README.md +109 -0
- package/lib/components/display/list/list.stories.tsx +188 -0
- package/lib/components/display/loading/README.md +25 -0
- package/lib/components/display/loading/loading.stories.tsx +15 -0
- package/lib/components/display/mapView/README.md +99 -0
- package/lib/components/display/mapView/mapView.stories.tsx +225 -0
- package/lib/components/display/modal/README.md +43 -0
- package/lib/components/display/modal/modal.stories.tsx +81 -0
- package/lib/components/display/notification/README.md +43 -0
- package/lib/components/display/notification/notification.stories.tsx +30 -0
- package/lib/components/display/progress/README.md +25 -0
- package/lib/components/display/progress/progress.stories.tsx +15 -0
- package/lib/components/display/stepper/README.md +96 -0
- package/lib/components/display/stepper/stepper.stories.tsx +175 -0
- package/lib/components/display/table/README.md +176 -0
- package/lib/components/display/table/table.stories.tsx +330 -0
- package/lib/components/display/tag/README.md +74 -0
- package/lib/components/display/tag/tag.stories.tsx +79 -0
- package/lib/components/input/address/README.md +132 -0
- package/lib/components/input/address/address.stories.tsx +147 -0
- package/lib/components/input/canvas/README.md +131 -0
- package/lib/components/input/canvas/canvas.stories.tsx +165 -0
- package/lib/components/input/checkbox/README.md +61 -0
- package/lib/components/input/checkbox/checkbox.stories.tsx +30 -0
- package/lib/components/input/datePicker/README.md +124 -0
- package/lib/components/input/datePicker/datePicker.stories.tsx +197 -0
- package/lib/components/input/form/README.md +55 -0
- package/lib/components/input/form/form.stories.tsx +143 -0
- package/lib/components/input/markdown/README.md +146 -0
- package/lib/components/input/markdown/markdown.stories.tsx +40 -0
- package/lib/components/input/monthYearPicker/README.md +69 -0
- package/lib/components/input/monthYearPicker/monthYearPicker.stories.tsx +47 -0
- package/lib/components/input/netPromoterScore/README.md +52 -0
- package/lib/components/input/netPromoterScore/netPromoterScore.stories.tsx +30 -0
- package/lib/components/input/rating/README.md +51 -0
- package/lib/components/input/rating/rating.stories.tsx +30 -0
- package/lib/components/input/select/README.md +108 -0
- package/lib/components/input/select/select.stories.tsx +286 -0
- package/lib/components/input/signature/README.md +106 -0
- package/lib/components/input/signature/signature.stories.tsx +188 -0
- package/lib/components/input/textArea/README.md +55 -0
- package/lib/components/input/textArea/textArea.stories.tsx +30 -0
- package/lib/components/input/textInput/README.md +81 -0
- package/lib/components/input/textInput/textinput.stories.tsx +231 -0
- package/lib/components/input/uploadInput/README.md +134 -0
- package/lib/components/input/uploadInput/uploadInput.stories.tsx +193 -0
- package/lib/components/navigation/bars/README.md +40 -0
- package/lib/components/navigation/bars/bars.stories.tsx +85 -0
- package/lib/components/navigation/bottomTab/README.md +53 -0
- package/lib/components/navigation/bottomTab/bottomTab.stories.tsx +32 -0
- package/lib/components/navigation/breadcrumb/README.md +48 -0
- package/lib/components/navigation/breadcrumb/breadcrumb.stories.tsx +27 -0
- package/lib/components/navigation/header/README.md +149 -0
- package/lib/components/navigation/header/header.stories.tsx +107 -0
- package/lib/components/navigation/leftNav/README.md +91 -0
- package/lib/components/navigation/leftNav/leftNav.stories.tsx +106 -0
- package/lib/components/navigation/tabs/README.md +75 -0
- package/lib/components/navigation/tabs/tabs.stories.tsx +60 -0
- package/lib/components/structure/column/README.md +40 -0
- package/lib/components/structure/column/column.stories.tsx +26 -0
- package/lib/components/structure/container/README.md +34 -0
- package/lib/components/structure/container/container.stories.tsx +19 -0
- package/lib/components/structure/link/README.md +55 -0
- package/lib/components/structure/link/link.stories.tsx +30 -0
- package/lib/components/structure/text/README.md +42 -0
- package/lib/components/structure/text/text.stories.tsx +39 -0
- package/lib/components/template/authPage/authPage.stories.tsx +68 -0
- package/lib/components/template/landingPage/landingPage.stories.tsx +288 -0
- package/lib/components/template/productListing/productListing.stories.tsx +297 -0
- package/lib/components/template/productOverview/productOverview.stories.tsx +194 -0
- package/lib/components/template/shoppingCart/shoppingCart.stories.tsx +252 -0
- package/lib/components/template/userHomePage/userHomePage.stories.tsx +605 -0
- package/lib/components/template/userRankingDashboard/userRankingDashboard.stories.tsx +481 -0
- package/lib/mcp/server.d.ts +2 -0
- package/lib/mcp/server.d.ts.map +1 -0
- package/lib/mcp/server.js +33 -0
- package/lib/mcp/server.js.map +1 -0
- package/lib/mcp/tools/getBackendComponent.d.ts +13 -0
- package/lib/mcp/tools/getBackendComponent.d.ts.map +1 -0
- package/lib/mcp/tools/getBackendComponent.js +119 -0
- package/lib/mcp/tools/getBackendComponent.js.map +1 -0
- package/lib/mcp/tools/getComponent.d.ts +17 -0
- package/lib/mcp/tools/getComponent.d.ts.map +1 -0
- package/lib/mcp/tools/getComponent.js +118 -0
- package/lib/mcp/tools/getComponent.js.map +1 -0
- package/lib/mcp/tools/listComponents.d.ts +13 -0
- package/lib/mcp/tools/listComponents.d.ts.map +1 -0
- package/lib/mcp/tools/listComponents.js +113 -0
- package/lib/mcp/tools/listComponents.js.map +1 -0
- package/lib/mcp/tools/listTemplates.d.ts +13 -0
- package/lib/mcp/tools/listTemplates.d.ts.map +1 -0
- package/lib/mcp/tools/listTemplates.js +68 -0
- package/lib/mcp/tools/listTemplates.js.map +1 -0
- package/lib/mcp/tools/suggestTemplate.d.ts +17 -0
- package/lib/mcp/tools/suggestTemplate.d.ts.map +1 -0
- package/lib/mcp/tools/suggestTemplate.js +212 -0
- package/lib/mcp/tools/suggestTemplate.js.map +1 -0
- package/package.json +23 -0
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
# Data Wrapper
|
|
2
|
+
|
|
3
|
+
<!-- STORY -->
|
|
4
|
+
|
|
5
|
+
<hr>
|
|
6
|
+
|
|
7
|
+
`DataTemplateWrapper` is a simplified wrapper around `DataTemplate` designed for embedding data views inside existing dashboards or app pages — without needing to manage routing, backend config, or library wiring yourself.
|
|
8
|
+
|
|
9
|
+
For most custom integrations, **`DataTemplateWrapper` is the recommended starting point**. You define the schema directly in code, pass a `workspace` config instead of building `devSettings` manually, and the wrapper handles axios, headlessUI, and papaparse internally. Use `DataTemplate` directly only when you have a Space object from the Blixify backend and want to drive the schema and config from the database.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Props from Data Template that do NOT apply to DataTemplateWrapper
|
|
14
|
+
|
|
15
|
+
These `DataTemplate` props are handled internally and cannot be passed:
|
|
16
|
+
|
|
17
|
+
- `devSettings` — replaced by `workspace`
|
|
18
|
+
- `lib.axios`, `lib.Menu`, `lib.Transition`, `lib.papaParser` — always bundled
|
|
19
|
+
- `localURLEndpoint` — always `""`
|
|
20
|
+
- `model` — replaced by `schema`
|
|
21
|
+
- `onComplete` — replaced by `handleComplete`
|
|
22
|
+
- `organise.handleNavigate` — replaced by top-level `handleNavigate`
|
|
23
|
+
- `organise.additionalQuery` — replaced by top-level `additionalQuery`
|
|
24
|
+
- `organise.sort` — replaced by top-level `sort`
|
|
25
|
+
- `organise.extendedTabs` / `organise.viewId` — replaced by `viewSettings`
|
|
26
|
+
|
|
27
|
+
You can still pass `organise` for everything else (`breadcrumbType`, `tabAlign`, `splitView`, `exportSettings`, etc.).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Import
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import { DataTemplateWrapper } from "blixify-ui-web/data/dataWrapper";
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Step-by-Step Setup
|
|
40
|
+
|
|
41
|
+
**1. Create a ref and navigation state**
|
|
42
|
+
|
|
43
|
+
The wrapper is a class component, so use `useRef` to access its imperative methods. Store `type` and `id` in state — these drive which view is rendered.
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
const dataRef = useRef<any>(null);
|
|
47
|
+
const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
|
|
48
|
+
const [selectedId, setSelectedId] = useState<string | undefined>(undefined);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**2. Define your schema**
|
|
52
|
+
|
|
53
|
+
Same shape as `DataTemplate`'s `model`. Define the columns you want to display and edit.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
const schema: DataStructure[] = [
|
|
57
|
+
{ id: "name", name: "Name", type: "string" },
|
|
58
|
+
{
|
|
59
|
+
id: "status",
|
|
60
|
+
name: "Status",
|
|
61
|
+
type: "list",
|
|
62
|
+
listSelection: ["active", "inactive"],
|
|
63
|
+
},
|
|
64
|
+
{ id: "date", name: "Date", type: "date" },
|
|
65
|
+
];
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**3. Wire navigation**
|
|
69
|
+
|
|
70
|
+
The wrapper calls `handleNavigate` whenever the user clicks a row, a breadcrumb, or a create button. Update your state here.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
const handleNavigate = (type: string, id?: string) => {
|
|
74
|
+
setViewType(type as any);
|
|
75
|
+
if (id !== undefined) setSelectedId(id);
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
You can also use `handleNavigate` to redirect to a different page entirely instead of updating local state — useful when the read or update view lives on a separate route:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
const handleNavigate = (type: string, id?: string) => {
|
|
83
|
+
if (type === "read" && id) {
|
|
84
|
+
router.push(`/records/${id}`); // React Router / Next.js
|
|
85
|
+
// or: window.location.href = `/records/${id}`;
|
|
86
|
+
} else if (type === "update" && id) {
|
|
87
|
+
router.push(`/records/${id}/edit`);
|
|
88
|
+
} else {
|
|
89
|
+
setViewType("list");
|
|
90
|
+
setSelectedId(undefined);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**4. Configure the workspace**
|
|
96
|
+
|
|
97
|
+
Pass your backend connection details via `workspace`. No need to build `devSettings` manually.
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
const workspace = {
|
|
101
|
+
apiEndpointType: "mongoDB",
|
|
102
|
+
apiEndpoint: "https://api.example.com/api/mongoData",
|
|
103
|
+
apiEndpointToken: "your-token",
|
|
104
|
+
apiUserToken: "",
|
|
105
|
+
assetEndpoint: "https://storage.example.com/o/",
|
|
106
|
+
assetUploadEndpoint: "https://api.example.com/api/asset",
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**5. Render the component**
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
<DataTemplateWrapper
|
|
114
|
+
ref={dataRef}
|
|
115
|
+
title="Checklists"
|
|
116
|
+
description="O&M service checklists"
|
|
117
|
+
type={viewType}
|
|
118
|
+
id={selectedId}
|
|
119
|
+
collectionId="checklists"
|
|
120
|
+
schema={schema}
|
|
121
|
+
workspace={workspace}
|
|
122
|
+
handleNavigate={handleNavigate}
|
|
123
|
+
/>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**6. Override edit permissions (optional)**
|
|
127
|
+
|
|
128
|
+
The wrapper defaults to read-only (`disabledUpdate: true, deleteBtn: false`). Pass `edit` to enable create/update/delete.
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
edit={{ allEditable: true, deleteBtn: true }}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
When `edit` is not passed, the wrapper defaults to:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
{
|
|
138
|
+
allEditable: true,
|
|
139
|
+
listEditable: false,
|
|
140
|
+
disabledUpdate: true,
|
|
141
|
+
disableBatchUpdate: true,
|
|
142
|
+
deleteBtn: false,
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Pass your own `edit` object to override any of these.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
**7. Apply a pre-filter (optional)**
|
|
151
|
+
|
|
152
|
+
Use `bareSettings.bareListFilterQuery` to always filter the list by a condition — useful when embedding the component inside a page that has its own filter state.
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
const [statusFilter, setStatusFilter] = useState("active");
|
|
156
|
+
|
|
157
|
+
bareSettings={{
|
|
158
|
+
bareListFilterQuery: statusFilter !== "all"
|
|
159
|
+
? [{ queryId: "status", type: "=", value: statusFilter }]
|
|
160
|
+
: [],
|
|
161
|
+
}}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Call `dataRef.current?.handleRefresh()` after changing the filter to re-fetch.
|
|
165
|
+
|
|
166
|
+
**8. newTabUrl (Optional)**
|
|
167
|
+
|
|
168
|
+
Enables right-click → "Open in new tab" on list rows. The component builds the link as `default + recordId + addons`.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
newTabUrl={{
|
|
172
|
+
default: "https://dashboard.example.com/records/", // base URL — record ID is appended here
|
|
173
|
+
addons: "?view=read", // optional suffix appended after the record ID
|
|
174
|
+
}}
|
|
175
|
+
// resulting link: https://dashboard.example.com/records/<id>?view=read
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Both fields are optional — omit `addons` if you don't need a suffix.
|
|
179
|
+
|
|
180
|
+
**9. Use a non-table view (optional)**
|
|
181
|
+
|
|
182
|
+
Pass `viewSettings` to activate an alternate view instead of the default table.
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
// Grid view — renders records as image cards
|
|
186
|
+
viewSettings={{
|
|
187
|
+
gridImage: "coverImage", // column ID of an image field
|
|
188
|
+
gridScroll: true, // gridScroll enables infinite scroll;
|
|
189
|
+
gridContain: false, //gridContain uses object-contain instead of object-cover
|
|
190
|
+
}}
|
|
191
|
+
|
|
192
|
+
// List view — renders records as a vertical image list
|
|
193
|
+
viewSettings={{
|
|
194
|
+
listImage: "coverImage", // column ID of an image field
|
|
195
|
+
}}
|
|
196
|
+
|
|
197
|
+
// Calendar view — renders records as calendar events
|
|
198
|
+
viewSettings={{
|
|
199
|
+
calendarSettings: {
|
|
200
|
+
title: "name", // column ID for event title
|
|
201
|
+
startDate: "startAt", // column ID for start date
|
|
202
|
+
endDate: "endAt", // column ID for end date (optional)
|
|
203
|
+
groupId: "category", // column ID to colour-group events (optional)
|
|
204
|
+
},
|
|
205
|
+
}}
|
|
206
|
+
|
|
207
|
+
// Custom calendar view — pass your own FullCalendar plugin via customPlugin
|
|
208
|
+
// When customPlugin is set, the default view switches to "timeGridWeek"
|
|
209
|
+
// unless you override it with defaultView
|
|
210
|
+
viewSettings={{
|
|
211
|
+
calendarSettings: {
|
|
212
|
+
title: "name",
|
|
213
|
+
startDate: "startAt",
|
|
214
|
+
endDate: "endAt",
|
|
215
|
+
customPlugin: myFullCalendarPlugin, // any FullCalendar-compatible plugin
|
|
216
|
+
defaultView: "timeGridWeek", // override the initial view (optional)
|
|
217
|
+
headerToolbar: { // override the toolbar buttons (optional)
|
|
218
|
+
left: "prev,next today",
|
|
219
|
+
center: "title",
|
|
220
|
+
right: "dayGridMonth,timeGridWeek,timeGridDay",
|
|
221
|
+
},
|
|
222
|
+
footerToolbar: { right: "prev,next" }, // optional footer toolbar
|
|
223
|
+
customButtons: { // define custom toolbar buttons (optional)
|
|
224
|
+
myBtn: { text: "Sync", click: () => handleSync() },
|
|
225
|
+
},
|
|
226
|
+
customView: { view: "week", duration: 2 }, // multi-week span (optional)
|
|
227
|
+
handleCalendarResize: (calendarRef, isMobileWidth) => { // respond to resize (optional)
|
|
228
|
+
if (isMobileWidth) calendarRef.current?.getApi().changeView("timeGridDay");
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
}}
|
|
232
|
+
|
|
233
|
+
// Feeds view — renders records as a timeline/activity feed
|
|
234
|
+
viewSettings={{
|
|
235
|
+
feedsSettings: {
|
|
236
|
+
title: "name", // column ID for feed item title
|
|
237
|
+
content: "notes", // column ID for feed item body
|
|
238
|
+
desc: "createdAt", // column ID for the right-corner element
|
|
239
|
+
icon: "HeartIcon", // heroicon name (optional)
|
|
240
|
+
iconClass: "text-red-500", // tailwind class for the icon (optional)
|
|
241
|
+
},
|
|
242
|
+
}}
|
|
243
|
+
|
|
244
|
+
// Kanban view — renders records as a drag-and-drop board
|
|
245
|
+
viewSettings={{
|
|
246
|
+
kanbanSettings: {
|
|
247
|
+
title: "name", // column ID for card title
|
|
248
|
+
useColumnName: true,
|
|
249
|
+
groupCluster: [
|
|
250
|
+
{ "To Do": ["todo"] },
|
|
251
|
+
{ "In Progress": ["in_progress"] },
|
|
252
|
+
{ "Review": ["review"] },
|
|
253
|
+
{ "Done": ["done"] },
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
}}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### Form section grouping
|
|
262
|
+
|
|
263
|
+
Fields in the update/create form can be grouped into named sections using `groupId` on each `DataStructure` entry. Fields without a `groupId` are placed in a default section at the bottom.
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
schema={[
|
|
267
|
+
{ id: "name", name: "Name", type: "string", groupId: "Basic Info" },
|
|
268
|
+
{ id: "email", name: "Email", type: "email", groupId: "Basic Info" },
|
|
269
|
+
{ id: "region", name: "Region", type: "string", groupId: "Location" },
|
|
270
|
+
{ id: "address", name: "Address", type: "address", groupId: "Location" },
|
|
271
|
+
{ id: "notes", name: "Notes", type: "textArea" }, // no groupId → default section
|
|
272
|
+
]}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
By default, groups render as labelled sections separated by a divider. To make groups collapsible, pass `groupIdCollapsible` via `organise`:
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
organise={{
|
|
279
|
+
groupIdCollapsible: {
|
|
280
|
+
"Basic Info": false, // false = expanded by default
|
|
281
|
+
"Location": true, // true = collapsed by default
|
|
282
|
+
},
|
|
283
|
+
}}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
When `groupIdCollapsible` is set, each group renders as a `Collapse` panel with a bold title. Groups are sorted alphabetically — fields without a `groupId` always render last.
|
|
287
|
+
|
|
288
|
+
You can also split the form into tabs using `tabId` on each field. Fields with the same `tabId` are grouped under that tab. Use `organise.tabAlign` to control the tab style (`"top"`, `"left"`, or `"stepper"`).
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
schema={[
|
|
292
|
+
{ id: "name", name: "Name", type: "string", tabId: "Details" },
|
|
293
|
+
{ id: "status", name: "Status", type: "list", tabId: "Details" },
|
|
294
|
+
{ id: "notes", name: "Notes", type: "textArea", tabId: "Notes" },
|
|
295
|
+
{ id: "signoff", name: "Sign Off", type: "signature", tabId: "Sign Off" },
|
|
296
|
+
]}
|
|
297
|
+
|
|
298
|
+
organise={{ tabAlign: "stepper" }} // renders tabs as a step-by-step wizard
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`tabId` and `groupId` can be combined — fields are first split by tab, then grouped within each tab.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Usage
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
<DataTemplateWrapper
|
|
309
|
+
ref={dataRef}
|
|
310
|
+
title="Maintenance Checklists"
|
|
311
|
+
description="O&M service checklists"
|
|
312
|
+
type={viewType} // "list" | "read" | "update"
|
|
313
|
+
id={selectedId}
|
|
314
|
+
collectionId="checklists"
|
|
315
|
+
schema={checklistSchema}
|
|
316
|
+
workspace={{
|
|
317
|
+
apiEndpointType: "mongoDB",
|
|
318
|
+
apiEndpoint: "https://api.example.com/api/mongoData",
|
|
319
|
+
apiEndpointToken: "your-token",
|
|
320
|
+
apiUserToken: "",
|
|
321
|
+
assetEndpoint: "https://storage.example.com/o/",
|
|
322
|
+
assetUploadEndpoint: "https://api.example.com/api/asset",
|
|
323
|
+
}}
|
|
324
|
+
edit={{ allEditable: true, deleteBtn: true }}
|
|
325
|
+
organise={{ breadcrumbType: "hidden", tabAlign: "left" }}
|
|
326
|
+
handleNavigate={(type, id) => {
|
|
327
|
+
setViewType(type);
|
|
328
|
+
setSelectedId(id);
|
|
329
|
+
}}
|
|
330
|
+
handleComplete={(id) => console.log("saved", id)}
|
|
331
|
+
limit={30}
|
|
332
|
+
/>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
### Grid view customisation
|
|
338
|
+
|
|
339
|
+
Use these together with `viewSettings={{ gridImage: "..." }}` to control how grid cards look and behave.
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
bareSettings={{
|
|
343
|
+
// Fully replace the default grid card with your own component
|
|
344
|
+
bareListGridComponent: (data) => (
|
|
345
|
+
<div className="rounded-lg overflow-hidden border border-gray-200">
|
|
346
|
+
<img src={data.coverImage} className="w-full h-40 object-cover" />
|
|
347
|
+
<div className="p-3">
|
|
348
|
+
<p className="font-medium text-sm">{data.name}</p>
|
|
349
|
+
<p className="text-xs text-gray-500">{data.location}</p>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
),
|
|
353
|
+
|
|
354
|
+
// Tailwind col-span class controlling how many columns each card takes
|
|
355
|
+
bareListGridColClassName: "col-span-1",
|
|
356
|
+
|
|
357
|
+
// Tag badges shown on the default grid card
|
|
358
|
+
bareListGridTags: (data) => [data.category, data.status],
|
|
359
|
+
|
|
360
|
+
// Ribbon text overlaid on the card image (e.g. "New", "Sold")
|
|
361
|
+
bareListGridRibbonText: (data) => data.status === "new" ? "New" : "",
|
|
362
|
+
|
|
363
|
+
// Fallback shown when the image column is empty
|
|
364
|
+
bareListGridFallbackImg: () => "/placeholder.png",
|
|
365
|
+
}}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
### List view customisation
|
|
371
|
+
|
|
372
|
+
Use these to control the table/list rows — add row-level action buttons, bulk actions, intercept data, or apply permanent filters.
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
bareSettings={{
|
|
376
|
+
// Icon buttons appended to each row
|
|
377
|
+
bareListActionBtns: (data) => [
|
|
378
|
+
<button key="send" onClick={(e) => { e.stopPropagation(); sendItem(data._id); }}>
|
|
379
|
+
<PaperAirplaneIcon className="w-4 h-4 text-primary-500" />
|
|
380
|
+
</button>,
|
|
381
|
+
],
|
|
382
|
+
|
|
383
|
+
// Bulk-action buttons shown when rows are selected
|
|
384
|
+
bareListMultipleSelectAddtionalActions: [
|
|
385
|
+
{
|
|
386
|
+
text: "Export Selected",
|
|
387
|
+
onClick: (ids, selectAll, query) => exportItems(ids),
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
|
|
391
|
+
// Fully replace the list UI with your own renderer
|
|
392
|
+
bareListComponent: (data, prettierData, loading, pageIndex) => (
|
|
393
|
+
<ul className="divide-y">
|
|
394
|
+
{data.map((item) => (
|
|
395
|
+
<li key={item._id} className="py-3 flex justify-between">
|
|
396
|
+
<span>{item.name}</span>
|
|
397
|
+
<span className="text-sm text-gray-500">{item.status}</span>
|
|
398
|
+
</li>
|
|
399
|
+
))}
|
|
400
|
+
</ul>
|
|
401
|
+
),
|
|
402
|
+
|
|
403
|
+
// Always-on filter applied to every list fetch (e.g. scope to current user)
|
|
404
|
+
bareListFilterQuery: [{ queryId: "assigneeId", type: "=", value: currentUserId }],
|
|
405
|
+
|
|
406
|
+
// Custom renderer for individual field values in the list
|
|
407
|
+
bareListDescription: (key, value) => {
|
|
408
|
+
if (key === "status") return <StatusBadge value={value} />;
|
|
409
|
+
return null; // return null to use the default renderer
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
// Called whenever list data loads — use to sync external state
|
|
413
|
+
bareListHandleData: (data, prettierData) => setRowCount(data.length),
|
|
414
|
+
|
|
415
|
+
// Called when row selection changes
|
|
416
|
+
bareListHandleSelection: (selectAll, selectedIds) => setSelected(selectedIds),
|
|
417
|
+
|
|
418
|
+
// Called whenever any filter changes
|
|
419
|
+
bareListHandleFilter: (search, select, date, reference) => console.log(search),
|
|
420
|
+
|
|
421
|
+
// Called with the full filter state on every change
|
|
422
|
+
bareListFilterSelected: (searchParams, selection, reference, refDate) => {
|
|
423
|
+
syncFiltersToURL(searchParams);
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// Extra filter components rendered in the filter bar
|
|
427
|
+
bareListFilterAddComponent: [
|
|
428
|
+
<MyCustomDateRangePicker key="range" onChange={setRange} />,
|
|
429
|
+
],
|
|
430
|
+
|
|
431
|
+
// MongoDB aggregation pipeline stages appended to list queries
|
|
432
|
+
bareListPipeline: [
|
|
433
|
+
{
|
|
434
|
+
spaceReferenceId: "users",
|
|
435
|
+
spaceReferenceAs: "assigneeData",
|
|
436
|
+
condition: [{ type: "=", fromColumnId: "assigneeId", toColumnId: "_id" }],
|
|
437
|
+
referenceSpaceLabel: ["name", "email"],
|
|
438
|
+
referenceFirstItem: true,
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
}}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
### Read view customisation
|
|
447
|
+
|
|
448
|
+
Use these to inject extra content into the read view, intercept data on load, or override how individual field values are displayed.
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
bareSettings={{
|
|
452
|
+
// Inject a custom component into the read view
|
|
453
|
+
// By default it renders below the field list
|
|
454
|
+
bareReadComponent: (data) => (
|
|
455
|
+
<div className="mt-4 p-4 bg-blue-50 rounded-lg">
|
|
456
|
+
<p className="text-sm font-medium">Custom summary for {data.name}</p>
|
|
457
|
+
<p className="text-xs text-gray-500">Last updated: {data.updatedAt}</p>
|
|
458
|
+
</div>
|
|
459
|
+
),
|
|
460
|
+
|
|
461
|
+
// Render the custom component above the field list instead of below
|
|
462
|
+
bareReadComponentPos: "top",
|
|
463
|
+
|
|
464
|
+
// Hide the default field list entirely — show only bareReadComponent
|
|
465
|
+
bareReadComponentHide: true,
|
|
466
|
+
|
|
467
|
+
// Custom renderer for individual field values in the read view
|
|
468
|
+
bareReadDescription: (key, value) => {
|
|
469
|
+
if (key === "status") return <StatusBadge value={value} />;
|
|
470
|
+
return null; // return null to use the default renderer
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
// Called when read view data loads — use to sync external state
|
|
474
|
+
bareReadHandleData: (data) => setPageTitle(data.name),
|
|
475
|
+
|
|
476
|
+
// Control whether clicking a reference field opens its detail view
|
|
477
|
+
bareReadReferenceView: (dataId, collectionId, attributeId) => {
|
|
478
|
+
// return false to disable the view button for this reference
|
|
479
|
+
return collectionId !== "restricted-collection";
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
// Show list-style tabs in read view (useful without a schemaEndpoint)
|
|
483
|
+
bareReadListTabs: true,
|
|
484
|
+
}}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
### Update / form customisation
|
|
490
|
+
|
|
491
|
+
Use these to control the create/edit form layout, prefill values, inject custom components, intercept saves, or replace individual fields.
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
bareSettings={{
|
|
495
|
+
// Prefill form fields based on existing record data
|
|
496
|
+
bareUpdatePrefill: (data) => [
|
|
497
|
+
{ key: "assigneeId", value: currentUserId },
|
|
498
|
+
{ key: "region", value: data.siteRegion },
|
|
499
|
+
],
|
|
500
|
+
|
|
501
|
+
// Inject a custom component into the form (renders below fields by default)
|
|
502
|
+
bareUpdateComponent: (data) => (
|
|
503
|
+
<div className="p-3 bg-yellow-50 rounded text-sm">
|
|
504
|
+
Note: changes will notify {data.assigneeName}
|
|
505
|
+
</div>
|
|
506
|
+
),
|
|
507
|
+
|
|
508
|
+
// Render bareUpdateComponent above the fields instead of below
|
|
509
|
+
bareUpdateComponentAbove: true,
|
|
510
|
+
|
|
511
|
+
// Replace the default save/cancel buttons with your own
|
|
512
|
+
bareUpdateActionComponent: (data, formRef) => (
|
|
513
|
+
<div className="flex gap-2">
|
|
514
|
+
<button onClick={() => formRef?.handleFormSubmit()}>Save</button>
|
|
515
|
+
<button onClick={onCancel}>Discard</button>
|
|
516
|
+
</div>
|
|
517
|
+
),
|
|
518
|
+
|
|
519
|
+
// Move action buttons to the bottom of the form
|
|
520
|
+
bareUpdateActionBottom: true,
|
|
521
|
+
|
|
522
|
+
// Form column layout: "one" = 2-col grid, "two"/"four" = 4-col grid
|
|
523
|
+
bareUpdateColumnType: "two",
|
|
524
|
+
|
|
525
|
+
// Mobile-optimised layout: horizontal label/input on mobile, standard on desktop
|
|
526
|
+
bareUpdateMobileView: true,
|
|
527
|
+
|
|
528
|
+
// Replace a specific field with a fully custom component
|
|
529
|
+
bareUpdateIndividualComponent: (columnId, ref, selectedData, setSelectedData) => {
|
|
530
|
+
if (columnId === "coordinates") {
|
|
531
|
+
return (
|
|
532
|
+
<MapPicker
|
|
533
|
+
value={selectedData.coordinates}
|
|
534
|
+
onChange={(val) => setSelectedData({ ...selectedData, coordinates: val })}
|
|
535
|
+
/>
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
return null; // return null to use the default field component
|
|
539
|
+
},
|
|
540
|
+
|
|
541
|
+
// Called on every field change — use to react to form state
|
|
542
|
+
bareUpdateHandleData: (data, changedIds) => {
|
|
543
|
+
if (changedIds?.includes("siteId")) refetchSiteDetails(data.siteId);
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
// Called when the active form tab changes
|
|
547
|
+
bareUpdateTabId: (tabId) => setActiveTab(tabId),
|
|
548
|
+
|
|
549
|
+
// Called before save — return false to cancel submission
|
|
550
|
+
bareUpdateHandlePreComplete: async (data) => {
|
|
551
|
+
const ok = await validateWithServer(data);
|
|
552
|
+
if (!ok) { showError("Validation failed"); return false; }
|
|
553
|
+
return true;
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
// Called after save completes
|
|
557
|
+
bareUpdateHandleComplete: async (data) => {
|
|
558
|
+
await sendNotification(data._id);
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
// Called after all post-save operations complete
|
|
562
|
+
bareUpdateHandlePostComplete: async (data) => {
|
|
563
|
+
await refreshRelatedList(data.projectId);
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
// Block deletion when this record is referenced elsewhere
|
|
567
|
+
bareReferenceDel: [
|
|
568
|
+
{
|
|
569
|
+
collectionId: "work-orders",
|
|
570
|
+
collectionName: "Work Orders",
|
|
571
|
+
columnId: "siteId",
|
|
572
|
+
schema: workOrderSchema,
|
|
573
|
+
},
|
|
574
|
+
],
|
|
575
|
+
|
|
576
|
+
// Enable offline data caching via IndexedDB
|
|
577
|
+
bareOffline: true,
|
|
578
|
+
|
|
579
|
+
// Custom CSV import/export endpoints
|
|
580
|
+
bareCSVImportEndpoint: "https://api.example.com/api/import",
|
|
581
|
+
bareCSVExportEndpoint: "https://api.example.com/api/export",
|
|
582
|
+
}}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## Offline Mode
|
|
588
|
+
|
|
589
|
+
Set `bareOffline: true` in `bareSettings` to enable offline data caching via IndexedDB. When active, the component saves form submissions locally when there is no network connection. Once the user is back online, they click the "Submit Offline Data" button (or you trigger `dataRef.current?.handleUploadOfflineModal()` programmatically) to flush the cached records to the server.
|
|
590
|
+
|
|
591
|
+
### How it works
|
|
592
|
+
|
|
593
|
+
1. User fills in and submits a form while offline — the record is written to IndexedDB instead of the server.
|
|
594
|
+
2. Only offline records are shown in the list (pulled from IndexedDB). The user can open them in read view and update view — all data is served from IndexedDB. These offline records are invisible when `bareOffline` is `false`.
|
|
595
|
+
3. When connectivity resumes, the user clicks "Submit Offline Data" (or you call `handleUploadOfflineModal()`). The component reads the queue from IndexedDB and replays each submission against the server.
|
|
596
|
+
|
|
597
|
+
### Pre-fetching reference options
|
|
598
|
+
|
|
599
|
+
Fields of type `reference` and `listReference` that pull their options from the server will be empty when offline. You must fetch those options while online and store them in IndexedDB yourself so the form stays usable offline.
|
|
600
|
+
|
|
601
|
+
Use the same database name the component uses and store each option set under a key with the format `[collectionId]-ref` (e.g. meraque-cycles-ref). Each value must be an array of `Option` objects — `{ key, label, secondaryLabel?, color?, image?, icon? }` — where `key` is the record's `_id`.
|
|
602
|
+
|
|
603
|
+
```tsx
|
|
604
|
+
// Call this once on app load (or when the user logs in) while online
|
|
605
|
+
async function prefetchReferenceOptions() {
|
|
606
|
+
const db = await openDB("blixify-offline", 1, {
|
|
607
|
+
upgrade(db) {
|
|
608
|
+
if (!db.objectStoreNames.contains("references")) {
|
|
609
|
+
db.createObjectStore("references");
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// Fetch raw records for every collection your form references
|
|
615
|
+
const [siteRecords, userRecords] = await Promise.all([
|
|
616
|
+
fetch("https://api.example.com/api/mongoData", {
|
|
617
|
+
method: "POST",
|
|
618
|
+
headers: { Authorization: "Bearer your-token" },
|
|
619
|
+
body: JSON.stringify({ collectionId: "sites", query: [] }),
|
|
620
|
+
}).then((r) => r.json()),
|
|
621
|
+
|
|
622
|
+
fetch("https://api.example.com/api/mongoData", {
|
|
623
|
+
method: "POST",
|
|
624
|
+
headers: { Authorization: "Bearer your-token" },
|
|
625
|
+
body: JSON.stringify({ collectionId: "users", query: [] }),
|
|
626
|
+
}).then((r) => r.json()),
|
|
627
|
+
]);
|
|
628
|
+
|
|
629
|
+
// Shape each record into an Option and store under "[collectionId]-ref"
|
|
630
|
+
await db.put(
|
|
631
|
+
"references",
|
|
632
|
+
siteRecords.map((r) => ({ key: r._id, label: r.name })),
|
|
633
|
+
"sites-ref"
|
|
634
|
+
);
|
|
635
|
+
await db.put(
|
|
636
|
+
"references",
|
|
637
|
+
userRecords.map((r) => ({
|
|
638
|
+
key: r._id,
|
|
639
|
+
label: r.name,
|
|
640
|
+
secondaryLabel: r.email,
|
|
641
|
+
})),
|
|
642
|
+
"users-ref"
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Call on mount, before rendering the form
|
|
647
|
+
useEffect(() => {
|
|
648
|
+
prefetchReferenceOptions();
|
|
649
|
+
}, []);
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
> Use library [`idb`](https://github.com/jakearchibald/idb) (`yarn add idb`) for a promise-based IndexedDB API.
|
|
653
|
+
|
|
654
|
+
### Full example
|
|
655
|
+
|
|
656
|
+
```tsx
|
|
657
|
+
import { openDB } from "idb";
|
|
658
|
+
|
|
659
|
+
function ChecklistPage() {
|
|
660
|
+
const dataRef = useRef<any>(null);
|
|
661
|
+
const [viewType, setViewType] = useState<"list" | "read" | "update">("list");
|
|
662
|
+
const [selectedId, setSelectedId] = useState<string | undefined>(undefined);
|
|
663
|
+
|
|
664
|
+
// Pre-fetch reference options while online
|
|
665
|
+
useEffect(() => {
|
|
666
|
+
prefetchReferenceOptions();
|
|
667
|
+
}, []);
|
|
668
|
+
|
|
669
|
+
const schema: DataStructure[] = [
|
|
670
|
+
{ id: "name", name: "Name", type: "string" },
|
|
671
|
+
{ id: "siteId", name: "Site", type: "reference", referenceId: "sites" },
|
|
672
|
+
{ id: "assignee", name: "Assignee", type: "listPerson" },
|
|
673
|
+
{ id: "notes", name: "Notes", type: "textArea" },
|
|
674
|
+
];
|
|
675
|
+
|
|
676
|
+
return (
|
|
677
|
+
<DataTemplateWrapper
|
|
678
|
+
ref={dataRef}
|
|
679
|
+
title="Checklists"
|
|
680
|
+
type={viewType}
|
|
681
|
+
id={selectedId}
|
|
682
|
+
collectionId="checklists"
|
|
683
|
+
schema={schema}
|
|
684
|
+
workspace={{
|
|
685
|
+
apiEndpointType: "mongoDB",
|
|
686
|
+
apiEndpoint: "https://api.example.com/api/mongoData",
|
|
687
|
+
apiEndpointToken: "your-token",
|
|
688
|
+
apiUserToken: "",
|
|
689
|
+
assetEndpoint: "https://storage.example.com/o/",
|
|
690
|
+
assetUploadEndpoint: "https://api.example.com/api/asset",
|
|
691
|
+
}}
|
|
692
|
+
edit={{ allEditable: true, deleteBtn: false }}
|
|
693
|
+
handleNavigate={(type, id) => {
|
|
694
|
+
setViewType(type as any);
|
|
695
|
+
if (id !== undefined) setSelectedId(id);
|
|
696
|
+
}}
|
|
697
|
+
bareSettings={{
|
|
698
|
+
bareOffline: true, // enable IndexedDB caching
|
|
699
|
+
}}
|
|
700
|
+
/>
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
### Triggering the upload modal programmatically
|
|
706
|
+
|
|
707
|
+
If you want to prompt the user to sync as soon as connectivity is detected, listen for the `online` event and call `handleUploadOfflineModal()`:
|
|
708
|
+
|
|
709
|
+
```tsx
|
|
710
|
+
useEffect(() => {
|
|
711
|
+
const handleOnline = () => {
|
|
712
|
+
dataRef.current?.handleUploadOfflineModal();
|
|
713
|
+
};
|
|
714
|
+
window.addEventListener("online", handleOnline);
|
|
715
|
+
return () => window.removeEventListener("online", handleOnline);
|
|
716
|
+
}, []);
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
The modal lists all queued records and lets the user confirm before uploading. Records that upload successfully are removed from IndexedDB automatically.
|
|
720
|
+
|
|
721
|
+
---
|
|
722
|
+
|
|
723
|
+
## Union Collections
|
|
724
|
+
|
|
725
|
+
`unionCollections` is passed via `organise` and lets you merge data from multiple MongoDB collections into a single list view. Each entry defines a secondary collection to query, how to filter it, which fields to project, and an optional `source` label to identify which collection each row came from.
|
|
726
|
+
|
|
727
|
+
This is only supported with `server: "mongoServer"`.
|
|
728
|
+
|
|
729
|
+
### Basic example — two collections, static source label
|
|
730
|
+
|
|
731
|
+
```tsx
|
|
732
|
+
organise={{
|
|
733
|
+
unionCollections: [
|
|
734
|
+
{
|
|
735
|
+
collection: "work-orders", // secondary collection to union in
|
|
736
|
+
source: "Work Order", // static label added as a "source" field on each row
|
|
737
|
+
query: [
|
|
738
|
+
{ queryId: "status", type: "=", value: "active" },
|
|
739
|
+
],
|
|
740
|
+
projection: {
|
|
741
|
+
_id: 1,
|
|
742
|
+
name: 1,
|
|
743
|
+
status: 1,
|
|
744
|
+
assigneeId: 1,
|
|
745
|
+
createdAt: 1,
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
],
|
|
749
|
+
sort: { sortId: "createdAt", type: "desc" },
|
|
750
|
+
}}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
The `source` field is automatically added to every row from that collection. Include `{ id: "source", name: "Source", type: "string", hideForm: true }` in your `schema` to display it as a column.
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
### Dynamic source label — derive the label from the row's own data
|
|
758
|
+
|
|
759
|
+
When `source` is `"dynamic"`, use `sourceLogic` to compute the label using a MongoDB `$cond` expression.
|
|
760
|
+
|
|
761
|
+
```tsx
|
|
762
|
+
organise={{
|
|
763
|
+
unionCollections: [
|
|
764
|
+
{
|
|
765
|
+
collection: "deliveries",
|
|
766
|
+
source: "dynamic", // compute label per row
|
|
767
|
+
sourceLogic: {
|
|
768
|
+
$cond: {
|
|
769
|
+
if: { $eq: ["$type", "om"] },
|
|
770
|
+
then: "O&M Maintenance",
|
|
771
|
+
else: "PV Delivery",
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
query: [
|
|
775
|
+
{ queryId: "type", type: "in", value: ["om", "pv"] },
|
|
776
|
+
],
|
|
777
|
+
projection: {
|
|
778
|
+
_id: 1, name: 1, type: 1, status: 1, createdAt: 1,
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
],
|
|
782
|
+
sort: { sortId: "createdAt", type: "desc" },
|
|
783
|
+
}}
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
---
|
|
787
|
+
|
|
788
|
+
### addFields — compute a virtual field for sorting across collections
|
|
789
|
+
|
|
790
|
+
Use `addFields` to add a computed field (e.g. a unified sort date) that doesn't exist natively on every collection. Sort by that field so rows from different collections interleave correctly.
|
|
791
|
+
|
|
792
|
+
```tsx
|
|
793
|
+
organise={{
|
|
794
|
+
unionCollections: [
|
|
795
|
+
{
|
|
796
|
+
collection: "deliveries",
|
|
797
|
+
source: "Delivery",
|
|
798
|
+
query: [],
|
|
799
|
+
projection: {
|
|
800
|
+
_id: 1, name: 1, proposed_date: 1, installation_date: 1, createdAt: 1,
|
|
801
|
+
},
|
|
802
|
+
// Add a virtual "sortDate" field — pick the first non-null date
|
|
803
|
+
addFields: {
|
|
804
|
+
sortDate: {
|
|
805
|
+
$ifNull: ["$proposed_date", "$createdAt"],
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
collection: "service-tickets",
|
|
811
|
+
source: "Service Ticket",
|
|
812
|
+
query: [],
|
|
813
|
+
projection: {
|
|
814
|
+
_id: 1, name: 1, service_date: 1, createdAt: 1,
|
|
815
|
+
},
|
|
816
|
+
addFields: {
|
|
817
|
+
sortDate: {
|
|
818
|
+
$ifNull: ["$service_date", "$createdAt"],
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
],
|
|
823
|
+
// Sort all rows by the computed field across both collections
|
|
824
|
+
sort: { sortId: "sortDate", type: "desc" },
|
|
825
|
+
}}
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
---
|
|
829
|
+
|
|
830
|
+
### Dynamic queries — rebuild unionCollections when filters change
|
|
831
|
+
|
|
832
|
+
Because `unionCollections` is inside `organise`, you can recompute it reactively when your filter state changes. Call `dataRef.current?.handleRefresh()` after updating state.
|
|
833
|
+
|
|
834
|
+
```tsx
|
|
835
|
+
const [installer, setInstaller] = useState("");
|
|
836
|
+
const [startDate, setStartDate] = useState("");
|
|
837
|
+
const [endDate, setEndDate] = useState("");
|
|
838
|
+
|
|
839
|
+
const buildOrganise = () => {
|
|
840
|
+
const dateQuery = startDate && endDate
|
|
841
|
+
? [{ queryId: "date", type: "><", value: [new Date(startDate).toISOString(), new Date(endDate).toISOString()] }]
|
|
842
|
+
: [];
|
|
843
|
+
|
|
844
|
+
const personQuery = installer
|
|
845
|
+
? [{ type: "search", searchIds: ["installer", "inspector"], value: installer }]
|
|
846
|
+
: [];
|
|
847
|
+
|
|
848
|
+
return {
|
|
849
|
+
unionCollections: [
|
|
850
|
+
{
|
|
851
|
+
collection: "deliveries",
|
|
852
|
+
source: "Delivery",
|
|
853
|
+
query: [...dateQuery, ...personQuery],
|
|
854
|
+
projection: { _id: 1, name: 1, status: 1, installer: 1, date: 1 },
|
|
855
|
+
addFields: { sortDate: { $ifNull: ["$date", "$createdAt"] } },
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
collection: "service-tickets",
|
|
859
|
+
source: "Service Ticket",
|
|
860
|
+
query: [...dateQuery, ...personQuery],
|
|
861
|
+
projection: { _id: 1, name: 1, status: 1, installer: 1, service_date: 1 },
|
|
862
|
+
addFields: { sortDate: { $ifNull: ["$service_date", "$createdAt"] } },
|
|
863
|
+
},
|
|
864
|
+
],
|
|
865
|
+
sort: { sortId: "sortDate", type: "desc" as const },
|
|
866
|
+
};
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
// Re-fetch whenever filters change
|
|
870
|
+
useEffect(() => {
|
|
871
|
+
dataRef.current?.handleRefresh();
|
|
872
|
+
}, [installer, startDate, endDate]);
|
|
873
|
+
|
|
874
|
+
<DataTemplateWrapper
|
|
875
|
+
...
|
|
876
|
+
organise={buildOrganise()}
|
|
877
|
+
/>
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
---
|
|
881
|
+
|
|
882
|
+
### Schema for union views
|
|
883
|
+
|
|
884
|
+
All fields that may appear across any of the unioned collections must be declared in `schema`. Mark them `optional: true` and `hideForm: true` since they are read-only display columns.
|
|
885
|
+
|
|
886
|
+
```tsx
|
|
887
|
+
schema={[
|
|
888
|
+
{ id: "source", name: "Source", type: "string", hideForm: true },
|
|
889
|
+
{ id: "name", name: "Name", type: "string", hideForm: true, optional: true },
|
|
890
|
+
{ id: "status", name: "Status", type: "string", hideForm: true, optional: true },
|
|
891
|
+
{ id: "installer", name: "Installer", type: "string", hideForm: true, optional: true },
|
|
892
|
+
{ id: "date", name: "Date", type: "date", hideForm: true, optional: true },
|
|
893
|
+
{ id: "service_date", name: "Svc Date", type: "date", hideForm: true, optional: true },
|
|
894
|
+
]}
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
---
|
|
898
|
+
|
|
899
|
+
## Ref Methods
|
|
900
|
+
|
|
901
|
+
| Method | Description |
|
|
902
|
+
| ---------------------------------- | ----------------------------------------------------------------- |
|
|
903
|
+
| `handleRefresh()` | Re-fetches the current list data |
|
|
904
|
+
| `handleFormSubmit()` | Programmatically submits the form |
|
|
905
|
+
| `handleFormCheck()` | Validates the form without submitting |
|
|
906
|
+
| `handleUpdateSelectedData(fields)` | Prefills form fields. `fields` is `{ key: string; value: any }[]` |
|
|
907
|
+
| `handleDeleteModal()` | Opens the delete confirmation modal |
|
|
908
|
+
| `handleExportReadPDF()` | Triggers PDF export of the current read view |
|
|
909
|
+
| `handleUploadOfflineModal()` | Opens the offline data upload modal |
|
|
910
|
+
|
|
911
|
+
---
|
|
912
|
+
|
|
913
|
+
## Props
|
|
914
|
+
|
|
915
|
+
| Name | Type | Default | Description |
|
|
916
|
+
| ----------------- | --------------------------------------- | --------- | ---------------------------------------------------------------------------------------- |
|
|
917
|
+
| `title` | `string \| ReactElement` | — | Title shown in the header |
|
|
918
|
+
| `description` | `string \| ReactElement` | `""` | Description shown below the title |
|
|
919
|
+
| `type` | `"list" \| "read" \| "update"` | — | Current view mode |
|
|
920
|
+
| `id` | `string` | — | Selected record ID. Pass `"new"` for a blank create form |
|
|
921
|
+
| `collectionId` | `string` | — | Database collection ID |
|
|
922
|
+
| `schema` | `DataStructure[]` | — | Column/field definitions (same shape as `DataTemplate`'s `model`) |
|
|
923
|
+
| `workspace` | `WorkspaceDevSettings` | — | Backend connection config — see Workspace below |
|
|
924
|
+
| `space` | `Space` | — | Full Space object — enables tabs, sort, and additional queries |
|
|
925
|
+
| `edit` | `Edit` | see below | Create/update/delete permissions — see DataTemplate's Edit |
|
|
926
|
+
| `organise` | `Organise` | — | View, sort, filter, export, and tab config — see DataTemplate's Organise |
|
|
927
|
+
| `bareSettings` | `BareSettings` | — | Custom components and callbacks — see Bare Settings below |
|
|
928
|
+
| `renderSettings` | `RenderSettings` | — | Override the list or loading UI |
|
|
929
|
+
| `viewSettings` | `ViewSettings` | — | Shorthand for setting up grid/list/calendar/feeds/kanban views — see View Settings below |
|
|
930
|
+
| `additionalQuery` | `AdditionalQuery[]` | — | Filter controls rendered above the list |
|
|
931
|
+
| `sort` | `Sort` | — | Default sort applied to list queries |
|
|
932
|
+
| `limit` | `number` | `10` | Records per page |
|
|
933
|
+
| `groupId` | `string` | — | Groups list results by this field |
|
|
934
|
+
| `uniqueLabel` | `string` | — | Appended to internal query keys to avoid collisions |
|
|
935
|
+
| `schemaReference` | `{ [key: string]: DataStructure[] }` | — | Hardcoded schema reference (bypasses schema endpoint) |
|
|
936
|
+
| `newTabUrl` | `{ default?: string; addons?: string }` | — | URL config for row links |
|
|
937
|
+
| `lib` | `LibSettings` | — | Firebase storage helpers — see Lib below |
|
|
938
|
+
| `handleNavigate` | `(type, selectedId, viewId) => void` | — | Called when the component needs to navigate between views |
|
|
939
|
+
| `handleComplete` | `(id: string) => void` | — | Called after a create/update operation completes |
|
|
940
|
+
|
|
941
|
+
## Workspace
|
|
942
|
+
|
|
943
|
+
Replaces `DataTemplate`'s `devSettings`. The wrapper calls `parseWorkspaceSettings` internally to build the full config.
|
|
944
|
+
|
|
945
|
+
| Name | Type | Description |
|
|
946
|
+
| --------------------- | ----------------------------------- | ---------------------------------------------- |
|
|
947
|
+
| `apiEndpointType` | `"mongoDB" \| "firebase" \| string` | Maps to `devSettings.server` |
|
|
948
|
+
| `apiEndpoint` | `string` | Database API base URL |
|
|
949
|
+
| `apiEndpointToken` | `string` | API token |
|
|
950
|
+
| `apiUserToken` | `string` | Per-user auth token |
|
|
951
|
+
| `assetEndpoint` | `string` | Asset retrieval base URL |
|
|
952
|
+
| `assetUploadEndpoint` | `string` | Upload endpoint |
|
|
953
|
+
| `assetBucket` | `string` | Storage bucket name (AWS/GCS) |
|
|
954
|
+
| `maxFileSize` | `number` | Global max file size in MB |
|
|
955
|
+
| `organisationMembers` | `Option[]` | Member list for `person` / `listPerson` fields |
|
|
956
|
+
|
|
957
|
+
---
|
|
958
|
+
|
|
959
|
+
## Lib
|
|
960
|
+
|
|
961
|
+
Only Firebase storage helpers are needed here — axios, headlessUI, and papaparse are bundled internally.
|
|
962
|
+
|
|
963
|
+
| Name | Type | Description |
|
|
964
|
+
| ---------------- | ----- | ---------------------------------- |
|
|
965
|
+
| `storage` | `any` | Firebase Storage instance |
|
|
966
|
+
| `ref` | `any` | Firebase `ref` function |
|
|
967
|
+
| `uploadBytes` | `any` | Firebase `uploadBytes` function |
|
|
968
|
+
| `getDownloadURL` | `any` | Firebase `getDownloadURL` function |
|
|
969
|
+
|
|
970
|
+
---
|
|
971
|
+
|
|
972
|
+
## Organise
|
|
973
|
+
|
|
974
|
+
The `organise` prop is the same type as `DataTemplate`'s `Organise` — refer to [DataTemplate's Organise prop](?path=/story/data--data-template) for the full list of options.
|
|
975
|
+
|
|
976
|
+
A few keys are handled differently in `DataTemplateWrapper`:
|
|
977
|
+
|
|
978
|
+
| Key | Behaviour |
|
|
979
|
+
| ------------------------- | -------------------------------------------------------------------------------- |
|
|
980
|
+
| `handleNavigate` | Use the top-level `handleNavigate` prop instead — it is merged in automatically |
|
|
981
|
+
| `additionalQuery` | Use the top-level `additionalQuery` prop instead — it is merged in automatically |
|
|
982
|
+
| `sort` | Use the top-level `sort` prop instead — it is merged in automatically |
|
|
983
|
+
| `viewId` / `extendedTabs` | Use `viewSettings` instead — the wrapper builds these for you |
|
|
984
|
+
|
|
985
|
+
Everything else (`breadcrumbType`, `tabAlign`, `splitView`, `exportSettings`, `advSortEnabled`, `advFilterEnabled`, `groupIdCollapsible`, etc.) works exactly the same as in `DataTemplate` and can be passed directly via `organise`.
|
|
986
|
+
|
|
987
|
+
---
|
|
988
|
+
|
|
989
|
+
## View Settings
|
|
990
|
+
|
|
991
|
+
A shorthand that builds the `extendedTabs` and `viewId` config for you. Only one view type can be active at a time — the first matching key wins in this order: `listImage` → `calendarSettings` → `feedsSettings` → `kanbanSettings` → `gridImage`.
|
|
992
|
+
|
|
993
|
+
| Name | Type | Description |
|
|
994
|
+
| ------------------ | ------------------ | --------------------------------------------------------------- |
|
|
995
|
+
| `gridImage` | `string` | Column ID of an image field — activates grid view |
|
|
996
|
+
| `gridScroll` | `boolean` | Enables infinite scroll in grid view |
|
|
997
|
+
| `gridContain` | `boolean` | Uses `object-contain` instead of `object-cover` for grid images |
|
|
998
|
+
| `listImage` | `string` | Column ID of an image field — activates list view |
|
|
999
|
+
| `calendarSettings` | `CalendarSettings` | Activates calendar view — see CalendarSettings below |
|
|
1000
|
+
| `feedsSettings` | `FeedsSettings` | Activates feeds view — see DataTemplate's Feeds Settings |
|
|
1001
|
+
| `kanbanSettings` | `KanbanSettings` | Activates kanban view — see DataTemplate's Kanban Settings |
|
|
1002
|
+
|
|
1003
|
+
### CalendarSettings
|
|
1004
|
+
|
|
1005
|
+
| Name | Type | Description | Optional |
|
|
1006
|
+
| ---------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- |
|
|
1007
|
+
| `title` | `string` | Column ID for the event title | |
|
|
1008
|
+
| `startDate` | `string` | Column ID for the event start date | |
|
|
1009
|
+
| `endDate` | `string` | Column ID for the event end date | ✓ |
|
|
1010
|
+
| `groupId` | `string` | Column ID — events with the same value share a background colour | ✓ |
|
|
1011
|
+
| `groupColor` | `Color[]` | Background colours for groups | ✓ |
|
|
1012
|
+
| `defaultStartDate` | `Date` | Initial date the calendar opens on | ✓ |
|
|
1013
|
+
| `defaultView` | `string` | Initial calendar view — e.g. `"dayGridMonth"`, `"timeGridWeek"`, `"timeGridDay"`, `"listMonth"` | ✓ |
|
|
1014
|
+
| `customPlugin` | `any` | A FullCalendar-compatible plugin. When set, the default view switches to `"timeGridWeek"` unless overridden | ✓ |
|
|
1015
|
+
| `customView` | `{ view: "week"; duration: number }` | Multi-week span for week view — e.g. `{ view: "week", duration: 2 }` shows a 2-week range | ✓ |
|
|
1016
|
+
| `headerToolbar` | `{ left?: string; center?: string; right?: string }` | Override the calendar header toolbar buttons | ✓ |
|
|
1017
|
+
| `footerToolbar` | `{ left?: string; center?: string; right?: string }` | Override the calendar footer toolbar buttons | ✓ |
|
|
1018
|
+
| `customButtons` | `{ [key: string]: any }` | Define custom toolbar buttons — keys match button names used in `headerToolbar`/`footerToolbar` | ✓ |
|
|
1019
|
+
| `customHeader` | `string` | Static string to override the toolbar title | ✓ |
|
|
1020
|
+
| `handleCalendarResize` | `(calendarRef: RefObject<any>, isMobileWidth: boolean) => void` | Called when the calendar container resizes — use to switch views on mobile | ✓ |
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
### Bare Settings
|
|
1025
|
+
|
|
1026
|
+
| Name | Type | Description |
|
|
1027
|
+
| ---------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
1028
|
+
| `bareReadComponent` | `(data: any) => ReactElement` | Custom component rendered in read view |
|
|
1029
|
+
| `bareReadComponentPos` | `"top"` | Renders `bareReadComponent` above the default read content |
|
|
1030
|
+
| `bareReadComponentHide` | `boolean` | Hides the default read content |
|
|
1031
|
+
| `bareReadDescription` | `(key, value) => any` | Custom renderer for read view field values |
|
|
1032
|
+
| `bareReadReferenceView` | `(dataId, collectionId, attributeId) => boolean \| void` | Controls whether a reference field opens a view |
|
|
1033
|
+
| `bareReadHandleData` | `(data: any) => void` | Called when read view data is loaded |
|
|
1034
|
+
| `bareReadListTabs` | `boolean` | Shows list-style tabs in read view |
|
|
1035
|
+
| `bareListComponent` | `(data, prettierData, loading, pageIndex) => ReactElement` | Fully replaces the list UI |
|
|
1036
|
+
| `bareListActionBtns` | `(data: any) => ReactElement[]` | Extra icon buttons on each list row |
|
|
1037
|
+
| `bareListMultipleSelectAddtionalActions` | `{ text: string; className?: string; onClick: (ids, selectAll, query) => void }[]` | Extra bulk-action buttons when rows are selected |
|
|
1038
|
+
| `bareListGridComponent` | `(data: any) => ReactElement` | Custom card renderer for grid view |
|
|
1039
|
+
| `bareListGridColClassName` | `string` | Tailwind class for grid column width |
|
|
1040
|
+
| `bareListGridTags` | `(data: any) => string[]` | Tag strings shown on grid cards |
|
|
1041
|
+
| `bareListGridRibbonText` | `(data: any) => string` | Ribbon text on grid cards |
|
|
1042
|
+
| `bareListGridFallbackImg` | `(data: any) => string \| ReactElement` | Fallback image for grid cards |
|
|
1043
|
+
| `bareListHandleData` | `(data, prettierData) => void` | Called when list data is loaded |
|
|
1044
|
+
| `bareListHandleFilter` | `(search, select, date, reference) => void` | Called when any filter changes |
|
|
1045
|
+
| `bareListHandleSelection` | `(selectAll, selectedIds, query?) => void` | Called when row selection changes |
|
|
1046
|
+
| `bareListDescription` | `(key, value) => any` | Custom renderer for list view field values |
|
|
1047
|
+
| `bareListFilterQuery` | `Query[]` | Additional queries always applied to list fetches |
|
|
1048
|
+
| `bareListFilterAddComponent` | `ReactElement[]` | Extra filter components in the filter bar |
|
|
1049
|
+
| `bareListPipeline` | `PipeLine[]` | Additional MongoDB aggregation pipeline stages |
|
|
1050
|
+
| `bareListFilterSelected` | `(filterSearchParams, filterSelection, filterReference, filterReferenceDate) => void` | Called with the current filter state on every change |
|
|
1051
|
+
| `bareUpdatePrefill` | `(data: any) => { key: string; value: any }[]` | Returns prefill values for the update form |
|
|
1052
|
+
| `bareUpdateComponent` | `(data: any) => ReactElement` | Custom component rendered inside the update form |
|
|
1053
|
+
| `bareUpdateComponentAbove` | `boolean` | Renders `bareUpdateComponent` above the form fields |
|
|
1054
|
+
| `bareUpdateActionComponent` | `(data: any) => ReactElement` | Custom action buttons in the form footer |
|
|
1055
|
+
| `bareUpdateColumnType` | `"one" \| "two" \| "four"` | Form column layout |
|
|
1056
|
+
| `bareUpdateMobileView` | `boolean` | Mobile-optimised form layout |
|
|
1057
|
+
| `bareUpdateActionBottom` | `boolean` | Moves action buttons to the bottom of the form |
|
|
1058
|
+
| `bareUpdateHandleData` | `(data, changedAttributeIds?) => void` | Called on every form field change |
|
|
1059
|
+
| `bareUpdateHandlePreComplete` | `(data: any) => Promise<boolean>` | Called before save — return `false` to cancel |
|
|
1060
|
+
| `bareUpdateHandleComplete` | `(data: any) => void` | Called after save completes |
|
|
1061
|
+
| `bareUpdateHandlePostComplete` | `(data: any) => void` | Called after post-save operations complete |
|
|
1062
|
+
| `bareUpdateTabId` | `(tabId: string) => void` | Called when the active form tab changes |
|
|
1063
|
+
| `bareUpdateTabComponent` | `(tabIds: string[]) => { tabId: string; icon?: ReactElement; skip?: boolean }[]` | Customise tab icons or skip specific tabs in the form |
|
|
1064
|
+
| `bareUpdateIndividualComponent` | `(columnId, ref, selectedData, setSelectedData) => ReactElement \| null` | Replaces a specific form field with a custom component |
|
|
1065
|
+
| `bareReferenceDel` | `{ collectionId, collectionName, columnId, schema }[]` | Blocks deletion when the record is referenced elsewhere |
|
|
1066
|
+
| `bareOffline` | `boolean` | Enables offline data caching |
|
|
1067
|
+
| `bareCSVImportEndpoint` | `string` | Custom CSV import endpoint |
|
|
1068
|
+
| `bareCSVExportEndpoint` | `string` | Custom CSV export endpoint |
|
|
1069
|
+
|
|
1070
|
+
---
|
|
1071
|
+
|
|
1072
|
+
## Render Settings
|
|
1073
|
+
|
|
1074
|
+
| Name | Type | Description |
|
|
1075
|
+
| ----------------- | ------------------------------------------------------------------- | ------------------------------------ |
|
|
1076
|
+
| `renderListUI` | `(actionComponent, filterComponent, dataComponent) => ReactElement` | Fully replaces the list layout |
|
|
1077
|
+
| `renderLoadingUI` | `() => ReactElement` | Replaces the default loading spinner |
|