@multiplatform.one/frappe-ui 7.10.0 → 7.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cjs/FrappeDashboard.cjs +8 -10
- package/dist/cjs/FrappeDashboard.native.js +8 -14
- package/dist/cjs/FrappeDashboard.native.js.map +1 -1
- package/dist/cjs/FrappeForm.cjs +465 -175
- package/dist/cjs/FrappeForm.native.js +611 -207
- package/dist/cjs/FrappeForm.native.js.map +1 -1
- package/dist/cjs/FrappeFormConnections.cjs +125 -0
- package/dist/cjs/FrappeFormConnections.native.js +149 -0
- package/dist/cjs/FrappeFormConnections.native.js.map +1 -0
- package/dist/cjs/FrappeImageView.cjs +7 -9
- package/dist/cjs/FrappeImageView.native.js +7 -13
- package/dist/cjs/FrappeImageView.native.js.map +1 -1
- package/dist/cjs/FrappeList.cjs +29 -4
- package/dist/cjs/FrappeList.native.js +37 -5
- package/dist/cjs/FrappeList.native.js.map +1 -1
- package/dist/cjs/FrappeListView.cjs +102 -29
- package/dist/cjs/FrappeListView.native.js +109 -29
- package/dist/cjs/FrappeListView.native.js.map +1 -1
- package/dist/cjs/FrappeProvider.cjs +7 -2
- package/dist/cjs/FrappeProvider.native.js +7 -2
- package/dist/cjs/FrappeProvider.native.js.map +1 -1
- package/dist/cjs/FrappeReportBuilder.cjs +10 -11
- package/dist/cjs/FrappeReportBuilder.native.js +10 -16
- package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
- package/dist/cjs/FrappeRowsReader.cjs +49 -0
- package/dist/cjs/FrappeRowsReader.native.js +53 -0
- package/dist/cjs/FrappeRowsReader.native.js.map +1 -0
- package/dist/cjs/FrappeTable.cjs +18 -6
- package/dist/cjs/FrappeTable.native.js +24 -6
- package/dist/cjs/FrappeTable.native.js.map +1 -1
- package/dist/cjs/fieldRegistry.cjs +36 -11
- package/dist/cjs/fieldRegistry.native.js +59 -11
- package/dist/cjs/fieldRegistry.native.js.map +1 -1
- package/dist/cjs/fields/Date/index.native.js +5 -2
- package/dist/cjs/fields/Date/index.native.js.map +1 -1
- package/dist/cjs/fields/Date/sheetKeyboard.cjs +2 -2
- package/dist/cjs/fields/Date/sheetKeyboard.native.js +2 -2
- package/dist/cjs/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/cjs/fields/DateRange/index.cjs +2 -1
- package/dist/cjs/fields/DateRange/index.native.js +4 -1
- package/dist/cjs/fields/DateRange/index.native.js.map +1 -1
- package/dist/cjs/formEngine/index.cjs +4 -2
- package/dist/cjs/formEngine/index.native.js +3 -1
- package/dist/cjs/formEngine/index.native.js.map +1 -1
- package/dist/cjs/formEngine/useSessionRoles.cjs +77 -0
- package/dist/cjs/formEngine/useSessionRoles.native.js +84 -0
- package/dist/cjs/formEngine/useSessionRoles.native.js.map +1 -0
- package/dist/cjs/frappeConnections.cjs +41 -0
- package/dist/cjs/frappeConnections.native.js +50 -0
- package/dist/cjs/frappeConnections.native.js.map +1 -0
- package/dist/cjs/frappeNavigation.cjs +107 -0
- package/dist/cjs/frappeNavigation.native.js +112 -0
- package/dist/cjs/frappeNavigation.native.js.map +1 -0
- package/dist/cjs/index.cjs +18 -1
- package/dist/cjs/index.native.js +18 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/schemaForm/index.cjs +4 -1
- package/dist/cjs/schemaForm/index.native.js +4 -1
- package/dist/cjs/schemaForm/index.native.js.map +1 -1
- package/dist/cjs/useConnectionFilters.cjs +146 -0
- package/dist/cjs/useConnectionFilters.native.js +213 -0
- package/dist/cjs/useConnectionFilters.native.js.map +1 -0
- package/dist/cjs/useFrappePagination.cjs +37 -18
- package/dist/cjs/useFrappePagination.native.js +59 -23
- package/dist/cjs/useFrappePagination.native.js.map +1 -1
- package/dist/cjs/useFrappeRows.native.js.map +1 -1
- package/dist/esm/FrappeDashboard.mjs +9 -11
- package/dist/esm/FrappeDashboard.mjs.map +1 -1
- package/dist/esm/FrappeDashboard.native.js +9 -15
- package/dist/esm/FrappeDashboard.native.js.map +1 -1
- package/dist/esm/FrappeForm.mjs +471 -181
- package/dist/esm/FrappeForm.mjs.map +1 -1
- package/dist/esm/FrappeForm.native.js +599 -195
- package/dist/esm/FrappeForm.native.js.map +1 -1
- package/dist/esm/FrappeFormConnections.mjs +99 -0
- package/dist/esm/FrappeFormConnections.mjs.map +1 -0
- package/dist/esm/FrappeFormConnections.native.js +120 -0
- package/dist/esm/FrappeFormConnections.native.js.map +1 -0
- package/dist/esm/FrappeImageView.mjs +8 -10
- package/dist/esm/FrappeImageView.mjs.map +1 -1
- package/dist/esm/FrappeImageView.native.js +8 -14
- package/dist/esm/FrappeImageView.native.js.map +1 -1
- package/dist/esm/FrappeList.mjs +31 -6
- package/dist/esm/FrappeList.mjs.map +1 -1
- package/dist/esm/FrappeList.native.js +39 -7
- package/dist/esm/FrappeList.native.js.map +1 -1
- package/dist/esm/FrappeListView.mjs +103 -30
- package/dist/esm/FrappeListView.mjs.map +1 -1
- package/dist/esm/FrappeListView.native.js +110 -30
- package/dist/esm/FrappeListView.native.js.map +1 -1
- package/dist/esm/FrappeProvider.mjs +7 -2
- package/dist/esm/FrappeProvider.mjs.map +1 -1
- package/dist/esm/FrappeProvider.native.js +7 -2
- package/dist/esm/FrappeProvider.native.js.map +1 -1
- package/dist/esm/FrappeReportBuilder.mjs +11 -12
- package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
- package/dist/esm/FrappeReportBuilder.native.js +11 -17
- package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
- package/dist/esm/FrappeRowsReader.mjs +24 -0
- package/dist/esm/FrappeRowsReader.mjs.map +1 -0
- package/dist/esm/FrappeRowsReader.native.js +25 -0
- package/dist/esm/FrappeRowsReader.native.js.map +1 -0
- package/dist/esm/FrappeTable.mjs +18 -6
- package/dist/esm/FrappeTable.mjs.map +1 -1
- package/dist/esm/FrappeTable.native.js +24 -6
- package/dist/esm/FrappeTable.native.js.map +1 -1
- package/dist/esm/fieldRegistry.mjs +35 -12
- package/dist/esm/fieldRegistry.mjs.map +1 -1
- package/dist/esm/fieldRegistry.native.js +58 -12
- package/dist/esm/fieldRegistry.native.js.map +1 -1
- package/dist/esm/fields/Date/index.native.js +6 -3
- package/dist/esm/fields/Date/index.native.js.map +1 -1
- package/dist/esm/fields/Date/sheetKeyboard.mjs +2 -2
- package/dist/esm/fields/Date/sheetKeyboard.mjs.map +1 -1
- package/dist/esm/fields/Date/sheetKeyboard.native.js +2 -2
- package/dist/esm/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/esm/fields/DateRange/index.mjs +2 -1
- package/dist/esm/fields/DateRange/index.mjs.map +1 -1
- package/dist/esm/fields/DateRange/index.native.js +4 -1
- package/dist/esm/fields/DateRange/index.native.js.map +1 -1
- package/dist/esm/formEngine/index.mjs +2 -1
- package/dist/esm/formEngine/index.mjs.map +1 -1
- package/dist/esm/formEngine/index.native.js +2 -1
- package/dist/esm/formEngine/index.native.js.map +1 -1
- package/dist/esm/formEngine/useSessionRoles.mjs +52 -0
- package/dist/esm/formEngine/useSessionRoles.mjs.map +1 -0
- package/dist/esm/formEngine/useSessionRoles.native.js +56 -0
- package/dist/esm/formEngine/useSessionRoles.native.js.map +1 -0
- package/dist/esm/frappeConnections.mjs +15 -0
- package/dist/esm/frappeConnections.mjs.map +1 -0
- package/dist/esm/frappeConnections.native.js +21 -0
- package/dist/esm/frappeConnections.native.js.map +1 -0
- package/dist/esm/frappeNavigation.mjs +78 -0
- package/dist/esm/frappeNavigation.mjs.map +1 -0
- package/dist/esm/frappeNavigation.native.js +80 -0
- package/dist/esm/frappeNavigation.native.js.map +1 -0
- package/dist/esm/index.mjs +7 -3
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +7 -3
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/schemaForm/index.mjs +4 -1
- package/dist/esm/schemaForm/index.mjs.map +1 -1
- package/dist/esm/schemaForm/index.native.js +4 -1
- package/dist/esm/schemaForm/index.native.js.map +1 -1
- package/dist/esm/useConnectionFilters.mjs +121 -0
- package/dist/esm/useConnectionFilters.mjs.map +1 -0
- package/dist/esm/useConnectionFilters.native.js +185 -0
- package/dist/esm/useConnectionFilters.native.js.map +1 -0
- package/dist/esm/useFrappePagination.mjs +38 -19
- package/dist/esm/useFrappePagination.mjs.map +1 -1
- package/dist/esm/useFrappePagination.native.js +60 -24
- package/dist/esm/useFrappePagination.native.js.map +1 -1
- package/dist/esm/useFrappeRows.mjs.map +1 -1
- package/dist/esm/useFrappeRows.native.js.map +1 -1
- package/dist/jsx/FrappeDashboard.mjs +9 -11
- package/dist/jsx/FrappeDashboard.mjs.map +1 -1
- package/dist/jsx/FrappeDashboard.native.js +8 -14
- package/dist/jsx/FrappeDashboard.native.js.map +1 -1
- package/dist/jsx/FrappeForm.mjs +471 -181
- package/dist/jsx/FrappeForm.mjs.map +1 -1
- package/dist/jsx/FrappeForm.native.js +611 -207
- package/dist/jsx/FrappeForm.native.js.map +1 -1
- package/dist/jsx/FrappeFormConnections.mjs +99 -0
- package/dist/jsx/FrappeFormConnections.mjs.map +1 -0
- package/dist/jsx/FrappeFormConnections.native.js +149 -0
- package/dist/jsx/FrappeFormConnections.native.js.map +1 -0
- package/dist/jsx/FrappeImageView.mjs +8 -10
- package/dist/jsx/FrappeImageView.mjs.map +1 -1
- package/dist/jsx/FrappeImageView.native.js +7 -13
- package/dist/jsx/FrappeImageView.native.js.map +1 -1
- package/dist/jsx/FrappeList.mjs +31 -6
- package/dist/jsx/FrappeList.mjs.map +1 -1
- package/dist/jsx/FrappeList.native.js +37 -5
- package/dist/jsx/FrappeList.native.js.map +1 -1
- package/dist/jsx/FrappeListView.mjs +103 -30
- package/dist/jsx/FrappeListView.mjs.map +1 -1
- package/dist/jsx/FrappeListView.native.js +109 -29
- package/dist/jsx/FrappeListView.native.js.map +1 -1
- package/dist/jsx/FrappeProvider.mjs +7 -2
- package/dist/jsx/FrappeProvider.mjs.map +1 -1
- package/dist/jsx/FrappeProvider.native.js +7 -2
- package/dist/jsx/FrappeProvider.native.js.map +1 -1
- package/dist/jsx/FrappeReportBuilder.mjs +11 -12
- package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
- package/dist/jsx/FrappeReportBuilder.native.js +10 -16
- package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
- package/dist/jsx/FrappeRowsReader.mjs +24 -0
- package/dist/jsx/FrappeRowsReader.mjs.map +1 -0
- package/dist/jsx/FrappeRowsReader.native.js +53 -0
- package/dist/jsx/FrappeRowsReader.native.js.map +1 -0
- package/dist/jsx/FrappeTable.mjs +18 -6
- package/dist/jsx/FrappeTable.mjs.map +1 -1
- package/dist/jsx/FrappeTable.native.js +24 -6
- package/dist/jsx/FrappeTable.native.js.map +1 -1
- package/dist/jsx/fieldRegistry.mjs +35 -12
- package/dist/jsx/fieldRegistry.mjs.map +1 -1
- package/dist/jsx/fieldRegistry.native.js +59 -11
- package/dist/jsx/fieldRegistry.native.js.map +1 -1
- package/dist/jsx/fields/Date/index.native.js +5 -2
- package/dist/jsx/fields/Date/index.native.js.map +1 -1
- package/dist/jsx/fields/Date/sheetKeyboard.mjs +2 -2
- package/dist/jsx/fields/Date/sheetKeyboard.mjs.map +1 -1
- package/dist/jsx/fields/Date/sheetKeyboard.native.js +2 -2
- package/dist/jsx/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/jsx/fields/DateRange/index.mjs +2 -1
- package/dist/jsx/fields/DateRange/index.mjs.map +1 -1
- package/dist/jsx/fields/DateRange/index.native.js +4 -1
- package/dist/jsx/fields/DateRange/index.native.js.map +1 -1
- package/dist/jsx/formEngine/index.mjs +2 -1
- package/dist/jsx/formEngine/index.mjs.map +1 -1
- package/dist/jsx/formEngine/index.native.js +3 -1
- package/dist/jsx/formEngine/index.native.js.map +1 -1
- package/dist/jsx/formEngine/useSessionRoles.mjs +52 -0
- package/dist/jsx/formEngine/useSessionRoles.mjs.map +1 -0
- package/dist/jsx/formEngine/useSessionRoles.native.js +84 -0
- package/dist/jsx/formEngine/useSessionRoles.native.js.map +1 -0
- package/dist/jsx/frappeConnections.mjs +15 -0
- package/dist/jsx/frappeConnections.mjs.map +1 -0
- package/dist/jsx/frappeConnections.native.js +50 -0
- package/dist/jsx/frappeConnections.native.js.map +1 -0
- package/dist/jsx/frappeNavigation.mjs +78 -0
- package/dist/jsx/frappeNavigation.mjs.map +1 -0
- package/dist/jsx/frappeNavigation.native.js +112 -0
- package/dist/jsx/frappeNavigation.native.js.map +1 -0
- package/dist/jsx/index.js +7 -3
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +7 -3
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +18 -1
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/schemaForm/index.mjs +4 -1
- package/dist/jsx/schemaForm/index.mjs.map +1 -1
- package/dist/jsx/schemaForm/index.native.js +4 -1
- package/dist/jsx/schemaForm/index.native.js.map +1 -1
- package/dist/jsx/useConnectionFilters.mjs +121 -0
- package/dist/jsx/useConnectionFilters.mjs.map +1 -0
- package/dist/jsx/useConnectionFilters.native.js +213 -0
- package/dist/jsx/useConnectionFilters.native.js.map +1 -0
- package/dist/jsx/useFrappePagination.mjs +38 -19
- package/dist/jsx/useFrappePagination.mjs.map +1 -1
- package/dist/jsx/useFrappePagination.native.js +59 -23
- package/dist/jsx/useFrappePagination.native.js.map +1 -1
- package/dist/jsx/useFrappeRows.mjs.map +1 -1
- package/dist/jsx/useFrappeRows.native.js.map +1 -1
- package/package.json +26 -22
- package/src/FileBrowser/AttachLibrary.stories.tsx +23 -0
- package/src/FrappeDashboard.tsx +11 -16
- package/src/FrappeForm.connections.spec.tsx +280 -0
- package/src/FrappeForm.connections.stories.tsx +93 -0
- package/src/FrappeForm.deskSemantics.spec.tsx +284 -0
- package/src/FrappeForm.tsx +560 -145
- package/src/FrappeFormConnections.tsx +148 -0
- package/src/FrappeGuestMeta.spec.tsx +143 -0
- package/src/FrappeImageView.tsx +8 -12
- package/src/FrappeList.tsx +71 -15
- package/src/FrappeListView.cards.spec.tsx +175 -0
- package/src/FrappeListView.cards.stories.tsx +131 -0
- package/src/FrappeListView.connections.spec.tsx +192 -0
- package/src/FrappeListView.connections.stories.tsx +99 -0
- package/src/FrappeListView.seed.spec.tsx +228 -0
- package/src/FrappeListView.tsx +177 -31
- package/src/FrappeProvider.catchUp.spec.tsx +502 -0
- package/src/FrappeProvider.tsx +15 -3
- package/src/FrappeReportBuilder.tsx +11 -19
- package/src/FrappeRowsReader.tsx +38 -0
- package/src/FrappeTable.optionalProps.spec.tsx +249 -0
- package/src/FrappeTable.spec.tsx +32 -42
- package/src/FrappeTable.stories.spec.tsx +55 -0
- package/src/FrappeTable.stories.tsx +28 -37
- package/src/FrappeTable.tsx +108 -45
- package/src/childTableDependsOn.spec.tsx +85 -0
- package/src/desk/DeskFormSidebar.spec.tsx +20 -0
- package/src/desk/DeskFormSidebar.stories.tsx +42 -0
- package/src/fieldRegistry.spec.tsx +27 -64
- package/src/fieldRegistry.ts +71 -14
- package/src/fields/Date/index.native.tsx +4 -2
- package/src/fields/Date/sheetKeyboard.spec.ts +14 -5
- package/src/fields/Date/sheetKeyboard.ts +12 -4
- package/src/fields/DateRange/DateRange.spec.tsx +67 -0
- package/src/fields/DateRange/index.tsx +5 -1
- package/src/fields/LinkTree.spec.tsx +8 -0
- package/src/fields/LinkTree.stories.tsx +28 -5
- package/src/fields/Select/Select.hydration.spec.tsx +177 -0
- package/src/formEngine/doctypeMeta.ts +15 -114
- package/src/formEngine/index.ts +2 -0
- package/src/formEngine/seam.spec.ts +23 -4
- package/src/formEngine/useSessionRoles.ts +73 -0
- package/src/frappeConnections.ts +50 -0
- package/src/frappeNavigation.tsx +112 -0
- package/src/frappeRoutes.spec.tsx +335 -0
- package/src/index.ts +28 -1
- package/src/schemaForm/index.tsx +1 -0
- package/src/useConnectionFilters.tsx +157 -0
- package/src/useFrappePagination.ts +70 -26
- package/src/useFrappeRows.sharedPath.spec.tsx +206 -0
- package/src/useFrappeRows.ts +5 -4
- package/types/FrappeDashboard.d.ts +3 -4
- package/types/FrappeDashboard.d.ts.map +1 -1
- package/types/FrappeForm.d.ts +57 -5
- package/types/FrappeForm.d.ts.map +1 -1
- package/types/FrappeFormConnections.d.ts +18 -0
- package/types/FrappeFormConnections.d.ts.map +1 -0
- package/types/FrappeImageView.d.ts +2 -3
- package/types/FrappeImageView.d.ts.map +1 -1
- package/types/FrappeList.d.ts +20 -2
- package/types/FrappeList.d.ts.map +1 -1
- package/types/FrappeListView.d.ts +53 -3
- package/types/FrappeListView.d.ts.map +1 -1
- package/types/FrappeProvider.d.ts +10 -3
- package/types/FrappeProvider.d.ts.map +1 -1
- package/types/FrappeReportBuilder.d.ts.map +1 -1
- package/types/FrappeRowsReader.d.ts +26 -0
- package/types/FrappeRowsReader.d.ts.map +1 -0
- package/types/FrappeTable.d.ts +39 -8
- package/types/FrappeTable.d.ts.map +1 -1
- package/types/fieldRegistry.d.ts +17 -3
- package/types/fieldRegistry.d.ts.map +1 -1
- package/types/fields/Date/index.native.d.ts.map +1 -1
- package/types/fields/Date/sheetKeyboard.d.ts +4 -3
- package/types/fields/Date/sheetKeyboard.d.ts.map +1 -1
- package/types/fields/DateRange/index.d.ts.map +1 -1
- package/types/formEngine/doctypeMeta.d.ts +10 -114
- package/types/formEngine/doctypeMeta.d.ts.map +1 -1
- package/types/formEngine/index.d.ts +1 -0
- package/types/formEngine/index.d.ts.map +1 -1
- package/types/formEngine/useSessionRoles.d.ts +19 -0
- package/types/formEngine/useSessionRoles.d.ts.map +1 -0
- package/types/frappeConnections.d.ts +33 -0
- package/types/frappeConnections.d.ts.map +1 -0
- package/types/frappeNavigation.d.ts +49 -0
- package/types/frappeNavigation.d.ts.map +1 -0
- package/types/index.d.ts +7 -3
- package/types/index.d.ts.map +1 -1
- package/types/schemaForm/index.d.ts.map +1 -1
- package/types/useConnectionFilters.d.ts +34 -0
- package/types/useConnectionFilters.d.ts.map +1 -0
- package/types/useFrappePagination.d.ts +18 -5
- package/types/useFrappePagination.d.ts.map +1 -1
- package/types/useFrappeRows.d.ts +5 -4
- package/types/useFrappeRows.d.ts.map +1 -1
- package/src/FileBrowser/AttachLibrary.spec.tsx +0 -74
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
3
|
+
import type { FrappeConnection } from "./frappeConnections";
|
|
4
|
+
import { FrappeRowsReader, type FrappeRowsSnapshot } from "./FrappeRowsReader";
|
|
5
|
+
import type { FrappeRowsConnection } from "./useFrappeRows";
|
|
6
|
+
|
|
7
|
+
/** `[field, operator, value]` on the doctype itself. */
|
|
8
|
+
export type FrappeFieldFilter = [string, string, unknown];
|
|
9
|
+
/** `[doctype, field, operator, value]`: Frappe's filter on another doctype's rows. */
|
|
10
|
+
export type FrappeDoctypeFilter = [string, string, string, unknown];
|
|
11
|
+
export type FrappeFilters =
|
|
12
|
+
| Record<string, unknown>
|
|
13
|
+
| Array<FrappeFieldFilter | FrappeDoctypeFilter>;
|
|
14
|
+
|
|
15
|
+
export interface ConnectionFiltersResult {
|
|
16
|
+
/**
|
|
17
|
+
* The filters to send: the doctype's own, plus one `["name", "in", names]`
|
|
18
|
+
* per connection filtered on. A `[doctype, …]` filter on a doctype that is
|
|
19
|
+
* no declared connection passes through untouched (a child table, which
|
|
20
|
+
* the bench joins itself).
|
|
21
|
+
*/
|
|
22
|
+
filters: Record<string, unknown> | Array<FrappeFieldFilter | FrappeDoctypeFilter> | undefined;
|
|
23
|
+
/** A connection's rows are still being read; the result is not known yet. */
|
|
24
|
+
pending: boolean;
|
|
25
|
+
error: Error | null;
|
|
26
|
+
/** Mount these: one reader per connection filtered on. */
|
|
27
|
+
readers: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface LinkedFilter {
|
|
31
|
+
connection: FrappeConnection;
|
|
32
|
+
conditions: FrappeFieldFilter[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type Snapshot = FrappeRowsSnapshot<Record<string, unknown> & { name: string; doctype: string }>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolves filters on a connection (Pokemon by `Pokemon Poke Type.poke_type`)
|
|
39
|
+
* into a filter on the doctype itself. Frappe's four-part filter joins child
|
|
40
|
+
* tables on the server; a standalone doctype linking back through a Link field
|
|
41
|
+
* is no child table, so the linked rows are read live here and their link
|
|
42
|
+
* values become `name in (...)`. Several conditions on one connection hold on
|
|
43
|
+
* the same linked row, as they do for a child table.
|
|
44
|
+
*/
|
|
45
|
+
export function useConnectionFilters(
|
|
46
|
+
connection: FrappeRowsConnection | undefined,
|
|
47
|
+
doctype: string,
|
|
48
|
+
filters: FrappeFilters | undefined,
|
|
49
|
+
/** `undefined` while the declaration is still loading (meta.links). */
|
|
50
|
+
connections: readonly FrappeConnection[] | undefined,
|
|
51
|
+
): ConnectionFiltersResult {
|
|
52
|
+
const filtersKey = JSON.stringify(filters ?? null);
|
|
53
|
+
const connectionsKey = JSON.stringify(connections ?? null);
|
|
54
|
+
|
|
55
|
+
const { own, linked, undeclared } = useMemo(() => {
|
|
56
|
+
const parsed = JSON.parse(filtersKey) as FrappeFilters | null;
|
|
57
|
+
const declaredList = JSON.parse(connectionsKey) as FrappeConnection[] | null;
|
|
58
|
+
const declared = declaredList ?? [];
|
|
59
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
60
|
+
return { own: parsed ?? undefined, linked: [] as LinkedFilter[], undeclared: false };
|
|
61
|
+
}
|
|
62
|
+
let waitsOnDeclaration = false;
|
|
63
|
+
const ownFilters: Array<FrappeFieldFilter | FrappeDoctypeFilter> = [];
|
|
64
|
+
const byDoctype = new Map<string, LinkedFilter>();
|
|
65
|
+
for (const filter of parsed) {
|
|
66
|
+
if (filter.length !== 4) {
|
|
67
|
+
ownFilters.push(filter);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const [target, field, operator, value] = filter;
|
|
71
|
+
if (target === doctype) {
|
|
72
|
+
ownFilters.push([field, operator, value]);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const declaration = declared.find((candidate) => candidate.doctype === target);
|
|
76
|
+
if (!declaration) {
|
|
77
|
+
if (!declaredList) waitsOnDeclaration = true;
|
|
78
|
+
ownFilters.push(filter);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const entry = byDoctype.get(target) ?? { connection: declaration, conditions: [] };
|
|
82
|
+
entry.conditions.push([field, operator, value]);
|
|
83
|
+
byDoctype.set(target, entry);
|
|
84
|
+
}
|
|
85
|
+
return { own: ownFilters, linked: [...byDoctype.values()], undeclared: waitsOnDeclaration };
|
|
86
|
+
}, [filtersKey, connectionsKey, doctype]);
|
|
87
|
+
|
|
88
|
+
const [snapshots, setSnapshots] = useState<Record<string, Snapshot>>({});
|
|
89
|
+
const handlers = useRef(new Map<string, (snapshot: Snapshot) => void>());
|
|
90
|
+
const handlerFor = useCallback((key: string) => {
|
|
91
|
+
let handler = handlers.current.get(key);
|
|
92
|
+
if (!handler) {
|
|
93
|
+
handler = (snapshot: Snapshot) => setSnapshots((prev) => ({ ...prev, [key]: snapshot }));
|
|
94
|
+
handlers.current.set(key, handler);
|
|
95
|
+
}
|
|
96
|
+
return handler;
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
const reads = useMemo(
|
|
100
|
+
() =>
|
|
101
|
+
linked.map(({ connection: link, conditions }) => ({
|
|
102
|
+
key: JSON.stringify([link.doctype, link.fieldname, conditions]),
|
|
103
|
+
link,
|
|
104
|
+
options: {
|
|
105
|
+
fields: ["name", link.fieldname],
|
|
106
|
+
filters: conditions,
|
|
107
|
+
limit: link.limit ?? 0,
|
|
108
|
+
},
|
|
109
|
+
})),
|
|
110
|
+
[linked],
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return useMemo(() => {
|
|
114
|
+
const readers = reads.map((read) => (
|
|
115
|
+
<FrappeRowsReader
|
|
116
|
+
key={read.key}
|
|
117
|
+
connection={connection}
|
|
118
|
+
doctype={read.link.doctype}
|
|
119
|
+
options={read.options as never}
|
|
120
|
+
onChange={handlerFor(read.key)}
|
|
121
|
+
/>
|
|
122
|
+
));
|
|
123
|
+
if (reads.length === 0) {
|
|
124
|
+
return { filters: own, pending: undeclared, error: null, readers };
|
|
125
|
+
}
|
|
126
|
+
let names: Set<string> | undefined;
|
|
127
|
+
let pending = false;
|
|
128
|
+
let error: Error | null = null;
|
|
129
|
+
for (const read of reads) {
|
|
130
|
+
const snapshot = snapshots[read.key];
|
|
131
|
+
if (!snapshot || snapshot.isLoading) {
|
|
132
|
+
pending = true;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (snapshot.error) {
|
|
136
|
+
error = error ?? snapshot.error;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const linkedNames = new Set(
|
|
140
|
+
snapshot.rows
|
|
141
|
+
.map((row) => row[read.link.fieldname])
|
|
142
|
+
.filter((value): value is string => typeof value === "string" && value !== ""),
|
|
143
|
+
);
|
|
144
|
+
names = names ? new Set([...names].filter((name) => linkedNames.has(name))) : linkedNames;
|
|
145
|
+
}
|
|
146
|
+
const ownList = Array.isArray(own)
|
|
147
|
+
? own
|
|
148
|
+
: own
|
|
149
|
+
? Object.entries(own).map(([field, value]) => [field, "=", value] as FrappeFieldFilter)
|
|
150
|
+
: [];
|
|
151
|
+
const resolved: Array<FrappeFieldFilter | FrappeDoctypeFilter> = [
|
|
152
|
+
...ownList,
|
|
153
|
+
["name", "in", [...(names ?? [])].sort()],
|
|
154
|
+
];
|
|
155
|
+
return { filters: resolved, pending: (pending || undeclared) && !error, error, readers };
|
|
156
|
+
}, [reads, snapshots, own, undeclared, connection, handlerFor]);
|
|
157
|
+
}
|
|
@@ -13,21 +13,33 @@
|
|
|
13
13
|
* return <List items={items} onLoadMore={onLoadMore} hasMore={hasMore} />;
|
|
14
14
|
* ```
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* the limit
|
|
20
|
-
*
|
|
16
|
+
* The read itself is {@link useFrappeRows} (MPO-251), so FrappeList shares
|
|
17
|
+
* the one implementation every other doctype view uses. On top of it this
|
|
18
|
+
* hook manages an increasing `limit` over the rows it returns. When the
|
|
19
|
+
* number of returned items equals the limit, `hasMore` is `true`. Calling
|
|
20
|
+
* `onLoadMore` increases the limit by `pageSize`.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import {
|
|
24
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
23
|
+
import { useCallback, useMemo, useState } from "react";
|
|
25
24
|
import type { UseFrappeCollectionConfig } from "@multiplatform.one/frappe";
|
|
25
|
+
import { useFrappeRows, type UseFrappeRowsOptions } from "./useFrappeRows";
|
|
26
26
|
|
|
27
27
|
export interface UseFrappePaginationOptions<TDoc> {
|
|
28
28
|
fields?: readonly (keyof TDoc & string)[];
|
|
29
29
|
filters?: Partial<TDoc> | Array<[keyof TDoc & string, string, unknown]>;
|
|
30
30
|
pageSize?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Rows the subscription reads. Omitted, the bench's own page length
|
|
33
|
+
* applies (20 on a stock site), which truncates any longer list.
|
|
34
|
+
*/
|
|
35
|
+
limit?: number;
|
|
36
|
+
orderBy?: UseFrappeRowsOptions<TDoc>["orderBy"];
|
|
37
|
+
/**
|
|
38
|
+
* Rows a route loader rendered on the server for this same query. They
|
|
39
|
+
* stand in until the live collection's first sync lands, so the server
|
|
40
|
+
* page and the hydrated page match; the live rows replace them after.
|
|
41
|
+
*/
|
|
42
|
+
initialRows?: readonly TDoc[];
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
export interface UseFrappePaginationResult<TDoc> {
|
|
@@ -49,6 +61,31 @@ export interface UseFrappePaginationResult<TDoc> {
|
|
|
49
61
|
|
|
50
62
|
const defaultPageSize = 20;
|
|
51
63
|
|
|
64
|
+
function toError(err: unknown): Error {
|
|
65
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type OrderBy<TDoc> = NonNullable<UseFrappeRowsOptions<TDoc>["orderBy"]>;
|
|
69
|
+
|
|
70
|
+
/** Row order for an `orderBy`, missing values last, the way the bench sorts a page. */
|
|
71
|
+
function compareRows<TDoc>(orderBy: OrderBy<TDoc>) {
|
|
72
|
+
return (a: TDoc, b: TDoc): number => {
|
|
73
|
+
for (const { field, order } of orderBy) {
|
|
74
|
+
const left = (a as Record<string, unknown>)[field];
|
|
75
|
+
const right = (b as Record<string, unknown>)[field];
|
|
76
|
+
if (left === right) continue;
|
|
77
|
+
if (left === null || left === undefined) return 1;
|
|
78
|
+
if (right === null || right === undefined) return -1;
|
|
79
|
+
const compared =
|
|
80
|
+
typeof left === "number" && typeof right === "number"
|
|
81
|
+
? left - right
|
|
82
|
+
: String(left).localeCompare(String(right));
|
|
83
|
+
if (compared !== 0) return String(order).toLowerCase() === "desc" ? -compared : compared;
|
|
84
|
+
}
|
|
85
|
+
return 0;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
52
89
|
export function useFrappePagination<TDoc extends { name: string; doctype: string }>(
|
|
53
90
|
config: UseFrappeCollectionConfig | undefined,
|
|
54
91
|
doctype: string,
|
|
@@ -56,31 +93,38 @@ export function useFrappePagination<TDoc extends { name: string; doctype: string
|
|
|
56
93
|
): UseFrappePaginationResult<TDoc> {
|
|
57
94
|
const pageSize = options?.pageSize ?? defaultPageSize;
|
|
58
95
|
const [limit, setLimit] = useState(pageSize);
|
|
59
|
-
const [error, setError] = useState<Error | null>(null);
|
|
60
96
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
97
|
+
// `limit: null` keeps the subscription on the bench's own page length, as
|
|
98
|
+
// it was before the shared read; `mapError` hands FrappeList the raw sync
|
|
99
|
+
// failure its isPermissionError check reads.
|
|
100
|
+
const {
|
|
101
|
+
rows: liveRows,
|
|
102
|
+
isLoading: isReading,
|
|
103
|
+
isQueryLoading,
|
|
104
|
+
syncError: error,
|
|
105
|
+
} = useFrappeRows<TDoc>(config, doctype, {
|
|
67
106
|
fields: options?.fields,
|
|
68
107
|
filters: options?.filters,
|
|
69
|
-
|
|
108
|
+
orderBy: options?.orderBy,
|
|
109
|
+
limit: options?.limit ?? null,
|
|
110
|
+
mapError: toError,
|
|
70
111
|
});
|
|
71
112
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const { data: rawData, isLoading } = useLiveQuery(
|
|
79
|
-
(q) => (collection ? q.from({ _list: collection }) : null),
|
|
80
|
-
[collection],
|
|
113
|
+
// The collection holds rows by name, whatever order the page was read in,
|
|
114
|
+
// so an ordered read is put back in its order here.
|
|
115
|
+
const orderBy = options?.orderBy;
|
|
116
|
+
const liveItems = useMemo(
|
|
117
|
+
() => (orderBy?.length ? [...liveRows].sort(compareRows(orderBy)) : liveRows),
|
|
118
|
+
[liveRows, orderBy],
|
|
81
119
|
);
|
|
82
120
|
|
|
83
|
-
|
|
121
|
+
// The server-rendered page holds until the first sync answers, and past a
|
|
122
|
+
// failed one: rows the visitor already sees never give way to an empty
|
|
123
|
+
// list. SSR and the hydration pass have no collection, so both render it.
|
|
124
|
+
const seed = options?.initialRows;
|
|
125
|
+
const seeded = Boolean(seed?.length) && (isReading || (error !== null && liveItems.length === 0));
|
|
126
|
+
const allItems = seeded ? (seed as TDoc[]) : liveItems;
|
|
127
|
+
const isLoading = isQueryLoading && !seeded;
|
|
84
128
|
|
|
85
129
|
// Slice to the current limit (progressive disclosure)
|
|
86
130
|
const items = useMemo(() => allItems.slice(0, limit), [allItems, limit]);
|
|
@@ -98,5 +142,5 @@ export function useFrappePagination<TDoc extends { name: string; doctype: string
|
|
|
98
142
|
setLimit(pageSize);
|
|
99
143
|
}, [pageSize]);
|
|
100
144
|
|
|
101
|
-
return { items, isLoading, hasMore, onLoadMore, limit, reset, error };
|
|
145
|
+
return { items, isLoading, hasMore, onLoadMore, limit, reset, error: seeded ? null : error };
|
|
102
146
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shipped doctype views share ONE read (MPO-251 AC3).
|
|
3
|
+
*
|
|
4
|
+
* What this locks:
|
|
5
|
+
* 1. No shipped doctype view assembles useFrappeCollection + useLiveQuery
|
|
6
|
+
* itself; each reads through useFrappeRows (FrappeList through
|
|
7
|
+
* useFrappePagination, which reads through useFrappeRows).
|
|
8
|
+
* 2. At runtime FrappeList, FrappeImageView, FrappeDashboard and
|
|
9
|
+
* FrappeReportBuilder call useFrappeRows and render the rows it returns.
|
|
10
|
+
* 3. FrappeList's permission error still reaches its honest error state
|
|
11
|
+
* through the shared read.
|
|
12
|
+
*
|
|
13
|
+
* Fixture-hermetic: documents ride InMemoryFixtureProvider.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { resolve } from "node:path";
|
|
18
|
+
import { InMemoryFixtureProvider } from "@multiplatform.one/frappe";
|
|
19
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
20
|
+
import { cleanup, screen, waitFor } from "@testing-library/react";
|
|
21
|
+
import i18next from "i18next";
|
|
22
|
+
import { initReactI18next } from "react-i18next";
|
|
23
|
+
import { Text } from "tamagui";
|
|
24
|
+
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
25
|
+
import { FrappeDashboard } from "./FrappeDashboard";
|
|
26
|
+
import { FrappeImageView } from "./FrappeImageView";
|
|
27
|
+
import { FrappeList } from "./FrappeList";
|
|
28
|
+
import { FrappeProvider } from "./FrappeProvider";
|
|
29
|
+
import { FrappeReportBuilder } from "./FrappeReportBuilder";
|
|
30
|
+
import { useFrappeRows } from "./useFrappeRows";
|
|
31
|
+
|
|
32
|
+
vi.mock("./useFrappeRows", async (importOriginal) => {
|
|
33
|
+
const actual = await importOriginal<typeof import("./useFrappeRows")>();
|
|
34
|
+
return { ...actual, useFrappeRows: vi.fn(actual.useFrappeRows) };
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const readSpy = vi.mocked(useFrappeRows);
|
|
38
|
+
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
await i18next.use(initReactI18next).init({
|
|
41
|
+
lng: "en",
|
|
42
|
+
interpolation: { escapeValue: false },
|
|
43
|
+
resources: { en: { translation: {} } },
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
readSpy.mockClear();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
cleanup();
|
|
53
|
+
vi.restoreAllMocks();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
interface Customer {
|
|
57
|
+
name: string;
|
|
58
|
+
doctype: string;
|
|
59
|
+
customer_name: string;
|
|
60
|
+
image: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const customers: Customer[] = [
|
|
64
|
+
{ name: "CUST-001", doctype: "Customer", customer_name: "Acme Corp", image: "" },
|
|
65
|
+
{ name: "CUST-002", doctype: "Customer", customer_name: "Globex", image: "" },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
function fixtureCorpus() {
|
|
69
|
+
return new InMemoryFixtureProvider({
|
|
70
|
+
DocType: [
|
|
71
|
+
{
|
|
72
|
+
name: "Customer",
|
|
73
|
+
doctype: "DocType",
|
|
74
|
+
module: "Selling",
|
|
75
|
+
image_field: "image",
|
|
76
|
+
title_field: "customer_name",
|
|
77
|
+
fields: [
|
|
78
|
+
{
|
|
79
|
+
fieldname: "customer_name",
|
|
80
|
+
fieldtype: "Data",
|
|
81
|
+
label: "Customer Name",
|
|
82
|
+
in_list_view: 1,
|
|
83
|
+
},
|
|
84
|
+
{ fieldname: "image", fieldtype: "Attach Image", label: "Image" },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
Customer: customers,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const baseURL = "https://sharedpath.spec.test";
|
|
93
|
+
|
|
94
|
+
function readsOf(doctype: string) {
|
|
95
|
+
return readSpy.mock.calls.filter(([, calledDoctype]) => calledDoctype === doctype);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function source(file: string) {
|
|
99
|
+
return readFileSync(resolve(process.cwd(), "src", file), "utf8")
|
|
100
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
101
|
+
.replace(/\/\/.*$/gm, "");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe("one implementation of the doctype read", () => {
|
|
105
|
+
const readers: Array<[file: string, reader: RegExp]> = [
|
|
106
|
+
["FrappeTable.tsx", /\buseFrappeRows\s*[<(]/],
|
|
107
|
+
["FrappeList.tsx", /\buseFrappePagination\s*[<(]/],
|
|
108
|
+
["useFrappePagination.ts", /\buseFrappeRows\s*[<(]/],
|
|
109
|
+
["FrappeTreeView.tsx", /\buseFrappeRows\s*[<(]/],
|
|
110
|
+
["FrappeKanban.tsx", /\buseFrappeRows\s*[<(]/],
|
|
111
|
+
["FrappeCalendar.tsx", /\buseFrappeRows\s*[<(]/],
|
|
112
|
+
["FrappeGantt.tsx", /\buseFrappeRows\s*[<(]/],
|
|
113
|
+
["FrappeImageView.tsx", /\buseFrappeRows\s*[<(]/],
|
|
114
|
+
["FrappeDashboard.tsx", /\buseFrappeRows\s*[<(]/],
|
|
115
|
+
["FrappeReportBuilder.tsx", /\buseFrappeRows\s*[<(]/],
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
it.each(readers)("%s reads through the shared hook, not the raw primitives", (file, reader) => {
|
|
119
|
+
const code = source(file);
|
|
120
|
+
expect(code).toMatch(reader);
|
|
121
|
+
expect(code).not.toMatch(/\buseFrappeCollection\s*[<(]/);
|
|
122
|
+
expect(code).not.toMatch(/\buseLiveQuery\s*\(/);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("the shipped views render rows from useFrappeRows", () => {
|
|
127
|
+
it("FrappeList", async () => {
|
|
128
|
+
renderWithProviders(
|
|
129
|
+
<FrappeProvider fixtures={fixtureCorpus()}>
|
|
130
|
+
<FrappeList<Customer>
|
|
131
|
+
doctype="Customer"
|
|
132
|
+
fields={["name", "customer_name"]}
|
|
133
|
+
renderItem={(item) => <Text>{item.customer_name}</Text>}
|
|
134
|
+
/>
|
|
135
|
+
</FrappeProvider>,
|
|
136
|
+
);
|
|
137
|
+
expect(await screen.findByText("Acme Corp")).toBeTruthy();
|
|
138
|
+
expect(readsOf("Customer")[0]?.[2]).toMatchObject({
|
|
139
|
+
fields: ["name", "customer_name"],
|
|
140
|
+
limit: null,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("FrappeImageView", async () => {
|
|
145
|
+
renderWithProviders(
|
|
146
|
+
<FrappeProvider baseURL={baseURL} fixtures={fixtureCorpus()}>
|
|
147
|
+
<FrappeImageView<Customer>
|
|
148
|
+
doctype="Customer"
|
|
149
|
+
imageField="image"
|
|
150
|
+
titleField="customer_name"
|
|
151
|
+
/>
|
|
152
|
+
</FrappeProvider>,
|
|
153
|
+
);
|
|
154
|
+
expect(await screen.findByText("Globex")).toBeTruthy();
|
|
155
|
+
expect(readsOf("Customer").length).toBeGreaterThan(0);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("FrappeDashboard", async () => {
|
|
159
|
+
const received: Array<Partial<Customer>[]> = [];
|
|
160
|
+
renderWithProviders(
|
|
161
|
+
<FrappeProvider baseURL={baseURL} fixtures={fixtureCorpus()}>
|
|
162
|
+
<FrappeDashboard<Customer>
|
|
163
|
+
title="Customers"
|
|
164
|
+
widgets={[]}
|
|
165
|
+
dataSource={{ doctype: "Customer", fields: ["name", "customer_name"] }}
|
|
166
|
+
transformData={(data) => {
|
|
167
|
+
received.push(data);
|
|
168
|
+
return [];
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
</FrappeProvider>,
|
|
172
|
+
);
|
|
173
|
+
await waitFor(() => expect(received.at(-1)).toHaveLength(2));
|
|
174
|
+
expect(readsOf("Customer").length).toBeGreaterThan(0);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("FrappeReportBuilder", async () => {
|
|
178
|
+
renderWithProviders(
|
|
179
|
+
<FrappeProvider fixtures={fixtureCorpus()}>
|
|
180
|
+
<FrappeReportBuilder doctype="Customer" />
|
|
181
|
+
</FrappeProvider>,
|
|
182
|
+
);
|
|
183
|
+
expect(await screen.findByText("Acme Corp")).toBeTruthy();
|
|
184
|
+
expect(readsOf("Customer").length).toBeGreaterThan(0);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("FrappeList error plumbing through the shared read", () => {
|
|
189
|
+
it("renders a 403 as a permission error, not an empty list", async () => {
|
|
190
|
+
const fixtures = fixtureCorpus();
|
|
191
|
+
vi.spyOn(fixtures, "getList").mockRejectedValue(
|
|
192
|
+
Object.assign(new Error("PermissionError"), { status: 403 }),
|
|
193
|
+
);
|
|
194
|
+
renderWithProviders(
|
|
195
|
+
<FrappeProvider fixtures={fixtures}>
|
|
196
|
+
<FrappeList<Customer>
|
|
197
|
+
doctype="Customer"
|
|
198
|
+
fields={["name", "customer_name"]}
|
|
199
|
+
renderItem={(item) => <Text>{item.customer_name}</Text>}
|
|
200
|
+
/>
|
|
201
|
+
</FrappeProvider>,
|
|
202
|
+
);
|
|
203
|
+
expect(await screen.findByText("You do not have permission to view Customer")).toBeTruthy();
|
|
204
|
+
expect(screen.queryByText("No Customer records found")).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
});
|
package/src/useFrappeRows.ts
CHANGED
|
@@ -35,10 +35,11 @@
|
|
|
35
35
|
* that key, so passing a different error handler never splits a
|
|
36
36
|
* collection.
|
|
37
37
|
*
|
|
38
|
-
* The shipped
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* The shipped doctype views read through this hook — FrappeTable,
|
|
39
|
+
* FrappeList (via useFrappePagination), FrappeTreeView, FrappeKanban,
|
|
40
|
+
* FrappeCalendar, FrappeGantt, FrappeImageView, FrappeDashboard and
|
|
41
|
+
* FrappeReportBuilder — so there is ONE implementation of the read rather
|
|
42
|
+
* than a reference one and N copies.
|
|
42
43
|
*
|
|
43
44
|
* @example
|
|
44
45
|
* ```tsx
|
|
@@ -41,10 +41,9 @@ export interface FrappeDashboardProps<TDoc extends {
|
|
|
41
41
|
/**
|
|
42
42
|
* Thin Frappe wrapper around the generic Dashboard component.
|
|
43
43
|
*
|
|
44
|
-
* Fetches live data via `
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* `@multiplatform.one/components`.
|
|
44
|
+
* Fetches live data via `useFrappeRows` if a `dataSource` is configured,
|
|
45
|
+
* transforms it into widget data, and passes everything to the generic
|
|
46
|
+
* `Dashboard` from `@multiplatform.one/components`.
|
|
48
47
|
*/
|
|
49
48
|
export declare function FrappeDashboard<TDoc extends {
|
|
50
49
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FrappeDashboard.d.ts","sourceRoot":"","sources":["../src/FrappeDashboard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FrappeDashboard.d.ts","sourceRoot":"","sources":["../src/FrappeDashboard.tsx"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGhF,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,OAAO,EAAwB,KAAK,WAAW,EAAM,MAAM,SAAS,CAAC;AAKrE;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,IAAI;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,IAAI,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IACpE,YAAY,CAAC,EAAE;QACb,KAAK,EAAE,MAAM,IAAI,GAAG,MAAM,CAAC;QAC3B,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;QACnD,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,MAAM,CAAC;KAC/B,EAAE,CAAC;CACL;AAED,MAAM,WAAW,oBAAoB,CAAC,IAAI,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAE,SAAQ,IAAI,CAChG,WAAW,EACX,UAAU,CACX;IACC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChG,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,eAAe,EAAE,CAAC;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC;CAC3C;AA+LD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,EAC9E,OAAO,EACP,IAAI,EACJ,WAAW,EACX,KAAK,EACL,WAAW,EACX,OAAO,EAAE,aAAa,EACtB,UAAU,EACV,aAAa,EACb,OAAW,EACX,GAAQ,EACR,OAAY,EACZ,WAAmB,EACnB,eAAoB,EACpB,OAAO,EACP,KAAK,EACL,GAAG,UAAU,EACd,EAAE,oBAAoB,CAAC,IAAI,CAAC,2CAiH5B;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
package/types/FrappeForm.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ import type { HeadingLevel } from "./fields/Heading";
|
|
|
4
4
|
import { type SidebarSection, type SidebarMetadataEntry } from "./FormSidebar";
|
|
5
5
|
import type React from "react";
|
|
6
6
|
import type { TFunction } from "i18next";
|
|
7
|
+
import { type FrappeConnection } from "./frappeConnections";
|
|
8
|
+
/** Linked rows per connection doctype, as the render prop receives them. */
|
|
9
|
+
export type FrappeConnectionRows = Record<string, Array<Record<string, unknown>>>;
|
|
7
10
|
export interface FrappeFormProps<TDoc extends {
|
|
8
11
|
name: string;
|
|
9
12
|
doctype: string;
|
|
@@ -27,7 +30,32 @@ export interface FrappeFormProps<TDoc extends {
|
|
|
27
30
|
meta?: DocTypeMeta;
|
|
28
31
|
/** True when the form is read-only (disabled prop, or doc deleted on the server). */
|
|
29
32
|
disabled: boolean;
|
|
33
|
+
/** The record's linked rows per connection doctype (edit mode), live. */
|
|
34
|
+
connections: FrappeConnectionRows;
|
|
35
|
+
/** A connection's rows are still being read. */
|
|
36
|
+
connectionsLoading: boolean;
|
|
30
37
|
}) => React.ReactNode;
|
|
38
|
+
/**
|
|
39
|
+
* Doctypes linking back to this one (the desk's Connections). Defaults to
|
|
40
|
+
* meta.links; declare them where the reader cannot load meta (Guest).
|
|
41
|
+
* Schema mode shows them on a Connections tab; render-prop mode hands the
|
|
42
|
+
* rows to `children`.
|
|
43
|
+
*/
|
|
44
|
+
connections?: readonly FrappeConnection[];
|
|
45
|
+
/** False leaves the Connections tab out of schema mode. */
|
|
46
|
+
showConnections?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The document a route loader rendered on the server (`loadDoc`), shown
|
|
49
|
+
* until the live read lands so the server page and the hydrated page match.
|
|
50
|
+
* Ignored unless its name is `name`.
|
|
51
|
+
*/
|
|
52
|
+
initialDoc?: Partial<TDoc>;
|
|
53
|
+
/** Replaces the skeleton while the document loads. */
|
|
54
|
+
loadingFallback?: React.ReactNode;
|
|
55
|
+
/** Replaces the not-found message for a name that does not resolve. */
|
|
56
|
+
notFoundFallback?: React.ReactNode;
|
|
57
|
+
/** Replaces the error state when the document cannot be read. */
|
|
58
|
+
errorFallback?: React.ReactNode;
|
|
31
59
|
/** Per-field overrides for schema-driven mode (when children not provided). */
|
|
32
60
|
overrides?: Record<string, FieldOverride>;
|
|
33
61
|
/** Fields to hide in schema-driven mode. */
|
|
@@ -42,9 +70,31 @@ export interface FrappeFormProps<TDoc extends {
|
|
|
42
70
|
* names are ignored. Cheaper than replacing `sections`.
|
|
43
71
|
*/
|
|
44
72
|
order?: string[];
|
|
73
|
+
/**
|
|
74
|
+
* Session roles for permlevel resolution. Omitted: the form asks the site
|
|
75
|
+
* for them when the doctype has fields above permlevel 0, and a fixtures
|
|
76
|
+
* connection takes the conservative rule (permlevel > 0 is read-only,
|
|
77
|
+
* never hidden).
|
|
78
|
+
*/
|
|
79
|
+
roles?: string[];
|
|
80
|
+
/** Called after a save; a created document carries the name the server gave it. */
|
|
45
81
|
onSave?: (doc: Partial<TDoc>) => void;
|
|
46
82
|
onCancel?: () => void;
|
|
47
83
|
onDelete?: (doc: Partial<TDoc>) => void;
|
|
84
|
+
/**
|
|
85
|
+
* Given, the form is a read-only view of the record: its toolbar offers
|
|
86
|
+
* Edit (this callback) and Delete instead of Save and Cancel.
|
|
87
|
+
*/
|
|
88
|
+
onEdit?: () => void;
|
|
89
|
+
/**
|
|
90
|
+
* The path the doctype's pages live under (`/moves`). Given, the form is
|
|
91
|
+
* the doctype's record page: it reads the record from the path through
|
|
92
|
+
* the FrappeProvider's navigation (`route/new` creates, `route/<name>`
|
|
93
|
+
* views, `route/<name>/edit` edits), carries the detail page template,
|
|
94
|
+
* and after a save, cancel, edit or delete moves to the matching page.
|
|
95
|
+
* Callbacks given alongside it replace those moves.
|
|
96
|
+
*/
|
|
97
|
+
route?: string;
|
|
48
98
|
showToolbar?: boolean;
|
|
49
99
|
showSidebar?: boolean;
|
|
50
100
|
showTimeline?: boolean;
|
|
@@ -103,14 +153,16 @@ export declare function buildSidebarSections(doc: Record<string, unknown>, share
|
|
|
103
153
|
*/
|
|
104
154
|
export declare function mapDocToSidebarMetadata(doc: Record<string, unknown>, t: TFunction): SidebarMetadataEntry[];
|
|
105
155
|
/**
|
|
106
|
-
*
|
|
156
|
+
* The form for a Frappe doctype, resolved from meta by the desk form engine.
|
|
107
157
|
*
|
|
108
|
-
* Fetches
|
|
109
|
-
*
|
|
110
|
-
*
|
|
158
|
+
* Fetches doctype metadata and the document, lays the fields out through
|
|
159
|
+
* `buildFormTabs` (tabs, sections, `depends_on`, `mandatory_depends_on`,
|
|
160
|
+
* `read_only_depends_on`, `collapsible_depends_on`, permlevel,
|
|
161
|
+
* `set_only_once`, `fetch_from` and docstatus), and renders each tab with
|
|
162
|
+
* `SchemaForm`. With a `route` it is the doctype's record page.
|
|
111
163
|
*/
|
|
112
164
|
export declare function FrappeForm<TDoc extends {
|
|
113
165
|
name: string;
|
|
114
166
|
doctype: string;
|
|
115
|
-
}>(
|
|
167
|
+
}>(props: FrappeFormProps<TDoc>): import("react/jsx-runtime").JSX.Element;
|
|
116
168
|
//# sourceMappingURL=FrappeForm.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FrappeForm.d.ts","sourceRoot":"","sources":["../src/FrappeForm.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"FrappeForm.d.ts","sourceRoot":"","sources":["../src/FrappeForm.tsx"],"names":[],"mappings":"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"}
|