@multiplatform.one/frappe-ui 7.28.0 → 7.29.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/dist/cjs/ChartCard.native.js.map +1 -1
- package/dist/cjs/FileBrowser/AttachLibrary.native.js.map +1 -1
- package/dist/cjs/FileBrowser/folderTree.native.js.map +1 -1
- package/dist/cjs/FileBrowser/index.cjs +20 -3
- package/dist/cjs/FileBrowser/index.native.js +20 -3
- package/dist/cjs/FileBrowser/index.native.js.map +1 -1
- package/dist/cjs/FileBrowser/types.native.js.map +1 -1
- package/dist/cjs/FileBrowser/viewPersistence.native.js.map +1 -1
- package/dist/cjs/FormSidebar.native.js.map +1 -1
- package/dist/cjs/FrappeAssignment.native.js.map +1 -1
- package/dist/cjs/FrappeCalendar.cjs +3 -2
- package/dist/cjs/FrappeCalendar.native.js +3 -2
- package/dist/cjs/FrappeCalendar.native.js.map +1 -1
- package/dist/cjs/FrappeChartCard.native.js.map +1 -1
- package/dist/cjs/FrappeConnectionIndicator.native.js.map +1 -1
- package/dist/cjs/FrappeDashboard.native.js.map +1 -1
- package/dist/cjs/FrappeForm.cjs +148 -464
- package/dist/cjs/FrappeForm.native.js +153 -634
- package/dist/cjs/FrappeForm.native.js.map +1 -1
- package/dist/cjs/FrappeFormActions.native.js.map +1 -1
- package/dist/cjs/FrappeFormConnections.native.js.map +1 -1
- package/dist/cjs/FrappeGantt.native.js.map +1 -1
- package/dist/cjs/FrappeImageView.native.js.map +1 -1
- package/dist/cjs/FrappeInsights.native.js.map +1 -1
- package/dist/cjs/FrappeKPICard.native.js.map +1 -1
- package/dist/cjs/FrappeKanban.native.js.map +1 -1
- package/dist/cjs/FrappeLikedBy.native.js.map +1 -1
- package/dist/cjs/FrappeList.native.js.map +1 -1
- package/dist/cjs/FrappeListView.cjs +5 -27
- package/dist/cjs/FrappeListView.native.js +11 -32
- package/dist/cjs/FrappeListView.native.js.map +1 -1
- package/dist/cjs/FrappeNotConfigured.native.js.map +1 -1
- package/dist/cjs/FrappeProvider.native.js.map +1 -1
- package/dist/cjs/FrappeReconnectIndicator.native.js.map +1 -1
- package/dist/cjs/FrappeRecordView.native.js.map +1 -1
- package/dist/cjs/FrappeReportBuilder.cjs +523 -309
- package/dist/cjs/FrappeReportBuilder.native.js +658 -353
- package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
- package/dist/cjs/FrappeReportView.cjs +35 -234
- package/dist/cjs/FrappeReportView.native.js +42 -286
- package/dist/cjs/FrappeReportView.native.js.map +1 -1
- package/dist/cjs/FrappeRowsReader.native.js.map +1 -1
- package/dist/cjs/FrappeTable.native.js.map +1 -1
- package/dist/cjs/FrappeTreeView.native.js.map +1 -1
- package/dist/cjs/SyncErrorNotice.native.js.map +1 -1
- package/dist/cjs/commentActions.native.js.map +1 -1
- package/dist/cjs/components/attachments.native.js.map +1 -1
- package/dist/cjs/components/tags.native.js.map +1 -1
- package/dist/cjs/components/timeline.native.js.map +1 -1
- package/dist/cjs/dependsOn.cjs +2 -2
- package/dist/cjs/dependsOn.native.js +1 -2
- package/dist/cjs/dependsOn.native.js.map +1 -1
- package/dist/cjs/desk/DeskFormPage.native.js.map +1 -1
- package/dist/cjs/desk/DeskFormSidebar.native.js.map +1 -1
- package/dist/cjs/desk/DeskListPage.native.js.map +1 -1
- package/dist/cjs/desk/DeskShell.native.js.map +1 -1
- package/dist/cjs/desk/DeskSidebar.native.js.map +1 -1
- package/dist/cjs/desk/awesomebar.native.js.map +1 -1
- package/dist/cjs/fieldOverrides.native.js.map +1 -1
- package/dist/cjs/fieldRegistry.native.js +31 -31
- package/dist/cjs/fieldRegistry.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/index.native.js.map +1 -1
- package/dist/cjs/fields/Check/index.native.js.map +1 -1
- package/dist/cjs/fields/CodeEditor/index.native.js.map +1 -1
- package/dist/cjs/fields/Color/index.native.js.map +1 -1
- package/dist/cjs/fields/Data/index.native.js.map +1 -1
- package/dist/cjs/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/cjs/fields/Date/types.native.js.map +1 -1
- package/dist/cjs/fields/Date/wire.native.js.map +1 -1
- package/dist/cjs/fields/DateRange/index.native.js.map +1 -1
- package/dist/cjs/fields/Duration/index.native.js.map +1 -1
- package/dist/cjs/fields/DynamicLink.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload.native.js.map +1 -1
- package/dist/cjs/fields/FileUploadField/index.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/index.native.js.map +1 -1
- package/dist/cjs/fields/HTML/index.native.js.map +1 -1
- package/dist/cjs/fields/Heading/index.native.js.map +1 -1
- package/dist/cjs/fields/Icon/index.native.js.map +1 -1
- package/dist/cjs/fields/Link.native.js.map +1 -1
- package/dist/cjs/fields/LinkTree.native.js.map +1 -1
- package/dist/cjs/fields/LongText/index.native.js.map +1 -1
- package/dist/cjs/fields/MentionInput/index.native.js.map +1 -1
- package/dist/cjs/fields/Numeric/index.native.js.map +1 -1
- package/dist/cjs/fields/OTPInput/index.native.js.map +1 -1
- package/dist/cjs/fields/Password/index.native.js.map +1 -1
- package/dist/cjs/fields/Phone/index.native.js.map +1 -1
- package/dist/cjs/fields/Progress/index.native.js.map +1 -1
- package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/Rating/index.native.js.map +1 -1
- package/dist/cjs/fields/ReadOnly/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/index.native.js.map +1 -1
- package/dist/cjs/fields/Signature/index.native.js.map +1 -1
- package/dist/cjs/fields/Slider/index.native.js.map +1 -1
- package/dist/cjs/fields/SmallText/index.native.js.map +1 -1
- package/dist/cjs/fields/Switch/index.native.js.map +1 -1
- package/dist/cjs/fields/TableMultiSelect.native.js.map +1 -1
- package/dist/cjs/fields/TextEditor/types.native.js.map +1 -1
- package/dist/cjs/fields/TimePicker/index.native.js.map +1 -1
- package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/shared/EditorPreviewPanes.cjs +2 -2
- package/dist/cjs/fields/shared/EditorPreviewPanes.native.js +2 -2
- package/dist/cjs/fields/shared/EditorPreviewPanes.native.js.map +1 -1
- package/dist/cjs/filterFields.native.js.map +1 -1
- package/dist/cjs/formEngine/createPrefill.native.js.map +1 -1
- package/dist/cjs/formEngine/dirtyTracking.native.js.map +1 -1
- package/dist/cjs/formEngine/docLifecycle.native.js.map +1 -1
- package/dist/cjs/formEngine/docPermissions.cjs +20 -0
- package/dist/cjs/formEngine/docPermissions.native.js +69 -0
- package/dist/cjs/formEngine/docPermissions.native.js.map +1 -1
- package/dist/cjs/formEngine/doctypeMeta.native.js.map +1 -1
- package/dist/cjs/formEngine/fetchFrom.native.js.map +1 -1
- package/dist/cjs/formEngine/fieldPermissions.native.js.map +1 -1
- package/dist/cjs/formEngine/formActions.native.js.map +1 -1
- package/dist/cjs/formEngine/formLayout.native.js.map +1 -1
- package/dist/cjs/formEngine/index.cjs +2 -0
- package/dist/cjs/formEngine/index.native.js +2 -0
- package/dist/cjs/formEngine/index.native.js.map +1 -1
- package/dist/cjs/formEngine/useAmendedAs.native.js.map +1 -1
- package/dist/cjs/formEngine/useDocRights.native.js.map +1 -1
- package/dist/cjs/formEngine/useFormEngine.native.js.map +1 -1
- package/dist/cjs/formEngine/useSessionRoles.native.js.map +1 -1
- package/dist/cjs/frappeConnections.native.js.map +1 -1
- package/dist/cjs/frappeEventColor.native.js.map +1 -1
- package/dist/cjs/frappeNavigation.native.js.map +1 -1
- package/dist/cjs/frappeStoryTarget.native.js.map +1 -1
- package/dist/cjs/frappeUsers.native.js.map +1 -1
- package/dist/cjs/index.cjs +67 -7
- package/dist/cjs/index.native.js +101 -41
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/insights/InsightsFacetTags.native.js.map +1 -1
- package/dist/cjs/insights/InsightsKpiBand.native.js.map +1 -1
- package/dist/cjs/insights/InsightsLeaders.native.js.map +1 -1
- package/dist/cjs/insights/InsightsRecords.native.js.map +1 -1
- package/dist/cjs/insights/InsightsTable.native.js.map +1 -1
- package/dist/cjs/insights/insightsData.native.js.map +1 -1
- package/dist/cjs/insights/insightsRecord.native.js.map +1 -1
- package/dist/cjs/list/FacetBadge.native.js.map +1 -1
- package/dist/cjs/list/FacetRail.native.js.map +1 -1
- package/dist/cjs/list/ImageWell.native.js.map +1 -1
- package/dist/cjs/list/ListControls.native.js.map +1 -1
- package/dist/cjs/list/ListToastHost.native.js.map +1 -1
- package/dist/cjs/list/RangeFilter.native.js.map +1 -1
- package/dist/cjs/list/ShufflePanel.native.js.map +1 -1
- package/dist/cjs/list/facetReading.native.js.map +1 -1
- package/dist/cjs/list/favorites.native.js.map +1 -1
- package/dist/cjs/list/listColumns.native.js.map +1 -1
- package/dist/cjs/list/listFields.native.js.map +1 -1
- package/dist/cjs/list/listFormat.native.js.map +1 -1
- package/dist/cjs/list/listState.native.js.map +1 -1
- package/dist/cjs/list/recordLook.native.js.map +1 -1
- package/dist/cjs/list/statusColors.native.js.map +1 -1
- package/dist/cjs/list/useListControls.native.js.map +1 -1
- package/dist/cjs/liveErrors.cjs +8 -2
- package/dist/cjs/liveErrors.native.js +8 -2
- package/dist/cjs/liveErrors.native.js.map +1 -1
- package/dist/cjs/measures.cjs +31 -0
- package/dist/cjs/measures.native.js +34 -0
- package/dist/cjs/measures.native.js.map +1 -0
- package/dist/cjs/record/RecordBars.native.js.map +1 -1
- package/dist/cjs/record/RecordGallery.native.js.map +1 -1
- package/dist/cjs/record/RecordHero.native.js.map +1 -1
- package/dist/cjs/record/RecordPage.native.js.map +1 -1
- package/dist/cjs/record/RecordPager.native.js.map +1 -1
- package/dist/cjs/record/RecordRows.native.js.map +1 -1
- package/dist/cjs/record/recordData.native.js.map +1 -1
- package/dist/cjs/record/recordSections.native.js.map +1 -1
- package/dist/cjs/recordShape.cjs +103 -0
- package/dist/cjs/recordShape.native.js +115 -0
- package/dist/cjs/recordShape.native.js.map +1 -0
- package/dist/cjs/report/ReportGrid.cjs +453 -0
- package/dist/cjs/report/ReportGrid.native.js +510 -0
- package/dist/cjs/report/ReportGrid.native.js.map +1 -0
- package/dist/cjs/report/ReportViewToolbar.cjs +253 -0
- package/dist/cjs/report/ReportViewToolbar.native.js +303 -0
- package/dist/cjs/report/ReportViewToolbar.native.js.map +1 -0
- package/dist/cjs/report/queryReport.cjs +444 -0
- package/dist/cjs/report/queryReport.native.js +588 -0
- package/dist/cjs/report/queryReport.native.js.map +1 -0
- package/dist/cjs/report/queryReportColumns.cjs +131 -0
- package/dist/cjs/report/queryReportColumns.native.js +159 -0
- package/dist/cjs/report/queryReportColumns.native.js.map +1 -0
- package/dist/cjs/report/reportViewModel.cjs +386 -0
- package/dist/cjs/report/reportViewModel.native.js +657 -0
- package/dist/cjs/report/reportViewModel.native.js.map +1 -0
- package/dist/cjs/report/savedReports.cjs +137 -0
- package/dist/cjs/report/savedReports.native.js +169 -0
- package/dist/cjs/report/savedReports.native.js.map +1 -0
- package/dist/cjs/report/useFrappeReport.cjs +184 -0
- package/dist/cjs/report/useFrappeReport.native.js +214 -0
- package/dist/cjs/report/useFrappeReport.native.js.map +1 -0
- package/dist/cjs/report/useQueryReport.cjs +201 -0
- package/dist/cjs/report/useQueryReport.native.js +233 -0
- package/dist/cjs/report/useQueryReport.native.js.map +1 -0
- package/dist/cjs/resolveControl.native.js.map +1 -1
- package/dist/cjs/schemaForm/SchemaWizard.native.js.map +1 -1
- package/dist/cjs/schemaForm/applyFieldOrder.native.js.map +1 -1
- package/dist/cjs/schemaForm/index.native.js +1 -1
- package/dist/cjs/schemaForm/index.native.js.map +1 -1
- package/dist/cjs/shared/devWarn.native.js.map +1 -1
- package/dist/cjs/shared/useKeyboardInset.native.js.map +1 -1
- package/dist/cjs/syncError.native.js.map +1 -1
- package/dist/cjs/useConnectionFilters.native.js.map +1 -1
- package/dist/cjs/useFrappeAggregate.native.js.map +1 -1
- package/dist/cjs/useFrappeFacets.native.js.map +1 -1
- package/dist/cjs/useFrappeForm.cjs +558 -0
- package/dist/cjs/useFrappeForm.native.js +714 -0
- package/dist/cjs/useFrappeForm.native.js.map +1 -0
- package/dist/cjs/useFrappePagination.native.js.map +1 -1
- package/dist/cjs/useFrappeRows.native.js.map +1 -1
- package/dist/esm/ChartCard.native.js.map +1 -1
- package/dist/esm/FileBrowser/AttachLibrary.native.js.map +1 -1
- package/dist/esm/FileBrowser/folderTree.native.js.map +1 -1
- package/dist/esm/FileBrowser/index.mjs +20 -3
- package/dist/esm/FileBrowser/index.mjs.map +1 -1
- package/dist/esm/FileBrowser/index.native.js +20 -3
- package/dist/esm/FileBrowser/index.native.js.map +1 -1
- package/dist/esm/FileBrowser/types.native.js.map +1 -1
- package/dist/esm/FileBrowser/viewPersistence.native.js.map +1 -1
- package/dist/esm/FormSidebar.native.js.map +1 -1
- package/dist/esm/FrappeAssignment.native.js.map +1 -1
- package/dist/esm/FrappeCalendar.mjs +4 -3
- package/dist/esm/FrappeCalendar.mjs.map +1 -1
- package/dist/esm/FrappeCalendar.native.js +4 -3
- package/dist/esm/FrappeCalendar.native.js.map +1 -1
- package/dist/esm/FrappeChartCard.native.js.map +1 -1
- package/dist/esm/FrappeConnectionIndicator.native.js.map +1 -1
- package/dist/esm/FrappeDashboard.native.js.map +1 -1
- package/dist/esm/FrappeForm.mjs +151 -467
- package/dist/esm/FrappeForm.mjs.map +1 -1
- package/dist/esm/FrappeForm.native.js +155 -636
- package/dist/esm/FrappeForm.native.js.map +1 -1
- package/dist/esm/FrappeFormActions.native.js.map +1 -1
- package/dist/esm/FrappeFormConnections.native.js.map +1 -1
- package/dist/esm/FrappeGantt.native.js.map +1 -1
- package/dist/esm/FrappeImageView.native.js.map +1 -1
- package/dist/esm/FrappeInsights.native.js.map +1 -1
- package/dist/esm/FrappeKPICard.native.js.map +1 -1
- package/dist/esm/FrappeKanban.native.js.map +1 -1
- package/dist/esm/FrappeLikedBy.native.js.map +1 -1
- package/dist/esm/FrappeList.native.js.map +1 -1
- package/dist/esm/FrappeListView.mjs +6 -28
- package/dist/esm/FrappeListView.mjs.map +1 -1
- package/dist/esm/FrappeListView.native.js +12 -33
- package/dist/esm/FrappeListView.native.js.map +1 -1
- package/dist/esm/FrappeNotConfigured.native.js.map +1 -1
- package/dist/esm/FrappeProvider.native.js.map +1 -1
- package/dist/esm/FrappeReconnectIndicator.native.js.map +1 -1
- package/dist/esm/FrappeRecordView.native.js.map +1 -1
- package/dist/esm/FrappeReportBuilder.mjs +526 -312
- package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
- package/dist/esm/FrappeReportBuilder.native.js +661 -356
- package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
- package/dist/esm/FrappeReportView.mjs +30 -229
- package/dist/esm/FrappeReportView.mjs.map +1 -1
- package/dist/esm/FrappeReportView.native.js +34 -278
- package/dist/esm/FrappeReportView.native.js.map +1 -1
- package/dist/esm/FrappeRowsReader.native.js.map +1 -1
- package/dist/esm/FrappeTable.native.js.map +1 -1
- package/dist/esm/FrappeTreeView.native.js.map +1 -1
- package/dist/esm/SyncErrorNotice.native.js.map +1 -1
- package/dist/esm/commentActions.native.js.map +1 -1
- package/dist/esm/components/attachments.native.js.map +1 -1
- package/dist/esm/components/tags.native.js.map +1 -1
- package/dist/esm/components/timeline.native.js.map +1 -1
- package/dist/esm/dependsOn.mjs +2 -2
- package/dist/esm/dependsOn.mjs.map +1 -1
- package/dist/esm/dependsOn.native.js +1 -2
- package/dist/esm/dependsOn.native.js.map +1 -1
- package/dist/esm/desk/DeskFormPage.native.js.map +1 -1
- package/dist/esm/desk/DeskFormSidebar.native.js.map +1 -1
- package/dist/esm/desk/DeskListPage.native.js.map +1 -1
- package/dist/esm/desk/DeskShell.native.js.map +1 -1
- package/dist/esm/desk/DeskSidebar.native.js.map +1 -1
- package/dist/esm/desk/awesomebar.native.js.map +1 -1
- package/dist/esm/fieldOverrides.native.js.map +1 -1
- package/dist/esm/fieldRegistry.native.js.map +1 -1
- package/dist/esm/fields/Barcode/index.native.js.map +1 -1
- package/dist/esm/fields/Check/index.native.js.map +1 -1
- package/dist/esm/fields/CodeEditor/index.native.js.map +1 -1
- package/dist/esm/fields/Color/index.native.js.map +1 -1
- package/dist/esm/fields/Data/index.native.js.map +1 -1
- package/dist/esm/fields/Date/sheetKeyboard.native.js.map +1 -1
- package/dist/esm/fields/Date/types.native.js.map +1 -1
- package/dist/esm/fields/Date/wire.native.js.map +1 -1
- package/dist/esm/fields/DateRange/index.native.js.map +1 -1
- package/dist/esm/fields/Duration/index.native.js.map +1 -1
- package/dist/esm/fields/DynamicLink.native.js.map +1 -1
- package/dist/esm/fields/FileUpload.native.js.map +1 -1
- package/dist/esm/fields/FileUploadField/index.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/index.native.js.map +1 -1
- package/dist/esm/fields/HTML/index.native.js.map +1 -1
- package/dist/esm/fields/Heading/index.native.js.map +1 -1
- package/dist/esm/fields/Icon/index.native.js.map +1 -1
- package/dist/esm/fields/Link.native.js.map +1 -1
- package/dist/esm/fields/LinkTree.native.js.map +1 -1
- package/dist/esm/fields/LongText/index.native.js.map +1 -1
- package/dist/esm/fields/MentionInput/index.native.js.map +1 -1
- package/dist/esm/fields/Numeric/index.native.js.map +1 -1
- package/dist/esm/fields/OTPInput/index.native.js.map +1 -1
- package/dist/esm/fields/Password/index.native.js.map +1 -1
- package/dist/esm/fields/Phone/index.native.js.map +1 -1
- package/dist/esm/fields/Progress/index.native.js.map +1 -1
- package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/esm/fields/Rating/index.native.js.map +1 -1
- package/dist/esm/fields/ReadOnly/index.native.js.map +1 -1
- package/dist/esm/fields/Select/index.native.js.map +1 -1
- package/dist/esm/fields/Signature/index.native.js.map +1 -1
- package/dist/esm/fields/Slider/index.native.js.map +1 -1
- package/dist/esm/fields/SmallText/index.native.js.map +1 -1
- package/dist/esm/fields/Switch/index.native.js.map +1 -1
- package/dist/esm/fields/TableMultiSelect.native.js.map +1 -1
- package/dist/esm/fields/TextEditor/types.native.js.map +1 -1
- package/dist/esm/fields/TimePicker/index.native.js.map +1 -1
- package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/esm/fields/shared/EditorPreviewPanes.mjs +2 -2
- package/dist/esm/fields/shared/EditorPreviewPanes.mjs.map +1 -1
- package/dist/esm/fields/shared/EditorPreviewPanes.native.js +2 -2
- package/dist/esm/fields/shared/EditorPreviewPanes.native.js.map +1 -1
- package/dist/esm/filterFields.native.js.map +1 -1
- package/dist/esm/formEngine/createPrefill.native.js.map +1 -1
- package/dist/esm/formEngine/dirtyTracking.native.js.map +1 -1
- package/dist/esm/formEngine/docLifecycle.native.js.map +1 -1
- package/dist/esm/formEngine/docPermissions.mjs +19 -1
- package/dist/esm/formEngine/docPermissions.mjs.map +1 -1
- package/dist/esm/formEngine/docPermissions.native.js +68 -1
- package/dist/esm/formEngine/docPermissions.native.js.map +1 -1
- package/dist/esm/formEngine/doctypeMeta.native.js.map +1 -1
- package/dist/esm/formEngine/fetchFrom.native.js.map +1 -1
- package/dist/esm/formEngine/fieldPermissions.native.js.map +1 -1
- package/dist/esm/formEngine/formActions.native.js.map +1 -1
- package/dist/esm/formEngine/formLayout.native.js.map +1 -1
- package/dist/esm/formEngine/index.mjs +2 -2
- package/dist/esm/formEngine/index.mjs.map +1 -1
- package/dist/esm/formEngine/index.native.js +2 -2
- package/dist/esm/formEngine/index.native.js.map +1 -1
- package/dist/esm/formEngine/useAmendedAs.native.js.map +1 -1
- package/dist/esm/formEngine/useDocRights.native.js.map +1 -1
- package/dist/esm/formEngine/useFormEngine.native.js.map +1 -1
- package/dist/esm/formEngine/useSessionRoles.native.js.map +1 -1
- package/dist/esm/frappeConnections.native.js.map +1 -1
- package/dist/esm/frappeEventColor.native.js.map +1 -1
- package/dist/esm/frappeNavigation.native.js.map +1 -1
- package/dist/esm/frappeStoryTarget.native.js.map +1 -1
- package/dist/esm/frappeUsers.native.js.map +1 -1
- package/dist/esm/index.mjs +16 -4
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +16 -4
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/insights/InsightsFacetTags.native.js.map +1 -1
- package/dist/esm/insights/InsightsKpiBand.native.js.map +1 -1
- package/dist/esm/insights/InsightsLeaders.native.js.map +1 -1
- package/dist/esm/insights/InsightsRecords.native.js.map +1 -1
- package/dist/esm/insights/InsightsTable.native.js.map +1 -1
- package/dist/esm/insights/insightsData.native.js.map +1 -1
- package/dist/esm/insights/insightsRecord.native.js.map +1 -1
- package/dist/esm/list/FacetBadge.native.js.map +1 -1
- package/dist/esm/list/FacetRail.native.js.map +1 -1
- package/dist/esm/list/ImageWell.native.js.map +1 -1
- package/dist/esm/list/ListControls.native.js.map +1 -1
- package/dist/esm/list/ListToastHost.native.js.map +1 -1
- package/dist/esm/list/RangeFilter.native.js.map +1 -1
- package/dist/esm/list/ShufflePanel.native.js.map +1 -1
- package/dist/esm/list/facetReading.native.js.map +1 -1
- package/dist/esm/list/favorites.native.js.map +1 -1
- package/dist/esm/list/listColumns.native.js.map +1 -1
- package/dist/esm/list/listFields.native.js.map +1 -1
- package/dist/esm/list/listFormat.native.js.map +1 -1
- package/dist/esm/list/listState.native.js.map +1 -1
- package/dist/esm/list/recordLook.native.js.map +1 -1
- package/dist/esm/list/statusColors.native.js.map +1 -1
- package/dist/esm/list/useListControls.native.js.map +1 -1
- package/dist/esm/liveErrors.mjs +8 -3
- package/dist/esm/liveErrors.mjs.map +1 -1
- package/dist/esm/liveErrors.native.js +8 -3
- package/dist/esm/liveErrors.native.js.map +1 -1
- package/dist/esm/measures.mjs +5 -0
- package/dist/esm/measures.mjs.map +1 -0
- package/dist/esm/measures.native.js +5 -0
- package/dist/esm/measures.native.js.map +1 -0
- package/dist/esm/record/RecordBars.native.js.map +1 -1
- package/dist/esm/record/RecordGallery.native.js.map +1 -1
- package/dist/esm/record/RecordHero.native.js.map +1 -1
- package/dist/esm/record/RecordPage.native.js.map +1 -1
- package/dist/esm/record/RecordPager.native.js.map +1 -1
- package/dist/esm/record/RecordRows.native.js.map +1 -1
- package/dist/esm/record/recordData.native.js.map +1 -1
- package/dist/esm/record/recordSections.native.js.map +1 -1
- package/dist/esm/recordShape.mjs +74 -0
- package/dist/esm/recordShape.mjs.map +1 -0
- package/dist/esm/recordShape.native.js +83 -0
- package/dist/esm/recordShape.native.js.map +1 -0
- package/dist/esm/report/ReportGrid.mjs +428 -0
- package/dist/esm/report/ReportGrid.mjs.map +1 -0
- package/dist/esm/report/ReportGrid.native.js +471 -0
- package/dist/esm/report/ReportGrid.native.js.map +1 -0
- package/dist/esm/report/ReportViewToolbar.mjs +228 -0
- package/dist/esm/report/ReportViewToolbar.mjs.map +1 -0
- package/dist/esm/report/ReportViewToolbar.native.js +264 -0
- package/dist/esm/report/ReportViewToolbar.native.js.map +1 -0
- package/dist/esm/report/queryReport.mjs +404 -0
- package/dist/esm/report/queryReport.mjs.map +1 -0
- package/dist/esm/report/queryReport.native.js +545 -0
- package/dist/esm/report/queryReport.native.js.map +1 -0
- package/dist/esm/report/queryReportColumns.mjs +105 -0
- package/dist/esm/report/queryReportColumns.mjs.map +1 -0
- package/dist/esm/report/queryReportColumns.native.js +119 -0
- package/dist/esm/report/queryReportColumns.native.js.map +1 -0
- package/dist/esm/report/reportViewModel.mjs +343 -0
- package/dist/esm/report/reportViewModel.mjs.map +1 -0
- package/dist/esm/report/reportViewModel.native.js +611 -0
- package/dist/esm/report/reportViewModel.native.js.map +1 -0
- package/dist/esm/report/savedReports.mjs +112 -0
- package/dist/esm/report/savedReports.mjs.map +1 -0
- package/dist/esm/report/savedReports.native.js +141 -0
- package/dist/esm/report/savedReports.native.js.map +1 -0
- package/dist/esm/report/useFrappeReport.mjs +158 -0
- package/dist/esm/report/useFrappeReport.mjs.map +1 -0
- package/dist/esm/report/useFrappeReport.native.js +185 -0
- package/dist/esm/report/useFrappeReport.native.js.map +1 -0
- package/dist/esm/report/useQueryReport.mjs +176 -0
- package/dist/esm/report/useQueryReport.mjs.map +1 -0
- package/dist/esm/report/useQueryReport.native.js +205 -0
- package/dist/esm/report/useQueryReport.native.js.map +1 -0
- package/dist/esm/resolveControl.native.js.map +1 -1
- package/dist/esm/schemaForm/SchemaWizard.native.js.map +1 -1
- package/dist/esm/schemaForm/applyFieldOrder.native.js.map +1 -1
- package/dist/esm/schemaForm/index.native.js.map +1 -1
- package/dist/esm/shared/devWarn.native.js.map +1 -1
- package/dist/esm/shared/useKeyboardInset.native.js.map +1 -1
- package/dist/esm/syncError.native.js.map +1 -1
- package/dist/esm/useConnectionFilters.native.js.map +1 -1
- package/dist/esm/useFrappeAggregate.native.js.map +1 -1
- package/dist/esm/useFrappeFacets.native.js.map +1 -1
- package/dist/esm/useFrappeForm.mjs +533 -0
- package/dist/esm/useFrappeForm.mjs.map +1 -0
- package/dist/esm/useFrappeForm.native.js +686 -0
- package/dist/esm/useFrappeForm.native.js.map +1 -0
- package/dist/esm/useFrappePagination.native.js.map +1 -1
- package/dist/esm/useFrappeRows.native.js.map +1 -1
- package/dist/jsx/FileBrowser/index.mjs +20 -3
- package/dist/jsx/FileBrowser/index.mjs.map +1 -1
- package/dist/jsx/FileBrowser/index.native.js +20 -3
- package/dist/jsx/FileBrowser/index.native.js.map +1 -1
- package/dist/jsx/FrappeCalendar.mjs +4 -3
- package/dist/jsx/FrappeCalendar.mjs.map +1 -1
- package/dist/jsx/FrappeCalendar.native.js +3 -2
- package/dist/jsx/FrappeCalendar.native.js.map +1 -1
- package/dist/jsx/FrappeForm.mjs +151 -467
- package/dist/jsx/FrappeForm.mjs.map +1 -1
- package/dist/jsx/FrappeForm.native.js +152 -633
- package/dist/jsx/FrappeForm.native.js.map +1 -1
- package/dist/jsx/FrappeListView.mjs +6 -28
- package/dist/jsx/FrappeListView.mjs.map +1 -1
- package/dist/jsx/FrappeListView.native.js +11 -32
- package/dist/jsx/FrappeListView.native.js.map +1 -1
- package/dist/jsx/FrappeReportBuilder.mjs +526 -312
- package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
- package/dist/jsx/FrappeReportBuilder.native.js +658 -353
- package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
- package/dist/jsx/FrappeReportView.mjs +30 -229
- package/dist/jsx/FrappeReportView.mjs.map +1 -1
- package/dist/jsx/FrappeReportView.native.js +39 -283
- package/dist/jsx/FrappeReportView.native.js.map +1 -1
- package/dist/jsx/dependsOn.mjs +2 -2
- package/dist/jsx/dependsOn.mjs.map +1 -1
- package/dist/jsx/dependsOn.native.js +1 -2
- package/dist/jsx/dependsOn.native.js.map +1 -1
- package/dist/jsx/fields/shared/EditorPreviewPanes.mjs +2 -2
- package/dist/jsx/fields/shared/EditorPreviewPanes.mjs.map +1 -1
- package/dist/jsx/fields/shared/EditorPreviewPanes.native.js +2 -2
- package/dist/jsx/fields/shared/EditorPreviewPanes.native.js.map +1 -1
- package/dist/jsx/formEngine/docPermissions.mjs +19 -1
- package/dist/jsx/formEngine/docPermissions.mjs.map +1 -1
- package/dist/jsx/formEngine/docPermissions.native.js +69 -0
- package/dist/jsx/formEngine/docPermissions.native.js.map +1 -1
- package/dist/jsx/formEngine/index.mjs +2 -2
- package/dist/jsx/formEngine/index.mjs.map +1 -1
- package/dist/jsx/formEngine/index.native.js +2 -0
- package/dist/jsx/formEngine/index.native.js.map +1 -1
- package/dist/jsx/index.js +16 -4
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +16 -4
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +67 -7
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/liveErrors.mjs +8 -3
- package/dist/jsx/liveErrors.mjs.map +1 -1
- package/dist/jsx/liveErrors.native.js +8 -2
- package/dist/jsx/liveErrors.native.js.map +1 -1
- package/dist/jsx/measures.mjs +5 -0
- package/dist/jsx/measures.mjs.map +1 -0
- package/dist/jsx/measures.native.js +34 -0
- package/dist/jsx/measures.native.js.map +1 -0
- package/dist/jsx/recordShape.mjs +74 -0
- package/dist/jsx/recordShape.mjs.map +1 -0
- package/dist/jsx/recordShape.native.js +115 -0
- package/dist/jsx/recordShape.native.js.map +1 -0
- package/dist/jsx/report/ReportGrid.mjs +428 -0
- package/dist/jsx/report/ReportGrid.mjs.map +1 -0
- package/dist/jsx/report/ReportGrid.native.js +510 -0
- package/dist/jsx/report/ReportGrid.native.js.map +1 -0
- package/dist/jsx/report/ReportViewToolbar.mjs +228 -0
- package/dist/jsx/report/ReportViewToolbar.mjs.map +1 -0
- package/dist/jsx/report/ReportViewToolbar.native.js +303 -0
- package/dist/jsx/report/ReportViewToolbar.native.js.map +1 -0
- package/dist/jsx/report/queryReport.mjs +404 -0
- package/dist/jsx/report/queryReport.mjs.map +1 -0
- package/dist/jsx/report/queryReport.native.js +588 -0
- package/dist/jsx/report/queryReport.native.js.map +1 -0
- package/dist/jsx/report/queryReportColumns.mjs +105 -0
- package/dist/jsx/report/queryReportColumns.mjs.map +1 -0
- package/dist/jsx/report/queryReportColumns.native.js +159 -0
- package/dist/jsx/report/queryReportColumns.native.js.map +1 -0
- package/dist/jsx/report/reportViewModel.mjs +343 -0
- package/dist/jsx/report/reportViewModel.mjs.map +1 -0
- package/dist/jsx/report/reportViewModel.native.js +657 -0
- package/dist/jsx/report/reportViewModel.native.js.map +1 -0
- package/dist/jsx/report/savedReports.mjs +112 -0
- package/dist/jsx/report/savedReports.mjs.map +1 -0
- package/dist/jsx/report/savedReports.native.js +169 -0
- package/dist/jsx/report/savedReports.native.js.map +1 -0
- package/dist/jsx/report/useFrappeReport.mjs +158 -0
- package/dist/jsx/report/useFrappeReport.mjs.map +1 -0
- package/dist/jsx/report/useFrappeReport.native.js +214 -0
- package/dist/jsx/report/useFrappeReport.native.js.map +1 -0
- package/dist/jsx/report/useQueryReport.mjs +176 -0
- package/dist/jsx/report/useQueryReport.mjs.map +1 -0
- package/dist/jsx/report/useQueryReport.native.js +233 -0
- package/dist/jsx/report/useQueryReport.native.js.map +1 -0
- package/dist/jsx/useFrappeForm.mjs +533 -0
- package/dist/jsx/useFrappeForm.mjs.map +1 -0
- package/dist/jsx/useFrappeForm.native.js +714 -0
- package/dist/jsx/useFrappeForm.native.js.map +1 -0
- package/package.json +16 -16
- package/src/FileBrowser/FileBrowser.spec.tsx +21 -0
- package/src/FileBrowser/FileBrowser.stories.tsx +27 -0
- package/src/FileBrowser/index.tsx +25 -2
- package/src/FrappeCalendar.tsx +8 -2
- package/src/FrappeForm.stories.tsx +52 -0
- package/src/FrappeForm.tsx +176 -565
- package/src/FrappeListView.cards.stories.tsx +3 -6
- package/src/FrappeListView.tsx +15 -55
- package/src/FrappeReportBuilder.stories.tsx +34 -7
- package/src/FrappeReportBuilder.tsx +790 -380
- package/src/FrappeReportView.spec.tsx +10 -6
- package/src/FrappeReportView.stories.tsx +20 -1
- package/src/FrappeReportView.tsx +89 -415
- package/src/dependsOn.ts +4 -1
- package/src/fields/shared/EditorPreviewPanes.tsx +2 -2
- package/src/formEngine/docPermissions.spec.ts +36 -1
- package/src/formEngine/docPermissions.ts +34 -0
- package/src/formEngine/index.ts +8 -1
- package/src/frappeRemainder.designLaw.spec.ts +9 -1
- package/src/frappeRoutes.spec.tsx +9 -0
- package/src/frappeViews.designLaw.spec.ts +15 -2
- package/src/guestFallback.spec.tsx +236 -0
- package/src/index.ts +117 -16
- package/src/liveErrors.spec.ts +10 -0
- package/src/liveErrors.ts +16 -3
- package/src/measures.ts +11 -0
- package/src/recordShape.spec.ts +100 -0
- package/src/recordShape.ts +133 -0
- package/src/report/ReportGrid.spec.tsx +175 -0
- package/src/report/ReportGrid.tsx +530 -0
- package/src/report/ReportViewToolbar.spec.tsx +90 -0
- package/src/report/ReportViewToolbar.tsx +314 -0
- package/src/report/queryReport.spec.ts +483 -0
- package/src/report/queryReport.ts +722 -0
- package/src/report/queryReportColumns.tsx +123 -0
- package/src/report/reportViewModel.spec.ts +393 -0
- package/src/report/reportViewModel.ts +617 -0
- package/src/report/savedReports.ts +172 -0
- package/src/report/seam.spec.ts +216 -0
- package/src/report/useFrappeReport.ts +241 -0
- package/src/report/useQueryReport.spec.tsx +131 -0
- package/src/report/useQueryReport.ts +309 -0
- package/src/useFrappeForm.spec.tsx +209 -0
- package/src/useFrappeForm.ts +778 -0
- package/types/FileBrowser/index.d.ts.map +1 -1
- package/types/FrappeCalendar.d.ts.map +1 -1
- package/types/FrappeForm.d.ts +21 -1
- package/types/FrappeForm.d.ts.map +1 -1
- package/types/FrappeListView.d.ts.map +1 -1
- package/types/FrappeReportBuilder.d.ts +66 -16
- package/types/FrappeReportBuilder.d.ts.map +1 -1
- package/types/FrappeReportView.d.ts +9 -127
- package/types/FrappeReportView.d.ts.map +1 -1
- package/types/dependsOn.d.ts.map +1 -1
- package/types/formEngine/docPermissions.d.ts +14 -0
- package/types/formEngine/docPermissions.d.ts.map +1 -1
- package/types/formEngine/index.d.ts +1 -1
- package/types/formEngine/index.d.ts.map +1 -1
- package/types/index.d.ts +15 -3
- package/types/index.d.ts.map +1 -1
- package/types/liveErrors.d.ts +9 -1
- package/types/liveErrors.d.ts.map +1 -1
- package/types/measures.d.ts +3 -0
- package/types/measures.d.ts.map +1 -0
- package/types/recordShape.d.ts +45 -0
- package/types/recordShape.d.ts.map +1 -0
- package/types/report/ReportGrid.d.ts +20 -0
- package/types/report/ReportGrid.d.ts.map +1 -0
- package/types/report/ReportViewToolbar.d.ts +31 -0
- package/types/report/ReportViewToolbar.d.ts.map +1 -0
- package/types/report/queryReport.d.ts +181 -0
- package/types/report/queryReport.d.ts.map +1 -0
- package/types/report/queryReportColumns.d.ts +12 -0
- package/types/report/queryReportColumns.d.ts.map +1 -0
- package/types/report/reportViewModel.d.ts +146 -0
- package/types/report/reportViewModel.d.ts.map +1 -0
- package/types/report/savedReports.d.ts +27 -0
- package/types/report/savedReports.d.ts.map +1 -0
- package/types/report/useFrappeReport.d.ts +60 -0
- package/types/report/useFrappeReport.d.ts.map +1 -0
- package/types/report/useQueryReport.d.ts +37 -0
- package/types/report/useQueryReport.d.ts.map +1 -0
- package/types/useFrappeForm.d.ts +142 -0
- package/types/useFrappeForm.d.ts.map +1 -0
|
@@ -1,64 +1,246 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* FrappeReportBuilder — a doctype's report view, the desk's
|
|
3
|
+
* /app/<doctype>/view/report (MPO-15, GL-28). The desk's view=report renders
|
|
4
|
+
* this component, so an app that reaches for it gets the same report the desk
|
|
5
|
+
* shows:
|
|
6
|
+
* - desk default columns (name, status, title/image fields, in_list_view
|
|
7
|
+
* and in_standard_filter fields, currency sources, modified), a column
|
|
8
|
+
* picker whose choice persists per doctype on this device while no saved
|
|
9
|
+
* report is selected
|
|
10
|
+
* - a per-column filter row on the standard frappe filter pipeline
|
|
11
|
+
* - one toolbar row: columns, group-by, saved reports, filter chips, export
|
|
12
|
+
* - saved reports on the `Report` doctype, the records desk's "Select
|
|
13
|
+
* Report" lists (in memory on a fixture corpus)
|
|
14
|
+
* - live rows through the collection channel: the full filtered result
|
|
15
|
+
* stays client-side (totals and export need it), ordered
|
|
16
|
+
* deterministically and paged client-side, with a totals row
|
|
17
|
+
* - a reader refused DocType meta (Guest) still gets the rows, laid out
|
|
18
|
+
* from `initialColumns` or one record's own fields, with nothing to save
|
|
19
|
+
* (LC-96); rows refused too is a permission state, never a retry
|
|
20
|
+
* The model is `./report/reportViewModel`; navigation, scope and the selected
|
|
21
|
+
* saved report come in as props, so the desk and a plain app mount the same
|
|
22
|
+
* component.
|
|
23
|
+
*/
|
|
3
24
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
25
|
+
BarChart,
|
|
26
|
+
EmptyState,
|
|
27
|
+
ScrollView,
|
|
28
|
+
Spinner,
|
|
29
|
+
YStack,
|
|
30
|
+
isWeb,
|
|
31
|
+
notify,
|
|
8
32
|
type GroupByConfig,
|
|
33
|
+
type ReportColumn,
|
|
9
34
|
} from "@multiplatform.one/components";
|
|
10
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
Order,
|
|
37
|
+
frappeFieldTypes,
|
|
38
|
+
useDocTypeMeta,
|
|
39
|
+
type DocTypeField,
|
|
40
|
+
type FrappeFieldType,
|
|
41
|
+
} from "@multiplatform.one/frappe";
|
|
42
|
+
import { Button } from "@multiplatform.one/forms";
|
|
43
|
+
import { downloadBlob } from "@multiplatform.one/platform";
|
|
11
44
|
import { storage } from "@multiplatform.one/store";
|
|
12
|
-
import
|
|
13
|
-
import { useTranslation } from "react-i18next";
|
|
14
|
-
import { Popover, Text, XStack, YStack, type YStackProps } from "tamagui";
|
|
15
|
-
import { FloppyDiskIcon, FolderOpenIcon, TrashIcon } from "@phosphor-icons/react";
|
|
45
|
+
import { DataTablePagination } from "@multiplatform.one/table";
|
|
16
46
|
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
47
|
+
import React, { useCallback, useEffect, useMemo, useState, type ReactNode } from "react";
|
|
48
|
+
import { useTranslation } from "react-i18next";
|
|
49
|
+
import type { YStackProps } from "tamagui";
|
|
50
|
+
import { FrappeNotConfigured } from "./FrappeNotConfigured";
|
|
17
51
|
import { useFrappeConfig } from "./FrappeProvider";
|
|
52
|
+
import { isPermissionError, refusalReason } from "./liveErrors";
|
|
53
|
+
import { recordKeyLabel, useRecordShape } from "./recordShape";
|
|
54
|
+
import { ReportGrid } from "./report/ReportGrid";
|
|
55
|
+
import { ReportViewToolbar } from "./report/ReportViewToolbar";
|
|
56
|
+
import {
|
|
57
|
+
DEFAULT_REPORT_PAGE_SIZE,
|
|
58
|
+
REPORT_PAGE_SIZES,
|
|
59
|
+
buildGridRows,
|
|
60
|
+
buildReportCsv,
|
|
61
|
+
columnFilterTuples,
|
|
62
|
+
computeTotals,
|
|
63
|
+
defaultReportFieldnames,
|
|
64
|
+
orderReportRows,
|
|
65
|
+
reportColumnsFromMeta,
|
|
66
|
+
resolveSelectedColumns,
|
|
67
|
+
type ReportFilters,
|
|
68
|
+
type ReportMetaSource,
|
|
69
|
+
type ReportRow,
|
|
70
|
+
type ReportSortState,
|
|
71
|
+
} from "./report/reportViewModel";
|
|
72
|
+
import { useSavedReports } from "./report/savedReports";
|
|
18
73
|
import { useFrappeRows } from "./useFrappeRows";
|
|
19
74
|
|
|
20
|
-
|
|
21
|
-
// Saved Report Types (Frappe-specific persisted storage)
|
|
22
|
-
// ============================================================================
|
|
75
|
+
type FilterTuple = [string, string, unknown];
|
|
23
76
|
|
|
24
|
-
interface
|
|
25
|
-
name
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
77
|
+
export interface FrappeReportBuilderProps extends Omit<YStackProps, "children"> {
|
|
78
|
+
/** Proper-cased doctype name (e.g. "ToDo", "Sales Order"). */
|
|
79
|
+
doctype: string;
|
|
80
|
+
/**
|
|
81
|
+
* The report's own filters (a deep link, a host's preset). They show as
|
|
82
|
+
* chips the user can clear. The object form means `field = value`.
|
|
83
|
+
*/
|
|
84
|
+
filters?: Record<string, unknown> | FilterTuple[];
|
|
85
|
+
/**
|
|
86
|
+
* Filters ANDed into every query: invisible to the chips and not
|
|
87
|
+
* clearable (a desk or tenant scope).
|
|
88
|
+
*/
|
|
89
|
+
scopeFilters?: FilterTuple[];
|
|
90
|
+
/** After the user clears `filters`, so a host can drop them from its address too. */
|
|
91
|
+
onClearFilters?: () => void;
|
|
92
|
+
/**
|
|
93
|
+
* Starting columns by fieldname, or as `ReportColumn` rows (the enabled
|
|
94
|
+
* ones). Defaults to desk's default columns.
|
|
95
|
+
*/
|
|
96
|
+
initialColumns?: ReadonlyArray<string | ReportColumn>;
|
|
97
|
+
/** Starting group-by field; a `GroupByConfig` also sets the chart's aggregation. */
|
|
98
|
+
initialGroupBy?: string | GroupByConfig;
|
|
99
|
+
/** A bar chart of the groups above the grid while the rows are grouped. */
|
|
100
|
+
showChart?: boolean;
|
|
101
|
+
/** Open a row's document. */
|
|
102
|
+
onOpenRow?: (name: string) => void;
|
|
103
|
+
/** A row press with the row and its index in the full ordered result. */
|
|
104
|
+
onRowPress?: (row: Record<string, unknown>, index: number) => void;
|
|
105
|
+
/** Open a Link cell's target document. */
|
|
106
|
+
onOpenLink?: (doctype: string, name: string) => void;
|
|
107
|
+
/**
|
|
108
|
+
* The selected saved report (a `Report Builder` Report doc name), or null
|
|
109
|
+
* for an unsaved report. Given, the selection is controlled.
|
|
110
|
+
*/
|
|
111
|
+
report?: string | null;
|
|
112
|
+
onReportChange?: (report: string | null) => void;
|
|
113
|
+
/** Replaces the CSV download; receives the full filtered, ordered rows. */
|
|
114
|
+
onExport?: (rows: Record<string, unknown>[]) => void;
|
|
115
|
+
/** Shown while the doctype's meta loads. */
|
|
116
|
+
loading?: ReactNode;
|
|
117
|
+
/** Shown when the doctype's meta cannot be read. */
|
|
118
|
+
error?: (error: Error) => ReactNode;
|
|
119
|
+
baseURL?: string;
|
|
120
|
+
auth?: { useToken?: boolean; token?: string; type?: string };
|
|
121
|
+
syncOptions?: Record<string, unknown>;
|
|
29
122
|
}
|
|
30
123
|
|
|
31
|
-
|
|
32
|
-
|
|
124
|
+
const EMPTY_FILTERS: ReportFilters = [];
|
|
125
|
+
const COLUMN_FILTER_DEBOUNCE_MS = 350;
|
|
126
|
+
|
|
127
|
+
function columnsStorageKey(doctype: string): string {
|
|
128
|
+
return `backoffice.report.columns.${doctype}`;
|
|
33
129
|
}
|
|
34
130
|
|
|
35
|
-
async function
|
|
131
|
+
async function readStoredColumns(doctype: string): Promise<string[] | undefined> {
|
|
36
132
|
try {
|
|
37
|
-
const raw = await Promise.resolve(storage.getItem(
|
|
38
|
-
if (raw) return
|
|
133
|
+
const raw = await Promise.resolve(storage.getItem(columnsStorageKey(doctype)));
|
|
134
|
+
if (!raw) return undefined;
|
|
135
|
+
const parsed: unknown = JSON.parse(raw);
|
|
136
|
+
if (Array.isArray(parsed) && parsed.every((entry) => typeof entry === "string")) {
|
|
137
|
+
return parsed as string[];
|
|
138
|
+
}
|
|
39
139
|
} catch {
|
|
40
|
-
// storage
|
|
140
|
+
// storage unavailable or corrupted — defaults win
|
|
41
141
|
}
|
|
42
|
-
return
|
|
142
|
+
return undefined;
|
|
43
143
|
}
|
|
44
144
|
|
|
45
|
-
|
|
145
|
+
function persistStoredColumns(doctype: string, fieldnames: readonly string[]): void {
|
|
46
146
|
try {
|
|
47
|
-
|
|
147
|
+
void Promise.resolve(storage.setItem(columnsStorageKey(doctype), JSON.stringify(fieldnames)));
|
|
48
148
|
} catch {
|
|
49
|
-
// storage
|
|
149
|
+
// storage unavailable — selection stays session-only
|
|
50
150
|
}
|
|
51
151
|
}
|
|
52
152
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
153
|
+
/** `{field: value}` → equality tuples; tuple arrays pass through. */
|
|
154
|
+
function toReportFilters(filters: FrappeReportBuilderProps["filters"]): ReportFilters | undefined {
|
|
155
|
+
if (!filters) return undefined;
|
|
156
|
+
if (Array.isArray(filters)) return filters.length > 0 ? filters : undefined;
|
|
157
|
+
const tuples = Object.entries(filters).map(([field, value]): FilterTuple => [field, "=", value]);
|
|
158
|
+
return tuples.length > 0 ? tuples : undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function initialFieldnames(columns: FrappeReportBuilderProps["initialColumns"]): string[] | null {
|
|
162
|
+
if (!columns) return null;
|
|
163
|
+
const fieldnames = columns.flatMap((column) =>
|
|
164
|
+
typeof column === "string" ? [column] : column.enabled ? [column.fieldname] : [],
|
|
165
|
+
);
|
|
166
|
+
return fieldnames.length > 0 ? fieldnames : null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function asFieldType(fieldtype: string | undefined): FrappeFieldType {
|
|
170
|
+
return frappeFieldTypes.find((known) => known === fieldtype) ?? "Data";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* What a reader refused DocType meta lays the report out from (LC-96): the
|
|
175
|
+
* host's `initialColumns`, else one record's own fields. Read-only and all in
|
|
176
|
+
* the default columns.
|
|
177
|
+
*/
|
|
178
|
+
function refusedMetaFields(
|
|
179
|
+
initialColumns: FrappeReportBuilderProps["initialColumns"],
|
|
180
|
+
recordFields: readonly DocTypeField[] | undefined,
|
|
181
|
+
): DocTypeField[] {
|
|
182
|
+
const declared = (initialColumns ?? []).map(
|
|
183
|
+
(column): DocTypeField =>
|
|
184
|
+
typeof column === "string"
|
|
185
|
+
? { fieldname: column, label: recordKeyLabel(column), fieldtype: "Data" }
|
|
186
|
+
: {
|
|
187
|
+
fieldname: column.fieldname,
|
|
188
|
+
label: column.label,
|
|
189
|
+
fieldtype: asFieldType(column.fieldtype),
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
return (declared.length > 0 ? declared : (recordFields ?? []))
|
|
193
|
+
.filter((field) => field.fieldname !== "name")
|
|
194
|
+
.map((field) => ({ ...field, read_only: 1, in_list_view: 1 }));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function asError(error: unknown): Error {
|
|
198
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function groupField(groupBy: FrappeReportBuilderProps["initialGroupBy"]): string | null {
|
|
202
|
+
if (!groupBy) return null;
|
|
203
|
+
return typeof groupBy === "string" ? groupBy : groupBy.field;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Group value → aggregate over its rows (count unless the config names one). */
|
|
207
|
+
function aggregateGroups(
|
|
208
|
+
rows: readonly ReportRow[],
|
|
209
|
+
field: string,
|
|
210
|
+
config: GroupByConfig | undefined,
|
|
211
|
+
): Array<{ label: string; value: number }> {
|
|
212
|
+
const groups = new Map<string, ReportRow[]>();
|
|
213
|
+
for (const row of rows) {
|
|
214
|
+
const key = row[field] === null || row[field] === undefined ? "" : String(row[field]);
|
|
215
|
+
const bucket = groups.get(key);
|
|
216
|
+
if (bucket) bucket.push(row);
|
|
217
|
+
else groups.set(key, [row]);
|
|
218
|
+
}
|
|
219
|
+
const numbers = (group: ReportRow[]) =>
|
|
220
|
+
group.map((row) => Number(config?.aggregationField ? row[config.aggregationField] : 0) || 0);
|
|
221
|
+
return [...groups.entries()].map(([label, group]) => {
|
|
222
|
+
switch (config?.aggregationField ? config.aggregation : "count") {
|
|
223
|
+
case "sum":
|
|
224
|
+
return { label, value: numbers(group).reduce((sum, value) => sum + value, 0) };
|
|
225
|
+
case "avg":
|
|
226
|
+
return {
|
|
227
|
+
label,
|
|
228
|
+
value: numbers(group).reduce((sum, value) => sum + value, 0) / group.length,
|
|
229
|
+
};
|
|
230
|
+
case "min":
|
|
231
|
+
return { label, value: Math.min(...numbers(group)) };
|
|
232
|
+
case "max":
|
|
233
|
+
return { label, value: Math.max(...numbers(group)) };
|
|
234
|
+
default:
|
|
235
|
+
return { label, value: group.length };
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
56
239
|
|
|
57
240
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* Presence checks, not `typeof window`: Hermes defines `window` without DOM.
|
|
241
|
+
* The export button needs somewhere to put a file: a DOM anchor and an
|
|
242
|
+
* object URL. Native and GTK have neither, so the built-in CSV path is left
|
|
243
|
+
* out there (B12); a host `onExport` owns delivery and shows everywhere.
|
|
62
244
|
*/
|
|
63
245
|
function canDownloadCsv(): boolean {
|
|
64
246
|
return (
|
|
@@ -70,418 +252,646 @@ function canDownloadCsv(): boolean {
|
|
|
70
252
|
);
|
|
71
253
|
}
|
|
72
254
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
initialColumns?: ReportColumn[];
|
|
80
|
-
initialGroupBy?: GroupByConfig;
|
|
81
|
-
filters?: Record<string, unknown> | Array<[string, string, unknown]>;
|
|
82
|
-
showChart?: boolean;
|
|
83
|
-
onExport?: (data: Record<string, unknown>[]) => void;
|
|
84
|
-
/** Row press handler — threaded to the generic ReportBuilder rows */
|
|
85
|
-
onRowPress?: (row: Record<string, unknown>, index: number) => void;
|
|
86
|
-
baseURL?: string;
|
|
87
|
-
auth?: { useToken?: boolean; token?: string; type?: string };
|
|
88
|
-
syncOptions?: Record<string, unknown>;
|
|
255
|
+
export function FrappeReportBuilder(props: FrappeReportBuilderProps) {
|
|
256
|
+
const config = useFrappeConfig();
|
|
257
|
+
if (!(props.baseURL ?? config?.baseURL) && !config?.fixtures) {
|
|
258
|
+
return <FrappeNotConfigured doctype={props.doctype} />;
|
|
259
|
+
}
|
|
260
|
+
return <FrappeReportBuilderBody {...props} />;
|
|
89
261
|
}
|
|
90
262
|
|
|
91
|
-
|
|
92
|
-
// Component
|
|
93
|
-
// ============================================================================
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Thin Frappe wrapper around the generic ReportBuilder component.
|
|
97
|
-
*
|
|
98
|
-
* Fetches DocType metadata and data via Frappe hooks, then passes
|
|
99
|
-
* column definitions and data to the generic `ReportBuilder` from
|
|
100
|
-
* `@multiplatform.one/components`. Adds Frappe-specific features:
|
|
101
|
-
* save/load reports to shared storage, CSV export.
|
|
102
|
-
*/
|
|
103
|
-
export function FrappeReportBuilder({
|
|
263
|
+
function FrappeReportBuilderBody({
|
|
104
264
|
doctype,
|
|
265
|
+
filters,
|
|
266
|
+
scopeFilters,
|
|
267
|
+
onClearFilters,
|
|
105
268
|
initialColumns,
|
|
106
269
|
initialGroupBy,
|
|
107
|
-
filters,
|
|
108
270
|
showChart = false,
|
|
109
|
-
|
|
271
|
+
onOpenRow,
|
|
110
272
|
onRowPress,
|
|
273
|
+
onOpenLink,
|
|
274
|
+
report: reportProp,
|
|
275
|
+
onReportChange,
|
|
276
|
+
onExport,
|
|
277
|
+
loading: loadingFallback,
|
|
278
|
+
error: errorFallback,
|
|
111
279
|
baseURL,
|
|
112
280
|
auth,
|
|
113
281
|
syncOptions,
|
|
114
282
|
...stackProps
|
|
115
283
|
}: FrappeReportBuilderProps) {
|
|
116
|
-
const config = useFrappeConfig();
|
|
117
|
-
const { knobProps } = useResolvedKnobs();
|
|
118
284
|
const { t } = useTranslation();
|
|
119
|
-
|
|
285
|
+
const { knobProps } = useResolvedKnobs();
|
|
286
|
+
const config = useFrappeConfig();
|
|
120
287
|
const finalBaseURL = baseURL ?? config?.baseURL;
|
|
121
288
|
const finalAuth = auth ?? config?.auth;
|
|
122
289
|
const finalSyncOptions = syncOptions ?? config?.syncOptions;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
290
|
+
const fixtureMode = Boolean(config?.fixtures);
|
|
291
|
+
|
|
292
|
+
const connection = useMemo(
|
|
293
|
+
() => ({
|
|
294
|
+
baseURL: finalBaseURL,
|
|
295
|
+
socketPort: config?.socketPort,
|
|
296
|
+
siteName: config?.siteName,
|
|
297
|
+
socketBaseURL: config?.socketBaseURL,
|
|
298
|
+
auth: finalAuth,
|
|
299
|
+
syncOptions: finalSyncOptions,
|
|
300
|
+
realtime: config?.realtime,
|
|
301
|
+
fixtures: config?.fixtures,
|
|
302
|
+
}),
|
|
303
|
+
[
|
|
304
|
+
finalBaseURL,
|
|
305
|
+
config?.socketPort,
|
|
306
|
+
config?.siteName,
|
|
307
|
+
config?.socketBaseURL,
|
|
308
|
+
finalAuth,
|
|
309
|
+
finalSyncOptions,
|
|
310
|
+
config?.realtime,
|
|
311
|
+
config?.fixtures,
|
|
312
|
+
],
|
|
313
|
+
);
|
|
314
|
+
const { meta, isLoading: metaLoading, error: metaError } = useDocTypeMeta(connection, doctype);
|
|
315
|
+
const metaRefused = !meta && metaError !== undefined && isPermissionError(metaError);
|
|
316
|
+
const initialColumnsJson = JSON.stringify(initialColumns ?? []);
|
|
317
|
+
const declaresColumns = initialColumnsJson !== "[]";
|
|
318
|
+
const recordShape = useRecordShape(connection, doctype, metaRefused && !declaresColumns);
|
|
319
|
+
const layoutMeta = useMemo((): ReportMetaSource | undefined => {
|
|
320
|
+
if (meta) return meta;
|
|
321
|
+
if (!metaRefused) return undefined;
|
|
322
|
+
const fields = refusedMetaFields(
|
|
323
|
+
JSON.parse(initialColumnsJson) as FrappeReportBuilderProps["initialColumns"],
|
|
324
|
+
recordShape.fields,
|
|
127
325
|
);
|
|
128
|
-
|
|
326
|
+
return fields.length > 0 ? { fields } : undefined;
|
|
327
|
+
}, [meta, metaRefused, initialColumnsJson, recordShape.fields]);
|
|
328
|
+
const readsWithoutMeta = !meta && layoutMeta !== undefined;
|
|
329
|
+
const savedReports = useSavedReports(
|
|
330
|
+
doctype,
|
|
331
|
+
useMemo(
|
|
332
|
+
() => ({
|
|
333
|
+
baseURL: metaRefused ? "" : (connection.baseURL ?? ""),
|
|
334
|
+
auth: connection.auth,
|
|
335
|
+
fixtures: connection.fixtures,
|
|
336
|
+
}),
|
|
337
|
+
[metaRefused, connection.baseURL, connection.auth, connection.fixtures],
|
|
338
|
+
),
|
|
339
|
+
);
|
|
129
340
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
f.fieldtype !== "Section Break" &&
|
|
156
|
-
f.fieldtype !== "Column Break" &&
|
|
157
|
-
f.fieldtype !== "Tab Break",
|
|
158
|
-
)
|
|
159
|
-
.map((f) => ({
|
|
160
|
-
fieldname: f.fieldname,
|
|
161
|
-
label: f.label || f.fieldname,
|
|
162
|
-
fieldtype: f.fieldtype,
|
|
163
|
-
enabled: !!f.in_list_view,
|
|
164
|
-
}));
|
|
165
|
-
}, [initialColumns, meta]);
|
|
166
|
-
|
|
167
|
-
// Data fetching
|
|
168
|
-
const fetchFields = useMemo(() => {
|
|
169
|
-
const fieldNames = allColumns.map((c) => c.fieldname);
|
|
170
|
-
if (!fieldNames.includes("name")) {
|
|
171
|
-
fieldNames.unshift("name");
|
|
172
|
-
}
|
|
173
|
-
return fieldNames as readonly string[];
|
|
174
|
-
}, [allColumns]);
|
|
341
|
+
// ── Saved-report selection: controlled by `report`, else internal ──
|
|
342
|
+
const [internalReport, setInternalReport] = useState<string | null>(null);
|
|
343
|
+
const currentReport = reportProp !== undefined ? reportProp : internalReport;
|
|
344
|
+
|
|
345
|
+
// ── Columns: defaults → device-stored → initial → explicit override ──
|
|
346
|
+
const allColumns = useMemo(() => {
|
|
347
|
+
const columns = reportColumnsFromMeta(layoutMeta);
|
|
348
|
+
if (!readsWithoutMeta || !layoutMeta) return columns;
|
|
349
|
+
const own = new Set(layoutMeta.fields.map((field) => field.fieldname));
|
|
350
|
+
return columns.filter((column) => column.fieldname === "name" || own.has(column.fieldname));
|
|
351
|
+
}, [layoutMeta, readsWithoutMeta]);
|
|
352
|
+
const defaultFieldnames = useMemo(
|
|
353
|
+
() =>
|
|
354
|
+
readsWithoutMeta && layoutMeta
|
|
355
|
+
? ["name", ...layoutMeta.fields.map((field) => field.fieldname)]
|
|
356
|
+
: defaultReportFieldnames(layoutMeta),
|
|
357
|
+
[layoutMeta, readsWithoutMeta],
|
|
358
|
+
);
|
|
359
|
+
const initialColumnsKey = JSON.stringify(initialFieldnames(initialColumns));
|
|
360
|
+
const startFieldnames = useMemo(
|
|
361
|
+
() => JSON.parse(initialColumnsKey) as string[] | null,
|
|
362
|
+
[initialColumnsKey],
|
|
363
|
+
);
|
|
364
|
+
const [storedColumns, setStoredColumns] = useState<string[] | undefined>(undefined);
|
|
365
|
+
const [selectedOverride, setSelectedOverride] = useState<string[] | null>(null);
|
|
175
366
|
|
|
176
|
-
const {
|
|
177
|
-
rows,
|
|
178
|
-
isQueryLoading: isLoading,
|
|
179
|
-
isError,
|
|
180
|
-
} = useFrappeRows<{ name: string; doctype: string }>(connectionConfig, doctype, {
|
|
181
|
-
fields: fetchFields as readonly ("name" | "doctype")[],
|
|
182
|
-
filters: filters as Partial<{ name: string; doctype: string }>,
|
|
183
|
-
limit: null,
|
|
184
|
-
});
|
|
185
|
-
const data = rows as unknown as Record<string, unknown>[];
|
|
186
|
-
|
|
187
|
-
// Live staleness: a realtime change on the doctype marks the current
|
|
188
|
-
// report result stale until the collection reconciles (data identity
|
|
189
|
-
// changes) or a settle window passes (the change may fall outside the
|
|
190
|
-
// report's filters/window). Builder UI state (chosen columns, group-by)
|
|
191
|
-
// lives in component state and is untouched by refreshes.
|
|
192
|
-
const [isLiveStale, setIsLiveStale] = useState(false);
|
|
193
|
-
const staleTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
194
|
-
const clearStaleTimer = useCallback(() => {
|
|
195
|
-
if (staleTimerRef.current) {
|
|
196
|
-
clearTimeout(staleTimerRef.current);
|
|
197
|
-
staleTimerRef.current = undefined;
|
|
198
|
-
}
|
|
199
|
-
}, []);
|
|
200
|
-
useFrappeDoctypeEvents(connectionConfig as any, doctype, (event) => {
|
|
201
|
-
if (
|
|
202
|
-
event.type !== "doc_update" &&
|
|
203
|
-
event.type !== "list_update" &&
|
|
204
|
-
event.type !== "doc_deleted" &&
|
|
205
|
-
event.type !== "doc_changed"
|
|
206
|
-
) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
setIsLiveStale(true);
|
|
210
|
-
clearStaleTimer();
|
|
211
|
-
staleTimerRef.current = setTimeout(() => setIsLiveStale(false), 2500);
|
|
212
|
-
});
|
|
213
|
-
const isFirstData = useRef(true);
|
|
214
367
|
useEffect(() => {
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
368
|
+
if (fixtureMode) return;
|
|
369
|
+
let cancelled = false;
|
|
370
|
+
void readStoredColumns(doctype).then((stored) => {
|
|
371
|
+
if (!cancelled) setStoredColumns(stored);
|
|
372
|
+
});
|
|
373
|
+
return () => {
|
|
374
|
+
cancelled = true;
|
|
375
|
+
};
|
|
376
|
+
}, [doctype, fixtureMode]);
|
|
223
377
|
|
|
224
|
-
|
|
225
|
-
|
|
378
|
+
const selectedFieldnames =
|
|
379
|
+
selectedOverride ?? startFieldnames ?? storedColumns ?? defaultFieldnames;
|
|
380
|
+
const selectedColumns = useMemo(
|
|
381
|
+
() => resolveSelectedColumns(allColumns, selectedFieldnames),
|
|
382
|
+
[allColumns, selectedFieldnames],
|
|
383
|
+
);
|
|
226
384
|
|
|
227
|
-
//
|
|
228
|
-
const
|
|
385
|
+
// ── Filters: the report's own + the per-column filter row ─────
|
|
386
|
+
const filtersKey = JSON.stringify(filters ?? null);
|
|
387
|
+
const propFilters = useMemo(
|
|
388
|
+
() => toReportFilters(JSON.parse(filtersKey) as FrappeReportBuilderProps["filters"]),
|
|
389
|
+
[filtersKey],
|
|
390
|
+
);
|
|
391
|
+
const [filtersCleared, setFiltersCleared] = useState(false);
|
|
392
|
+
const [reportFilters, setReportFilters] = useState<ReportFilters | null>(null);
|
|
393
|
+
const baseFilters = filtersCleared
|
|
394
|
+
? EMPTY_FILTERS
|
|
395
|
+
: (reportFilters ?? propFilters ?? EMPTY_FILTERS);
|
|
396
|
+
|
|
397
|
+
const [filterDrafts, setFilterDrafts] = useState<Record<string, string>>({});
|
|
398
|
+
const [committedFilters, setCommittedFilters] = useState<Record<string, string>>({});
|
|
399
|
+
useEffect(() => {
|
|
400
|
+
const handle = setTimeout(() => {
|
|
401
|
+
setCommittedFilters((previous) =>
|
|
402
|
+
JSON.stringify(previous) === JSON.stringify(filterDrafts) ? previous : filterDrafts,
|
|
403
|
+
);
|
|
404
|
+
}, COLUMN_FILTER_DEBOUNCE_MS);
|
|
405
|
+
return () => clearTimeout(handle);
|
|
406
|
+
}, [filterDrafts]);
|
|
407
|
+
|
|
408
|
+
const columnTuples = useMemo(
|
|
409
|
+
() => columnFilterTuples(selectedColumns, committedFilters),
|
|
410
|
+
[selectedColumns, committedFilters],
|
|
411
|
+
);
|
|
412
|
+
const scopeKey = JSON.stringify(scopeFilters ?? []);
|
|
413
|
+
const scope = useMemo(() => JSON.parse(scopeKey) as ReportFilters, [scopeKey]);
|
|
414
|
+
const effectiveFilters = useMemo(
|
|
415
|
+
() => [...scope, ...baseFilters, ...columnTuples],
|
|
416
|
+
[scope, baseFilters, columnTuples],
|
|
417
|
+
);
|
|
229
418
|
|
|
230
|
-
//
|
|
419
|
+
// ── Sort / group / paging ───────────────────────────────────
|
|
420
|
+
const metaSort = useMemo<ReportSortState>(
|
|
421
|
+
() => ({
|
|
422
|
+
field: (meta?.sort_field || "modified").split(" ")[0],
|
|
423
|
+
order: (meta?.sort_order || "DESC").toLowerCase() === "asc" ? "asc" : "desc",
|
|
424
|
+
}),
|
|
425
|
+
[meta?.sort_field, meta?.sort_order],
|
|
426
|
+
);
|
|
427
|
+
const [sortOverride, setSortOverride] = useState<ReportSortState | null>(null);
|
|
428
|
+
const sort = sortOverride ?? metaSort;
|
|
429
|
+
const startGroupBy = groupField(initialGroupBy);
|
|
430
|
+
const chartConfig = typeof initialGroupBy === "object" ? initialGroupBy : undefined;
|
|
431
|
+
const [groupBy, setGroupBy] = useState<string | null>(startGroupBy);
|
|
432
|
+
const [pageSize, setPageSize] = useState<number>(DEFAULT_REPORT_PAGE_SIZE);
|
|
433
|
+
const [page, setPage] = useState(0);
|
|
434
|
+
|
|
435
|
+
// A doctype swap resets every view state (a route reuses the component).
|
|
231
436
|
useEffect(() => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
437
|
+
setSelectedOverride(null);
|
|
438
|
+
setStoredColumns(undefined);
|
|
439
|
+
setSortOverride(null);
|
|
440
|
+
setGroupBy(startGroupBy);
|
|
441
|
+
setPage(0);
|
|
442
|
+
setPageSize(DEFAULT_REPORT_PAGE_SIZE);
|
|
443
|
+
setFilterDrafts({});
|
|
444
|
+
setCommittedFilters({});
|
|
445
|
+
setFiltersCleared(false);
|
|
446
|
+
setReportFilters(null);
|
|
447
|
+
// startGroupBy is the mount-time seed; only a doctype swap re-seeds.
|
|
448
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
449
|
+
}, [doctype]);
|
|
241
450
|
|
|
242
|
-
|
|
243
|
-
const [savedReports, setSavedReports] = useState<SavedReport[]>([]);
|
|
244
|
-
const [saveDialogOpen, setSaveDialogOpen] = useState(false);
|
|
245
|
-
const [saveReportName, setSaveReportName] = useState("");
|
|
451
|
+
useEffect(() => setFiltersCleared(false), [filtersKey]);
|
|
246
452
|
|
|
453
|
+
const effectiveFiltersKey = useMemo(() => JSON.stringify(effectiveFilters), [effectiveFilters]);
|
|
247
454
|
useEffect(() => {
|
|
455
|
+
setPage(0);
|
|
456
|
+
}, [effectiveFiltersKey, pageSize, groupBy]);
|
|
457
|
+
|
|
458
|
+
// ── Apply a saved report's config when the selection changes ─
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
if (!currentReport) {
|
|
461
|
+
setReportFilters(null);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
248
464
|
let cancelled = false;
|
|
249
|
-
void (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
465
|
+
void savedReports.load(currentReport).then((saved) => {
|
|
466
|
+
if (cancelled || !saved) return;
|
|
467
|
+
setSelectedOverride(saved.fieldnames.length > 0 ? saved.fieldnames : null);
|
|
468
|
+
setReportFilters(saved.filters);
|
|
469
|
+
setFilterDrafts({});
|
|
470
|
+
setCommittedFilters({});
|
|
471
|
+
setFiltersCleared(false);
|
|
472
|
+
if (saved.sort) setSortOverride(saved.sort);
|
|
473
|
+
if (saved.pageSize && REPORT_PAGE_SIZES.some((size) => size === saved.pageSize)) {
|
|
474
|
+
setPageSize(saved.pageSize);
|
|
253
475
|
}
|
|
254
|
-
|
|
476
|
+
setGroupBy(saved.groupBy ?? null);
|
|
477
|
+
});
|
|
255
478
|
return () => {
|
|
256
479
|
cancelled = true;
|
|
257
480
|
};
|
|
258
|
-
|
|
481
|
+
// savedReports.load changes identity with its report list; reloading the
|
|
482
|
+
// config on list refreshes would clobber in-progress edits — key on the
|
|
483
|
+
// selected report (and doctype via load's closure) only.
|
|
484
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
485
|
+
}, [currentReport, doctype]);
|
|
486
|
+
|
|
487
|
+
// ── Column selection changes ────────────────────────────────
|
|
488
|
+
const handleToggleColumn = useCallback(
|
|
489
|
+
(fieldname: string) => {
|
|
490
|
+
setSelectedOverride((previous) => {
|
|
491
|
+
const current = previous ?? startFieldnames ?? storedColumns ?? defaultFieldnames;
|
|
492
|
+
const next = current.includes(fieldname)
|
|
493
|
+
? current.length === 1
|
|
494
|
+
? current
|
|
495
|
+
: current.filter((entry) => entry !== fieldname)
|
|
496
|
+
: [...current, fieldname];
|
|
497
|
+
if (!fixtureMode && !currentReport) persistStoredColumns(doctype, next);
|
|
498
|
+
return next;
|
|
499
|
+
});
|
|
500
|
+
},
|
|
501
|
+
[startFieldnames, storedColumns, defaultFieldnames, fixtureMode, currentReport, doctype],
|
|
502
|
+
);
|
|
259
503
|
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
groupBy,
|
|
266
|
-
filters,
|
|
267
|
-
};
|
|
268
|
-
const updated = [...savedReports.filter((r) => r.name !== report.name), report];
|
|
269
|
-
setSavedReports(updated);
|
|
270
|
-
void persistSavedReports(doctype, updated);
|
|
271
|
-
setSaveDialogOpen(false);
|
|
272
|
-
setSaveReportName("");
|
|
273
|
-
}, [saveReportName, enabledColumns, groupBy, filters, savedReports, doctype]);
|
|
274
|
-
|
|
275
|
-
const handleLoadReport = useCallback((report: SavedReport) => {
|
|
276
|
-
setEnabledColumns(report.columns);
|
|
277
|
-
setGroupBy(report.groupBy);
|
|
278
|
-
}, []);
|
|
504
|
+
const handleResetColumns = useCallback(() => {
|
|
505
|
+
setSelectedOverride(defaultFieldnames);
|
|
506
|
+
setStoredColumns(undefined);
|
|
507
|
+
if (!fixtureMode) persistStoredColumns(doctype, defaultFieldnames);
|
|
508
|
+
}, [defaultFieldnames, fixtureMode, doctype]);
|
|
279
509
|
|
|
280
|
-
|
|
510
|
+
// ── Data: live collection over the full filtered result ─────
|
|
511
|
+
const fetchFields = useMemo(() => {
|
|
512
|
+
const fields = new Set<string>(["name", sort.field]);
|
|
513
|
+
for (const column of selectedColumns) fields.add(column.fieldname);
|
|
514
|
+
if (groupBy) fields.add(groupBy);
|
|
515
|
+
if (chartConfig?.aggregationField) fields.add(chartConfig.aggregationField);
|
|
516
|
+
return [...fields];
|
|
517
|
+
}, [selectedColumns, sort.field, groupBy, chartConfig?.aggregationField]);
|
|
518
|
+
|
|
519
|
+
const orderBy = useMemo(
|
|
520
|
+
() => [{ field: sort.field, order: sort.order === "asc" ? Order.Asc : Order.Desc }],
|
|
521
|
+
[sort.field, sort.order],
|
|
522
|
+
);
|
|
523
|
+
const {
|
|
524
|
+
rows: rowsData,
|
|
525
|
+
syncError,
|
|
526
|
+
isLoading: rowsLoading,
|
|
527
|
+
} = useFrappeRows<ReportRow & { doctype: string }>(connection, doctype, {
|
|
528
|
+
fields: fetchFields,
|
|
529
|
+
filters: effectiveFilters,
|
|
530
|
+
orderBy,
|
|
531
|
+
limit: null,
|
|
532
|
+
enabled: layoutMeta !== undefined,
|
|
533
|
+
mapError: asError,
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const orderedRows = useMemo(
|
|
537
|
+
() => orderReportRows(rowsData, sort, groupBy),
|
|
538
|
+
[rowsData, sort, groupBy],
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
const total = orderedRows.length;
|
|
542
|
+
const pageCount = Math.max(1, Math.ceil(total / pageSize));
|
|
543
|
+
const effectivePage = Math.min(page, pageCount - 1);
|
|
544
|
+
const gridRows = useMemo(
|
|
545
|
+
() => buildGridRows(orderedRows, effectivePage * pageSize, pageSize, groupBy),
|
|
546
|
+
[orderedRows, effectivePage, pageSize, groupBy],
|
|
547
|
+
);
|
|
548
|
+
const totals = useMemo(
|
|
549
|
+
() => computeTotals(selectedColumns, orderedRows),
|
|
550
|
+
[selectedColumns, orderedRows],
|
|
551
|
+
);
|
|
552
|
+
const groupByColumn = groupBy
|
|
553
|
+
? allColumns.find((column) => column.fieldname === groupBy)
|
|
554
|
+
: undefined;
|
|
555
|
+
const chartData = useMemo(
|
|
556
|
+
() => (showChart && groupBy ? aggregateGroups(orderedRows, groupBy, chartConfig) : []),
|
|
557
|
+
[showChart, groupBy, orderedRows, chartConfig],
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
// ── Navigation ──────────────────────────────────────────────
|
|
561
|
+
const rowsPressable = Boolean(onOpenRow || onRowPress);
|
|
562
|
+
const handleOpenRow = useCallback(
|
|
281
563
|
(name: string) => {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
564
|
+
onOpenRow?.(name);
|
|
565
|
+
if (onRowPress) {
|
|
566
|
+
const index = orderedRows.findIndex((row) => row.name === name);
|
|
567
|
+
if (index >= 0) onRowPress(orderedRows[index], index);
|
|
568
|
+
}
|
|
285
569
|
},
|
|
286
|
-
[
|
|
570
|
+
[onOpenRow, onRowPress, orderedRows],
|
|
287
571
|
);
|
|
288
572
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
573
|
+
const handleSelectReport = useCallback(
|
|
574
|
+
(name: string | null) => {
|
|
575
|
+
if (reportProp === undefined) setInternalReport(name);
|
|
576
|
+
onReportChange?.(name);
|
|
577
|
+
if (!name) {
|
|
578
|
+
setReportFilters(null);
|
|
579
|
+
setSelectedOverride(null);
|
|
580
|
+
setSortOverride(null);
|
|
581
|
+
setGroupBy(null);
|
|
295
582
|
}
|
|
583
|
+
},
|
|
584
|
+
[reportProp, onReportChange],
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
const handleClearBaseFilters = useCallback(() => {
|
|
588
|
+
setFiltersCleared(true);
|
|
589
|
+
if (propFilters && !reportFilters) onClearFilters?.();
|
|
590
|
+
}, [propFilters, reportFilters, onClearFilters]);
|
|
296
591
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
592
|
+
// ── Saved-report actions ────────────────────────────────────
|
|
593
|
+
const handleSaveReport = useCallback(
|
|
594
|
+
(name: string) => {
|
|
595
|
+
void savedReports
|
|
596
|
+
.save(name, {
|
|
597
|
+
fieldnames: [...selectedFieldnames],
|
|
598
|
+
filters: effectiveFilters,
|
|
599
|
+
sort,
|
|
600
|
+
pageSize,
|
|
601
|
+
groupBy,
|
|
602
|
+
addTotals: totals.length > 0,
|
|
603
|
+
})
|
|
604
|
+
.then((savedName) => {
|
|
605
|
+
notify({
|
|
606
|
+
severity: "success",
|
|
607
|
+
scope: "page",
|
|
608
|
+
body: t("report.saveDone", {
|
|
609
|
+
defaultValue: "Saved report “{{name}}”",
|
|
610
|
+
name: savedName,
|
|
611
|
+
}),
|
|
612
|
+
});
|
|
613
|
+
if (savedName !== currentReport) handleSelectReport(savedName);
|
|
614
|
+
})
|
|
615
|
+
.catch((error: unknown) => {
|
|
616
|
+
notify({
|
|
617
|
+
severity: "error",
|
|
618
|
+
scope: "page",
|
|
619
|
+
body:
|
|
620
|
+
error instanceof Error
|
|
621
|
+
? error.message
|
|
622
|
+
: t("report.saveFailed", "Could not save the report"),
|
|
623
|
+
});
|
|
311
624
|
});
|
|
312
|
-
|
|
313
|
-
|
|
625
|
+
},
|
|
626
|
+
[
|
|
627
|
+
savedReports,
|
|
628
|
+
selectedFieldnames,
|
|
629
|
+
effectiveFilters,
|
|
630
|
+
sort,
|
|
631
|
+
pageSize,
|
|
632
|
+
groupBy,
|
|
633
|
+
totals.length,
|
|
634
|
+
currentReport,
|
|
635
|
+
handleSelectReport,
|
|
636
|
+
t,
|
|
637
|
+
],
|
|
638
|
+
);
|
|
314
639
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
640
|
+
const handleDeleteReport = useCallback(
|
|
641
|
+
(name: string) => {
|
|
642
|
+
void savedReports
|
|
643
|
+
.remove(name)
|
|
644
|
+
.then(() => {
|
|
645
|
+
notify({
|
|
646
|
+
severity: "success",
|
|
647
|
+
scope: "page",
|
|
648
|
+
body: t("report.deleteDone", {
|
|
649
|
+
defaultValue: "Deleted report “{{name}}”",
|
|
650
|
+
name,
|
|
651
|
+
}),
|
|
652
|
+
});
|
|
653
|
+
if (name === currentReport) handleSelectReport(null);
|
|
654
|
+
})
|
|
655
|
+
.catch((error: unknown) => {
|
|
656
|
+
notify({
|
|
657
|
+
severity: "error",
|
|
658
|
+
scope: "page",
|
|
659
|
+
body:
|
|
660
|
+
error instanceof Error
|
|
661
|
+
? error.message
|
|
662
|
+
: t("report.deleteFailed", "Could not delete the report"),
|
|
663
|
+
});
|
|
664
|
+
});
|
|
318
665
|
},
|
|
319
|
-
[
|
|
666
|
+
[savedReports, currentReport, handleSelectReport, t],
|
|
320
667
|
);
|
|
321
668
|
|
|
322
|
-
//
|
|
323
|
-
// on every platform; the built-in CSV path only shows where the web download
|
|
324
|
-
// pipeline exists. `undefined` hides ReportBuilder's Export button entirely,
|
|
325
|
-
// so native/GTK never render a dead control. Web behavior is unchanged.
|
|
669
|
+
// ── Export: full filtered + ordered result ──────────────────
|
|
326
670
|
const exportEnabled = Boolean(onExport) || canDownloadCsv();
|
|
671
|
+
const handleExport = useCallback(() => {
|
|
672
|
+
if (onExport) {
|
|
673
|
+
onExport(orderedRows);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
downloadBlob(
|
|
677
|
+
new Blob([buildReportCsv(selectedColumns, orderedRows)], {
|
|
678
|
+
type: "text/csv;charset=utf-8;",
|
|
679
|
+
}),
|
|
680
|
+
`${doctype}_report.csv`,
|
|
681
|
+
);
|
|
682
|
+
notify({
|
|
683
|
+
severity: "success",
|
|
684
|
+
scope: "page",
|
|
685
|
+
body: t("report.exportDone", {
|
|
686
|
+
count: orderedRows.length,
|
|
687
|
+
defaultValue_one: "Exported {{count}} row to CSV",
|
|
688
|
+
defaultValue_other: "Exported {{count}} rows to CSV",
|
|
689
|
+
}),
|
|
690
|
+
});
|
|
691
|
+
}, [onExport, selectedColumns, orderedRows, doctype, t]);
|
|
692
|
+
|
|
693
|
+
const handleColumnFilterChange = useCallback((fieldname: string, value: string) => {
|
|
694
|
+
setFilterDrafts((previous) => {
|
|
695
|
+
if (value === "") {
|
|
696
|
+
if (!(fieldname in previous)) return previous;
|
|
697
|
+
const { [fieldname]: _dropped, ...rest } = previous;
|
|
698
|
+
return rest;
|
|
699
|
+
}
|
|
700
|
+
return { ...previous, [fieldname]: value };
|
|
701
|
+
});
|
|
702
|
+
}, []);
|
|
327
703
|
|
|
328
|
-
|
|
704
|
+
const handleClearColumnFilters = useCallback(() => {
|
|
705
|
+
setFilterDrafts({});
|
|
706
|
+
setCommittedFilters({});
|
|
707
|
+
}, []);
|
|
708
|
+
|
|
709
|
+
// ── Render ──────────────────────────────────────────────────
|
|
710
|
+
if (metaLoading && !meta) {
|
|
329
711
|
return (
|
|
330
|
-
|
|
331
|
-
{
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
<Spinner size="large" />
|
|
338
|
-
<Text color="$color10">{t("Loading {{doctype}} schema...", { doctype })}</Text>
|
|
339
|
-
</YStack>
|
|
712
|
+
<>
|
|
713
|
+
{loadingFallback ?? (
|
|
714
|
+
<YStack flex={1} alignItems="center" justifyContent="center" {...stackProps}>
|
|
715
|
+
<Spinner size="large" />
|
|
716
|
+
</YStack>
|
|
717
|
+
)}
|
|
718
|
+
</>
|
|
340
719
|
);
|
|
341
720
|
}
|
|
342
|
-
|
|
343
|
-
|
|
721
|
+
if (metaError && metaRefused && !layoutMeta) {
|
|
722
|
+
if (recordShape.status === "pending") {
|
|
723
|
+
return (
|
|
724
|
+
<>
|
|
725
|
+
{loadingFallback ?? (
|
|
726
|
+
<YStack flex={1} alignItems="center" justifyContent="center" {...stackProps}>
|
|
727
|
+
<Spinner size="large" />
|
|
728
|
+
</YStack>
|
|
729
|
+
)}
|
|
730
|
+
</>
|
|
731
|
+
);
|
|
732
|
+
}
|
|
344
733
|
return (
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
734
|
+
<>
|
|
735
|
+
{errorFallback ? (
|
|
736
|
+
errorFallback(metaError)
|
|
737
|
+
) : (
|
|
738
|
+
<EmptyState
|
|
739
|
+
testID="frappe-report-not-permitted"
|
|
740
|
+
title={t("report.notPermitted", {
|
|
741
|
+
defaultValue: "You can't open the {{doctype}} report",
|
|
742
|
+
doctype,
|
|
743
|
+
})}
|
|
744
|
+
description={refusalReason(metaError)}
|
|
745
|
+
/>
|
|
746
|
+
)}
|
|
747
|
+
</>
|
|
351
748
|
);
|
|
352
749
|
}
|
|
353
|
-
|
|
354
|
-
if (isLoading) {
|
|
750
|
+
if (metaError && !meta && !readsWithoutMeta) {
|
|
355
751
|
return (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
752
|
+
<>
|
|
753
|
+
{errorFallback ? (
|
|
754
|
+
errorFallback(metaError)
|
|
755
|
+
) : (
|
|
756
|
+
<EmptyState
|
|
757
|
+
intent="error"
|
|
758
|
+
title={t("report.metaFailed", {
|
|
759
|
+
defaultValue: "Couldn't load the {{doctype}} report",
|
|
760
|
+
doctype,
|
|
761
|
+
})}
|
|
762
|
+
description={metaError.message}
|
|
763
|
+
/>
|
|
764
|
+
)}
|
|
765
|
+
</>
|
|
362
766
|
);
|
|
363
767
|
}
|
|
364
768
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
769
|
+
let body: React.ReactNode;
|
|
770
|
+
if (syncError && isPermissionError(syncError)) {
|
|
771
|
+
body = (
|
|
772
|
+
<EmptyState
|
|
773
|
+
testID="frappe-report-not-permitted"
|
|
774
|
+
title={t("report.notPermitted", {
|
|
775
|
+
defaultValue: "You can't open the {{doctype}} report",
|
|
776
|
+
doctype,
|
|
777
|
+
})}
|
|
778
|
+
description={refusalReason(syncError)}
|
|
779
|
+
/>
|
|
780
|
+
);
|
|
781
|
+
} else if (syncError) {
|
|
782
|
+
body = (
|
|
783
|
+
<EmptyState
|
|
784
|
+
intent="error"
|
|
785
|
+
title={t("report.loadFailed", "Couldn't load report rows")}
|
|
786
|
+
description={syncError.message}
|
|
787
|
+
/>
|
|
788
|
+
);
|
|
789
|
+
} else if (rowsLoading && total === 0) {
|
|
790
|
+
body = (
|
|
791
|
+
<YStack flex={1} alignItems="center" justifyContent="center">
|
|
792
|
+
<Spinner size="large" />
|
|
372
793
|
</YStack>
|
|
373
794
|
);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
{
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
{/* Save */}
|
|
398
|
-
<Popover placement="bottom-end" open={saveDialogOpen} onOpenChange={setSaveDialogOpen}>
|
|
399
|
-
<Popover.Trigger asChild>
|
|
400
|
-
<Button size="$2" icon={FloppyDiskIcon}>
|
|
401
|
-
{t("Save")}
|
|
795
|
+
} else if (total === 0) {
|
|
796
|
+
const hasFilters = effectiveFilters.length > 0;
|
|
797
|
+
body = (
|
|
798
|
+
<EmptyState
|
|
799
|
+
title={t("report.emptyTitle", "No results")}
|
|
800
|
+
description={
|
|
801
|
+
hasFilters
|
|
802
|
+
? t("report.emptyFiltered", "No rows match the current filters")
|
|
803
|
+
: t("report.emptyDescription", {
|
|
804
|
+
defaultValue: "There are no {{doctype}} records yet",
|
|
805
|
+
doctype,
|
|
806
|
+
})
|
|
807
|
+
}
|
|
808
|
+
action={
|
|
809
|
+
hasFilters ? (
|
|
810
|
+
<Button
|
|
811
|
+
size="$3"
|
|
812
|
+
onPress={() => {
|
|
813
|
+
handleClearColumnFilters();
|
|
814
|
+
handleClearBaseFilters();
|
|
815
|
+
}}
|
|
816
|
+
>
|
|
817
|
+
{t("report.clearAllFilters", "Clear all filters")}
|
|
402
818
|
</Button>
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
onChangeText={setSaveReportName}
|
|
415
|
-
inputProps={{ onSubmitEditing: handleSaveReport } as any}
|
|
416
|
-
/>
|
|
417
|
-
<Button size="$2" onPress={handleSaveReport} disabled={!saveReportName.trim()}>
|
|
418
|
-
{t("Save")}
|
|
419
|
-
</Button>
|
|
420
|
-
</YStack>
|
|
421
|
-
</PopoverContent>
|
|
422
|
-
</Popover>
|
|
423
|
-
|
|
424
|
-
{/* Load */}
|
|
425
|
-
{savedReports.length > 0 && (
|
|
426
|
-
<Popover placement="bottom-end">
|
|
427
|
-
<Popover.Trigger asChild>
|
|
428
|
-
<Button size="$2" icon={FolderOpenIcon}>
|
|
429
|
-
{t("Load")}
|
|
430
|
-
</Button>
|
|
431
|
-
</Popover.Trigger>
|
|
432
|
-
<PopoverContent>
|
|
433
|
-
<YStack {...knobProps.gap} minWidth={180}>
|
|
434
|
-
<Text {...knobProps.label} fontWeight="400" color={formCommonColors.muted}>
|
|
435
|
-
{t("Saved Reports")}
|
|
436
|
-
</Text>
|
|
437
|
-
<Separator />
|
|
438
|
-
{savedReports.map((report) => (
|
|
439
|
-
<XStack
|
|
440
|
-
key={report.name}
|
|
441
|
-
alignItems="center"
|
|
442
|
-
justifyContent="space-between"
|
|
443
|
-
padding="$2"
|
|
444
|
-
{...knobProps.containerRadius}
|
|
445
|
-
hoverStyle={{ backgroundColor: "$color2" }}
|
|
446
|
-
cursor="pointer"
|
|
447
|
-
onPress={() => handleLoadReport(report)}
|
|
448
|
-
>
|
|
449
|
-
<Text {...knobProps.body} flex={1}>
|
|
450
|
-
{report.name}
|
|
451
|
-
</Text>
|
|
452
|
-
<Button
|
|
453
|
-
size="$2"
|
|
454
|
-
circular
|
|
455
|
-
chromeless
|
|
456
|
-
icon={TrashIcon}
|
|
457
|
-
onPress={(e) => {
|
|
458
|
-
(e as unknown as { stopPropagation: () => void }).stopPropagation();
|
|
459
|
-
handleDeleteReport(report.name);
|
|
460
|
-
}}
|
|
461
|
-
aria-label={t("Delete {{name}}", { name: report.name })}
|
|
462
|
-
/>
|
|
463
|
-
</XStack>
|
|
464
|
-
))}
|
|
465
|
-
</YStack>
|
|
466
|
-
</PopoverContent>
|
|
467
|
-
</Popover>
|
|
819
|
+
) : undefined
|
|
820
|
+
}
|
|
821
|
+
/>
|
|
822
|
+
);
|
|
823
|
+
} else {
|
|
824
|
+
body = (
|
|
825
|
+
<YStack flex={1} minWidth={0} {...knobProps.gap}>
|
|
826
|
+
{chartData.length > 0 && (
|
|
827
|
+
<YStack {...knobProps.surface} {...knobProps.containerRadius} {...knobProps.panelPadding}>
|
|
828
|
+
<BarChart data={chartData} />
|
|
829
|
+
</YStack>
|
|
468
830
|
)}
|
|
469
|
-
|
|
831
|
+
<ReportGrid
|
|
832
|
+
columns={selectedColumns}
|
|
833
|
+
rows={gridRows}
|
|
834
|
+
sort={sort}
|
|
835
|
+
onSortChange={setSortOverride}
|
|
836
|
+
columnFilters={filterDrafts}
|
|
837
|
+
onColumnFilterChange={handleColumnFilterChange}
|
|
838
|
+
onOpenRow={rowsPressable ? handleOpenRow : undefined}
|
|
839
|
+
onOpenLink={onOpenLink}
|
|
840
|
+
totals={totals}
|
|
841
|
+
groupByColumn={groupByColumn}
|
|
842
|
+
/>
|
|
843
|
+
</YStack>
|
|
844
|
+
);
|
|
845
|
+
}
|
|
470
846
|
|
|
471
|
-
|
|
472
|
-
|
|
847
|
+
const content = (
|
|
848
|
+
<YStack flex={1} minWidth={0} {...stackProps}>
|
|
849
|
+
<ReportViewToolbar
|
|
473
850
|
columns={allColumns}
|
|
474
|
-
|
|
475
|
-
|
|
851
|
+
selected={selectedFieldnames}
|
|
852
|
+
onToggleColumn={handleToggleColumn}
|
|
853
|
+
onResetColumns={handleResetColumns}
|
|
854
|
+
groupBy={groupBy}
|
|
476
855
|
onGroupByChange={setGroupBy}
|
|
477
|
-
|
|
856
|
+
baseFilters={baseFilters}
|
|
857
|
+
onClearBaseFilters={baseFilters.length > 0 ? handleClearBaseFilters : undefined}
|
|
858
|
+
columnFilterCount={columnTuples.length}
|
|
859
|
+
onClearColumnFilters={handleClearColumnFilters}
|
|
860
|
+
savedReports={savedReports.reports}
|
|
861
|
+
currentReport={currentReport}
|
|
862
|
+
onSelectReport={handleSelectReport}
|
|
863
|
+
onSaveReport={readsWithoutMeta ? undefined : handleSaveReport}
|
|
864
|
+
onDeleteReport={readsWithoutMeta ? undefined : handleDeleteReport}
|
|
478
865
|
onExport={exportEnabled ? handleExport : undefined}
|
|
479
|
-
|
|
480
|
-
showChart={showChart}
|
|
481
|
-
flex={1}
|
|
866
|
+
exportDisabled={total === 0}
|
|
482
867
|
/>
|
|
868
|
+
<YStack flex={1} minWidth={0} paddingHorizontal="$3">
|
|
869
|
+
{body}
|
|
870
|
+
</YStack>
|
|
871
|
+
{total > 0 && !syncError && (
|
|
872
|
+
<YStack paddingHorizontal="$3" paddingVertical="$2">
|
|
873
|
+
<DataTablePagination
|
|
874
|
+
pagination={{
|
|
875
|
+
pageIndex: effectivePage,
|
|
876
|
+
pageSize,
|
|
877
|
+
totalCount: total,
|
|
878
|
+
totalPages: pageCount,
|
|
879
|
+
}}
|
|
880
|
+
onPaginationChange={(next) => {
|
|
881
|
+
setPage(next.pageIndex);
|
|
882
|
+
setPageSize(next.pageSize);
|
|
883
|
+
}}
|
|
884
|
+
pageSizeOptions={[...REPORT_PAGE_SIZES]}
|
|
885
|
+
compact
|
|
886
|
+
/>
|
|
887
|
+
</YStack>
|
|
888
|
+
)}
|
|
483
889
|
</YStack>
|
|
484
890
|
);
|
|
891
|
+
|
|
892
|
+
// Native has no page-level scroll (the desk list precedent) — wrap so rows
|
|
893
|
+
// below the fold stay reachable. Web keeps the flex layout.
|
|
894
|
+
return isWeb ? content : <ScrollView flex={1}>{content}</ScrollView>;
|
|
485
895
|
}
|
|
486
896
|
|
|
487
897
|
export type { ReportColumn, GroupByConfig } from "@multiplatform.one/components";
|