@multiplatform.one/backoffice 7.28.1 → 7.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/desk/DeskSurface.native.js.map +1 -1
  3. package/dist/cjs/form/DocFormScreen.cjs +167 -224
  4. package/dist/cjs/form/DocFormScreen.native.js +182 -259
  5. package/dist/cjs/form/DocFormScreen.native.js.map +1 -1
  6. package/dist/cjs/i18n/en.cjs +1 -0
  7. package/dist/cjs/i18n/en.native.js +1 -0
  8. package/dist/cjs/i18n/en.native.js.map +1 -1
  9. package/dist/cjs/list/ListBulkActionsBar.cjs +30 -18
  10. package/dist/cjs/list/ListBulkActionsBar.native.js +43 -31
  11. package/dist/cjs/list/ListBulkActionsBar.native.js.map +1 -1
  12. package/dist/cjs/list/sidebar-section.cjs +15 -7
  13. package/dist/cjs/list/sidebar-section.native.js +16 -7
  14. package/dist/cjs/list/sidebar-section.native.js.map +1 -1
  15. package/dist/cjs/report/DoctypeReportView.cjs +20 -374
  16. package/dist/cjs/report/DoctypeReportView.native.js +23 -455
  17. package/dist/cjs/report/DoctypeReportView.native.js.map +1 -1
  18. package/dist/cjs/report/QueryReportScreen.cjs +33 -23
  19. package/dist/cjs/report/QueryReportScreen.native.js +33 -23
  20. package/dist/cjs/report/QueryReportScreen.native.js.map +1 -1
  21. package/dist/cjs/report/queryReportColumns.cjs +3 -105
  22. package/dist/cjs/report/queryReportColumns.native.js +2 -129
  23. package/dist/cjs/report/queryReportColumns.native.js.map +1 -1
  24. package/dist/cjs/report/queryReportFilterBar.cjs +3 -4
  25. package/dist/cjs/report/queryReportFilterBar.native.js +3 -4
  26. package/dist/cjs/report/queryReportFilterBar.native.js.map +1 -1
  27. package/dist/cjs/report/queryReportUrlState.cjs +2 -2
  28. package/dist/cjs/report/queryReportUrlState.native.js +2 -2
  29. package/dist/cjs/report/queryReportUrlState.native.js.map +1 -1
  30. package/dist/cjs/report/report-grid.cjs +2 -426
  31. package/dist/cjs/report/report-grid.native.js +1 -479
  32. package/dist/cjs/report/report-grid.native.js.map +1 -1
  33. package/dist/cjs/report/report-toolbar.cjs +2 -228
  34. package/dist/cjs/report/report-toolbar.native.js +1 -274
  35. package/dist/cjs/report/report-toolbar.native.js.map +1 -1
  36. package/dist/cjs/report/reportShapes.cjs +12 -383
  37. package/dist/cjs/report/reportShapes.native.js +12 -501
  38. package/dist/cjs/report/reportShapes.native.js.map +1 -1
  39. package/dist/cjs/report/reportViewModel.cjs +20 -360
  40. package/dist/cjs/report/reportViewModel.native.js +19 -627
  41. package/dist/cjs/report/reportViewModel.native.js.map +1 -1
  42. package/dist/cjs/report/savedReports.cjs +2 -106
  43. package/dist/cjs/report/savedReports.native.js +1 -132
  44. package/dist/cjs/report/savedReports.native.js.map +1 -1
  45. package/dist/cjs/report/useQueryReport.cjs +11 -219
  46. package/dist/cjs/report/useQueryReport.native.js +11 -244
  47. package/dist/cjs/report/useQueryReport.native.js.map +1 -1
  48. package/dist/cjs/shared/measures.cjs +3 -5
  49. package/dist/cjs/shared/measures.native.js +3 -5
  50. package/dist/cjs/shared/measures.native.js.map +1 -1
  51. package/dist/cjs/views/ReportView.cjs +3 -256
  52. package/dist/cjs/views/ReportView.native.js +3 -382
  53. package/dist/cjs/views/ReportView.native.js.map +1 -1
  54. package/dist/cjs/views/report-controls.cjs +6 -6
  55. package/dist/cjs/views/report-controls.native.js +6 -6
  56. package/dist/cjs/views/report-controls.native.js.map +1 -1
  57. package/dist/esm/desk/DeskSurface.mjs.map +1 -1
  58. package/dist/esm/desk/DeskSurface.native.js.map +1 -1
  59. package/dist/esm/form/DocFormScreen.mjs +169 -226
  60. package/dist/esm/form/DocFormScreen.mjs.map +1 -1
  61. package/dist/esm/form/DocFormScreen.native.js +184 -261
  62. package/dist/esm/form/DocFormScreen.native.js.map +1 -1
  63. package/dist/esm/i18n/en.mjs +1 -0
  64. package/dist/esm/i18n/en.mjs.map +1 -1
  65. package/dist/esm/i18n/en.native.js +1 -0
  66. package/dist/esm/i18n/en.native.js.map +1 -1
  67. package/dist/esm/list/ListBulkActionsBar.mjs +31 -19
  68. package/dist/esm/list/ListBulkActionsBar.mjs.map +1 -1
  69. package/dist/esm/list/ListBulkActionsBar.native.js +37 -25
  70. package/dist/esm/list/ListBulkActionsBar.native.js.map +1 -1
  71. package/dist/esm/list/sidebar-section.mjs +16 -8
  72. package/dist/esm/list/sidebar-section.mjs.map +1 -1
  73. package/dist/esm/list/sidebar-section.native.js +17 -8
  74. package/dist/esm/list/sidebar-section.native.js.map +1 -1
  75. package/dist/esm/report/DoctypeReportView.mjs +21 -375
  76. package/dist/esm/report/DoctypeReportView.mjs.map +1 -1
  77. package/dist/esm/report/DoctypeReportView.native.js +24 -456
  78. package/dist/esm/report/DoctypeReportView.native.js.map +1 -1
  79. package/dist/esm/report/QueryReportScreen.mjs +26 -16
  80. package/dist/esm/report/QueryReportScreen.mjs.map +1 -1
  81. package/dist/esm/report/QueryReportScreen.native.js +26 -16
  82. package/dist/esm/report/QueryReportScreen.native.js.map +1 -1
  83. package/dist/esm/report/queryReportColumns.mjs +1 -103
  84. package/dist/esm/report/queryReportColumns.mjs.map +1 -1
  85. package/dist/esm/report/queryReportColumns.native.js +1 -117
  86. package/dist/esm/report/queryReportColumns.native.js.map +1 -1
  87. package/dist/esm/report/queryReportFilterBar.mjs +1 -2
  88. package/dist/esm/report/queryReportFilterBar.mjs.map +1 -1
  89. package/dist/esm/report/queryReportFilterBar.native.js +1 -2
  90. package/dist/esm/report/queryReportFilterBar.native.js.map +1 -1
  91. package/dist/esm/report/queryReportUrlState.mjs +1 -1
  92. package/dist/esm/report/queryReportUrlState.native.js +1 -1
  93. package/dist/esm/report/report-grid.mjs +1 -425
  94. package/dist/esm/report/report-grid.mjs.map +1 -1
  95. package/dist/esm/report/report-grid.native.js +1 -468
  96. package/dist/esm/report/report-grid.native.js.map +1 -1
  97. package/dist/esm/report/report-toolbar.mjs +2 -228
  98. package/dist/esm/report/report-toolbar.mjs.map +1 -1
  99. package/dist/esm/report/report-toolbar.native.js +2 -264
  100. package/dist/esm/report/report-toolbar.native.js.map +1 -1
  101. package/dist/esm/report/reportShapes.mjs +2 -373
  102. package/dist/esm/report/reportShapes.mjs.map +1 -1
  103. package/dist/esm/report/reportShapes.native.js +2 -491
  104. package/dist/esm/report/reportShapes.native.js.map +1 -1
  105. package/dist/esm/report/reportViewModel.mjs +2 -342
  106. package/dist/esm/report/reportViewModel.mjs.map +1 -1
  107. package/dist/esm/report/reportViewModel.native.js +2 -610
  108. package/dist/esm/report/reportViewModel.native.js.map +1 -1
  109. package/dist/esm/report/savedReports.mjs +1 -105
  110. package/dist/esm/report/savedReports.mjs.map +1 -1
  111. package/dist/esm/report/savedReports.native.js +1 -132
  112. package/dist/esm/report/savedReports.native.js.map +1 -1
  113. package/dist/esm/report/useQueryReport.mjs +2 -210
  114. package/dist/esm/report/useQueryReport.mjs.map +1 -1
  115. package/dist/esm/report/useQueryReport.native.js +2 -235
  116. package/dist/esm/report/useQueryReport.native.js.map +1 -1
  117. package/dist/esm/shared/measures.mjs +1 -3
  118. package/dist/esm/shared/measures.mjs.map +1 -1
  119. package/dist/esm/shared/measures.native.js +1 -3
  120. package/dist/esm/shared/measures.native.js.map +1 -1
  121. package/dist/esm/views/ReportView.mjs +3 -256
  122. package/dist/esm/views/ReportView.mjs.map +1 -1
  123. package/dist/esm/views/ReportView.native.js +3 -382
  124. package/dist/esm/views/ReportView.native.js.map +1 -1
  125. package/dist/esm/views/report-controls.mjs +3 -3
  126. package/dist/esm/views/report-controls.mjs.map +1 -1
  127. package/dist/esm/views/report-controls.native.js +3 -3
  128. package/dist/esm/views/report-controls.native.js.map +1 -1
  129. package/dist/jsx/desk/DeskSurface.mjs.map +1 -1
  130. package/dist/jsx/desk/DeskSurface.native.js.map +1 -1
  131. package/dist/jsx/form/DocFormScreen.mjs +169 -226
  132. package/dist/jsx/form/DocFormScreen.mjs.map +1 -1
  133. package/dist/jsx/form/DocFormScreen.native.js +182 -259
  134. package/dist/jsx/form/DocFormScreen.native.js.map +1 -1
  135. package/dist/jsx/i18n/en.mjs +1 -0
  136. package/dist/jsx/i18n/en.mjs.map +1 -1
  137. package/dist/jsx/i18n/en.native.js +1 -0
  138. package/dist/jsx/i18n/en.native.js.map +1 -1
  139. package/dist/jsx/list/ListBulkActionsBar.mjs +31 -19
  140. package/dist/jsx/list/ListBulkActionsBar.mjs.map +1 -1
  141. package/dist/jsx/list/ListBulkActionsBar.native.js +43 -31
  142. package/dist/jsx/list/ListBulkActionsBar.native.js.map +1 -1
  143. package/dist/jsx/list/sidebar-section.mjs +16 -8
  144. package/dist/jsx/list/sidebar-section.mjs.map +1 -1
  145. package/dist/jsx/list/sidebar-section.native.js +16 -7
  146. package/dist/jsx/list/sidebar-section.native.js.map +1 -1
  147. package/dist/jsx/report/DoctypeReportView.mjs +21 -375
  148. package/dist/jsx/report/DoctypeReportView.mjs.map +1 -1
  149. package/dist/jsx/report/DoctypeReportView.native.js +23 -455
  150. package/dist/jsx/report/DoctypeReportView.native.js.map +1 -1
  151. package/dist/jsx/report/QueryReportScreen.mjs +26 -16
  152. package/dist/jsx/report/QueryReportScreen.mjs.map +1 -1
  153. package/dist/jsx/report/QueryReportScreen.native.js +33 -23
  154. package/dist/jsx/report/QueryReportScreen.native.js.map +1 -1
  155. package/dist/jsx/report/queryReportColumns.mjs +1 -103
  156. package/dist/jsx/report/queryReportColumns.mjs.map +1 -1
  157. package/dist/jsx/report/queryReportColumns.native.js +2 -129
  158. package/dist/jsx/report/queryReportColumns.native.js.map +1 -1
  159. package/dist/jsx/report/queryReportFilterBar.mjs +1 -2
  160. package/dist/jsx/report/queryReportFilterBar.mjs.map +1 -1
  161. package/dist/jsx/report/queryReportFilterBar.native.js +3 -4
  162. package/dist/jsx/report/queryReportFilterBar.native.js.map +1 -1
  163. package/dist/jsx/report/queryReportUrlState.mjs +1 -1
  164. package/dist/jsx/report/queryReportUrlState.native.js +2 -2
  165. package/dist/jsx/report/queryReportUrlState.native.js.map +1 -1
  166. package/dist/jsx/report/report-grid.mjs +1 -425
  167. package/dist/jsx/report/report-grid.mjs.map +1 -1
  168. package/dist/jsx/report/report-grid.native.js +1 -479
  169. package/dist/jsx/report/report-grid.native.js.map +1 -1
  170. package/dist/jsx/report/report-toolbar.mjs +2 -228
  171. package/dist/jsx/report/report-toolbar.mjs.map +1 -1
  172. package/dist/jsx/report/report-toolbar.native.js +1 -274
  173. package/dist/jsx/report/report-toolbar.native.js.map +1 -1
  174. package/dist/jsx/report/reportShapes.mjs +2 -373
  175. package/dist/jsx/report/reportShapes.mjs.map +1 -1
  176. package/dist/jsx/report/reportShapes.native.js +12 -501
  177. package/dist/jsx/report/reportShapes.native.js.map +1 -1
  178. package/dist/jsx/report/reportViewModel.mjs +2 -342
  179. package/dist/jsx/report/reportViewModel.mjs.map +1 -1
  180. package/dist/jsx/report/reportViewModel.native.js +19 -627
  181. package/dist/jsx/report/reportViewModel.native.js.map +1 -1
  182. package/dist/jsx/report/savedReports.mjs +1 -105
  183. package/dist/jsx/report/savedReports.mjs.map +1 -1
  184. package/dist/jsx/report/savedReports.native.js +1 -132
  185. package/dist/jsx/report/savedReports.native.js.map +1 -1
  186. package/dist/jsx/report/useQueryReport.mjs +2 -210
  187. package/dist/jsx/report/useQueryReport.mjs.map +1 -1
  188. package/dist/jsx/report/useQueryReport.native.js +11 -244
  189. package/dist/jsx/report/useQueryReport.native.js.map +1 -1
  190. package/dist/jsx/shared/measures.mjs +1 -3
  191. package/dist/jsx/shared/measures.mjs.map +1 -1
  192. package/dist/jsx/shared/measures.native.js +3 -5
  193. package/dist/jsx/shared/measures.native.js.map +1 -1
  194. package/dist/jsx/views/ReportView.mjs +3 -256
  195. package/dist/jsx/views/ReportView.mjs.map +1 -1
  196. package/dist/jsx/views/ReportView.native.js +3 -382
  197. package/dist/jsx/views/ReportView.native.js.map +1 -1
  198. package/dist/jsx/views/report-controls.mjs +3 -3
  199. package/dist/jsx/views/report-controls.mjs.map +1 -1
  200. package/dist/jsx/views/report-controls.native.js +6 -6
  201. package/dist/jsx/views/report-controls.native.js.map +1 -1
  202. package/package.json +12 -12
  203. package/src/backofficeScreens.designLaw.spec.ts +0 -5
  204. package/src/desk/DeskSurface.tsx +4 -7
  205. package/src/form/DocFormScreen.chrome.spec.ts +77 -0
  206. package/src/form/DocFormScreen.guest.screen.spec.tsx +175 -0
  207. package/src/form/DocFormScreen.rights.spec.tsx +7 -3
  208. package/src/form/DocFormScreen.tsx +214 -315
  209. package/src/i18n/en.ts +1 -0
  210. package/src/list/ListBulkActionsBar.rights.spec.tsx +134 -0
  211. package/src/list/ListBulkActionsBar.screen.spec.tsx +7 -1
  212. package/src/list/ListBulkActionsBar.tsx +76 -57
  213. package/src/list/ListFilterBar.screen.spec.tsx +14 -13
  214. package/src/list/sidebar-section.tsx +11 -3
  215. package/src/report/DoctypeReportView.tsx +28 -550
  216. package/src/report/QueryReportScreen.spec.tsx +75 -1
  217. package/src/report/QueryReportScreen.tsx +38 -22
  218. package/src/report/queryReportColumns.tsx +4 -123
  219. package/src/report/queryReportFilterBar.tsx +3 -5
  220. package/src/report/queryReportUrlState.ts +1 -1
  221. package/src/report/report-grid.tsx +4 -529
  222. package/src/report/report-toolbar.tsx +4 -308
  223. package/src/report/reportShapes.ts +25 -613
  224. package/src/report/reportShim.spec.ts +162 -0
  225. package/src/report/reportViewModel.ts +32 -605
  226. package/src/report/savedReports.ts +4 -161
  227. package/src/report/useQueryReport.ts +9 -347
  228. package/src/shared/measures.ts +3 -11
  229. package/src/views/ReportView.screen.spec.tsx +25 -20
  230. package/src/views/ReportView.tsx +7 -439
  231. package/src/views/report-controls.tsx +12 -20
  232. package/types/desk/DeskSurface.d.ts.map +1 -1
  233. package/types/form/DocFormScreen.d.ts +2 -2
  234. package/types/form/DocFormScreen.d.ts.map +1 -1
  235. package/types/i18n/en.d.ts +1 -0
  236. package/types/i18n/en.d.ts.map +1 -1
  237. package/types/list/ListBulkActionsBar.d.ts.map +1 -1
  238. package/types/list/sidebar-section.d.ts.map +1 -1
  239. package/types/report/DoctypeReportView.d.ts.map +1 -1
  240. package/types/report/QueryReportScreen.d.ts +1 -1
  241. package/types/report/QueryReportScreen.d.ts.map +1 -1
  242. package/types/report/queryReportColumns.d.ts +2 -11
  243. package/types/report/queryReportColumns.d.ts.map +1 -1
  244. package/types/report/queryReportFilterBar.d.ts +1 -1
  245. package/types/report/queryReportFilterBar.d.ts.map +1 -1
  246. package/types/report/queryReportUrlState.d.ts +1 -1
  247. package/types/report/queryReportUrlState.d.ts.map +1 -1
  248. package/types/report/report-grid.d.ts +2 -19
  249. package/types/report/report-grid.d.ts.map +1 -1
  250. package/types/report/report-toolbar.d.ts +2 -29
  251. package/types/report/report-toolbar.d.ts.map +1 -1
  252. package/types/report/reportShapes.d.ts +2 -115
  253. package/types/report/reportShapes.d.ts.map +1 -1
  254. package/types/report/reportViewModel.d.ts +2 -135
  255. package/types/report/reportViewModel.d.ts.map +1 -1
  256. package/types/report/savedReports.d.ts +2 -18
  257. package/types/report/savedReports.d.ts.map +1 -1
  258. package/types/report/useQueryReport.d.ts +2 -35
  259. package/types/report/useQueryReport.d.ts.map +1 -1
  260. package/types/shared/measures.d.ts +1 -2
  261. package/types/shared/measures.d.ts.map +1 -1
  262. package/types/views/ReportView.d.ts +3 -22
  263. package/types/views/ReportView.d.ts.map +1 -1
  264. package/types/views/report-controls.d.ts +2 -5
  265. package/types/views/report-controls.d.ts.map +1 -1
  266. package/src/report/report-grid.spec.tsx +0 -175
  267. package/src/report/report-toolbar.spec.tsx +0 -90
  268. package/src/report/reportShapes.spec.ts +0 -391
  269. package/src/report/reportViewModel.spec.ts +0 -393
@@ -1,161 +1,4 @@
1
- // Saved reports — desk parity via the `Report` doctype (report_type
2
- // "Report Builder"): the same records desk's "Select Report" dropdown
3
- // lists. Save/Save As ride the whitelisted frappe.desk.reportview
4
- // save_report / delete_report methods (identical payload shape, so desk
5
- // opens reports saved here and vice versa). Fixture mode (stories / no
6
- // bench) keeps an in-memory list so the control stays demonstrable.
7
- import { useFrappeConfig } from "@multiplatform.one/frappe-ui";
8
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
9
- import { useFrappeApp } from "../shared/useFrappeApp";
10
- import {
11
- toDeskReportSettings,
12
- fromDeskReportSettings,
13
- type ReportViewConfig,
14
- } from "./reportViewModel";
15
-
16
- export interface SavedReportSummary {
17
- /** Report document name (usually equals the report title). */
18
- name: string;
19
- reportName: string;
20
- json?: string;
21
- }
22
-
23
- export interface UseSavedReportsResult {
24
- reports: SavedReportSummary[];
25
- loading: boolean;
26
- /** Load a saved report's view config (fields/filters/sort/…). */
27
- load: (name: string) => Promise<ReportViewConfig | undefined>;
28
- /** Create or update a Report doc; returns the server-side name. */
29
- save: (name: string, config: ReportViewConfig) => Promise<string>;
30
- remove: (name: string) => Promise<void>;
31
- refresh: () => void;
32
- }
33
-
34
- type ReportListRow = { name: string; report_name?: string; json?: string };
35
-
36
- export function useSavedReports(doctype: string): UseSavedReportsResult {
37
- const app = useFrappeApp();
38
- const config = useFrappeConfig();
39
- const fixtureMode = Boolean(config?.fixtures);
40
- const [reports, setReports] = useState<SavedReportSummary[]>([]);
41
- const [loading, setLoading] = useState(false);
42
- const [generation, setGeneration] = useState(0);
43
- // Fixture-mode saves live here (stories/tests) — never reaches a bench.
44
- const memoryConfigs = useRef(new Map<string, ReportViewConfig>());
45
-
46
- useEffect(() => {
47
- memoryConfigs.current = new Map();
48
- setReports([]);
49
- }, [doctype]);
50
-
51
- useEffect(() => {
52
- if (!app || fixtureMode) return;
53
- let cancelled = false;
54
- setLoading(true);
55
- app
56
- .call()
57
- .get<{ message?: ReportListRow[] }>("frappe.client.get_list", {
58
- doctype: "Report",
59
- filters: JSON.stringify([
60
- ["ref_doctype", "=", doctype],
61
- ["report_type", "=", "Report Builder"],
62
- ["disabled", "=", 0],
63
- ]),
64
- fields: JSON.stringify(["name", "report_name", "json"]),
65
- limit_page_length: 0,
66
- order_by: "report_name asc",
67
- })
68
- .then((res) => {
69
- if (cancelled) return;
70
- const rows = res.payload?.message ?? [];
71
- setReports(
72
- rows.map((row) => ({
73
- name: row.name,
74
- reportName: row.report_name || row.name,
75
- json: row.json,
76
- })),
77
- );
78
- setLoading(false);
79
- })
80
- .catch(() => {
81
- // Report doctype may be unreadable for restricted roles — the
82
- // selector then simply offers no saved reports.
83
- if (cancelled) return;
84
- setReports([]);
85
- setLoading(false);
86
- });
87
- return () => {
88
- cancelled = true;
89
- };
90
- }, [app, fixtureMode, doctype, generation]);
91
-
92
- const load = useCallback(
93
- async (name: string): Promise<ReportViewConfig | undefined> => {
94
- if (fixtureMode) return memoryConfigs.current.get(name);
95
- const summary = reports.find((report) => report.name === name);
96
- let json = summary?.json;
97
- if (json === undefined && app) {
98
- const res = await app
99
- .call()
100
- .get<{ message?: { json?: string } }>("frappe.client.get_value", {
101
- doctype: "Report",
102
- filters: JSON.stringify({ name }),
103
- fieldname: JSON.stringify(["json"]),
104
- });
105
- json = res.payload?.message?.json;
106
- }
107
- if (!json) return undefined;
108
- try {
109
- return fromDeskReportSettings(doctype, JSON.parse(json));
110
- } catch {
111
- return undefined;
112
- }
113
- },
114
- [app, fixtureMode, reports, doctype],
115
- );
116
-
117
- const save = useCallback(
118
- async (name: string, viewConfig: ReportViewConfig): Promise<string> => {
119
- const trimmed = name.trim();
120
- if (!trimmed) throw new Error("Report name is required");
121
- if (fixtureMode || !app) {
122
- memoryConfigs.current.set(trimmed, viewConfig);
123
- setReports((previous) => [
124
- ...previous.filter((report) => report.name !== trimmed),
125
- { name: trimmed, reportName: trimmed },
126
- ]);
127
- return trimmed;
128
- }
129
- const res = await app
130
- .call()
131
- .post<{ message?: string }>("frappe.desk.reportview.save_report", {
132
- name: trimmed,
133
- doctype,
134
- report_settings: JSON.stringify(toDeskReportSettings(doctype, viewConfig)),
135
- });
136
- setGeneration((value) => value + 1);
137
- return res.payload?.message ?? trimmed;
138
- },
139
- [app, fixtureMode, doctype],
140
- );
141
-
142
- const remove = useCallback(
143
- async (name: string): Promise<void> => {
144
- if (fixtureMode || !app) {
145
- memoryConfigs.current.delete(name);
146
- setReports((previous) => previous.filter((report) => report.name !== name));
147
- return;
148
- }
149
- await app.call().post("frappe.desk.reportview.delete_report", { name });
150
- setGeneration((value) => value + 1);
151
- },
152
- [app, fixtureMode, doctype],
153
- );
154
-
155
- const refresh = useCallback(() => setGeneration((value) => value + 1), []);
156
-
157
- return useMemo(
158
- () => ({ reports, loading, load, save, remove, refresh }),
159
- [reports, loading, load, save, remove, refresh],
160
- );
161
- }
1
+ // Saved reports live in @multiplatform.one/frappe-ui (MPO-15, GL-28); this
2
+ // path keeps handing out the same names.
3
+ export { useSavedReports } from "@multiplatform.one/frappe-ui";
4
+ export type { SavedReportSummary, UseSavedReportsResult } from "@multiplatform.one/frappe-ui";
@@ -1,47 +1,6 @@
1
- // Data hook for frappe Query / Script Reports (the desk
2
- // /app/query-report/<name> surface): loads the Report doc (title,
3
- // ref_doctype, doc-level filters) plus the desk-side script module
4
- // (frappe.desk.query_report.get_script) and runs
5
- // frappe.desk.query_report.run. Wire-shape normalization (and the probed
6
- // response documentation) lives in reportShapes.ts; this file owns
7
- // fetch/state.
8
- //
9
- // GET /api/method/frappe.client.get {doctype:"Report", name}
10
- // → {message: {report_name, ref_doctype, report_type, disabled,
11
- // prepared_report, add_total_row, filters: [...], ...}}
12
- // GET /api/method/frappe.desk.query_report.get_script {report_name}
13
- // → {message: {script, html_format, execution_time, filters}}
14
- //
15
- // Filter meta sources (desk precedence, query_report.js setup): the script
16
- // module's declarative `filters` win when the script declares any; the
17
- // Report doc `filters` child table applies only when it doesn't. Script
18
- // filters are extracted from the raw JS text without eval
19
- // (reportShapes.extractScriptFilters) — entries beyond the declarative
20
- // subset (get_query functions, dynamic defaults, desk-only fieldtypes)
21
- // surface as `scriptFilterGap` so the screen can render an honest note.
22
- // `ignore_prepared_report` forces a synchronous run for prepared reports
23
- // (desk's background Prepared Report queue is out of scope here).
24
- import type { FrappeApp } from "@multiplatform.one/frappe";
25
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
26
- import { useFrappeApp } from "../shared/useFrappeApp";
27
- import {
28
- type QueryReportColumn,
29
- type QueryReportFilterMeta,
30
- type QueryReportFilters,
31
- type QueryReportFixture,
32
- type QueryReportMeta,
33
- type QueryReportRow,
34
- type QueryReportRunMessage,
35
- buildRunFilters,
36
- defaultFilterValues,
37
- extractScriptFilters,
38
- humanizeFrappeError,
39
- isEmptyFilterValue,
40
- normalizeFilterMeta,
41
- normalizeQueryReportColumns,
42
- normalizeQueryReportRows,
43
- } from "./reportShapes";
44
-
1
+ // The query-report hook lives in @multiplatform.one/frappe-ui (MPO-15,
2
+ // GL-28), on the same transport as FrappeReportView; this path keeps
3
+ // handing out the same names, the model's included.
45
4
  export {
46
5
  buildRunFilters,
47
6
  compactQueryReportFilters,
@@ -51,8 +10,9 @@ export {
51
10
  normalizeQueryReportColumns,
52
11
  normalizeQueryReportRows,
53
12
  scrubFieldname,
54
- stripHtml,
55
- } from "./reportShapes";
13
+ stripReportHtml as stripHtml,
14
+ useQueryReport,
15
+ } from "@multiplatform.one/frappe-ui";
56
16
  export type {
57
17
  QueryReportColumn,
58
18
  QueryReportFilterMeta,
@@ -61,304 +21,6 @@ export type {
61
21
  QueryReportMeta,
62
22
  QueryReportRow,
63
23
  ScriptFilterExtraction,
64
- } from "./reportShapes";
65
-
66
- const RUN_METHOD = "frappe.desk.query_report.run";
67
- const SCRIPT_METHOD = "frappe.desk.query_report.get_script";
68
-
69
- export interface UseQueryReportOptions {
70
- fixture?: QueryReportFixture;
71
- /** Filter values applied before the first run (report swaps re-apply them). */
72
- initialFilters?: QueryReportFilters;
73
- }
74
-
75
- export interface UseQueryReportResult {
76
- meta?: QueryReportMeta;
77
- columns: QueryReportColumn[];
78
- rows: QueryReportRow[];
79
- /** True until the first meta+script+run cycle settles. */
80
- loading: boolean;
81
- /** True while a (re)run is in flight. */
82
- running: boolean;
83
- error?: Error;
84
- /** The report's informational `message` payload (may embed HTML). */
85
- serverMessage?: string;
86
- /** Server-reported run time in seconds. */
87
- executionTime?: number;
88
- /** Merged filter meta: script-declared filters, else doc-level rows. */
89
- filterMeta: QueryReportFilterMeta[];
90
- /**
91
- * True when the desk script declares filters this surface could not
92
- * derive (non-declarative entries) — the honest "defined in code" note.
93
- */
94
- scriptFilterGap: boolean;
95
- /** Mandatory filters currently unfilled — the run is skipped while non-empty. */
96
- missingRequired: QueryReportFilterMeta[];
97
- filters: QueryReportFilters;
98
- setFilters: (next: QueryReportFilters) => void;
99
- reload: () => void;
100
- }
101
-
102
- async function fetchReportMeta(app: FrappeApp, reportName: string): Promise<QueryReportMeta> {
103
- const res = await app.call().get<{ message?: QueryReportMeta }>("frappe.client.get", {
104
- doctype: "Report",
105
- name: reportName,
106
- });
107
- const doc = res.payload?.message;
108
- if (!doc) throw new Error(`Report "${reportName}" not found`);
109
- return doc;
110
- }
111
-
112
- async function fetchReportScript(app: FrappeApp, reportName: string): Promise<string | undefined> {
113
- const res = await app.call().get<{ message?: { script?: string | null } }>(SCRIPT_METHOD, {
114
- report_name: reportName,
115
- });
116
- const script = res.payload?.message?.script;
117
- return typeof script === "string" ? script : undefined;
118
- }
119
-
120
- export function useQueryReport(
121
- reportName: string,
122
- options: UseQueryReportOptions = {},
123
- ): UseQueryReportResult {
124
- const { fixture } = options;
125
- const app = useFrappeApp();
126
-
127
- // Latest initialFilters without re-running effects on identity churn.
128
- const initialFiltersRef = useRef(options.initialFilters);
129
- initialFiltersRef.current = options.initialFilters;
130
-
131
- const [meta, setMeta] = useState<QueryReportMeta | undefined>(undefined);
132
- const [script, setScript] = useState<string | undefined>(undefined);
133
- const [sourcesSettled, setSourcesSettled] = useState(false);
134
- const [filters, setFiltersState] = useState<QueryReportFilters>(() => ({
135
- ...initialFiltersRef.current,
136
- }));
137
- const [columns, setColumns] = useState<QueryReportColumn[]>([]);
138
- const [rows, setRows] = useState<QueryReportRow[]>([]);
139
- const [loading, setLoading] = useState(true);
140
- const [running, setRunning] = useState(false);
141
- const [error, setError] = useState<Error | undefined>(undefined);
142
- const [serverMessage, setServerMessage] = useState<string | undefined>(undefined);
143
- const [executionTime, setExecutionTime] = useState<number | undefined>(undefined);
144
- const [generation, setGeneration] = useState(0);
145
- const seededFor = useRef<string | undefined>(undefined);
146
-
147
- // The route component is reused across query-report navigations — a
148
- // report swap resets filters and shows the loading state again.
149
- const lastReport = useRef(reportName);
150
- useEffect(() => {
151
- if (lastReport.current === reportName) return;
152
- lastReport.current = reportName;
153
- setFiltersState({ ...initialFiltersRef.current });
154
- setMeta(undefined);
155
- setScript(undefined);
156
- setSourcesSettled(false);
157
- setColumns([]);
158
- setRows([]);
159
- setError(undefined);
160
- setServerMessage(undefined);
161
- setExecutionTime(undefined);
162
- setLoading(true);
163
- }, [reportName]);
164
-
165
- // Report doc meta (title, ref_doctype, doc filters) + the desk script
166
- // module, fetched together. Both are non-fatal niceties — the run below
167
- // reports the real errors — but the run waits for them so declared
168
- // defaults are seeded into the FIRST run (desk parity).
169
- useEffect(() => {
170
- if (fixture) return;
171
- if (!app) {
172
- setSourcesSettled(true);
173
- return;
174
- }
175
- let cancelled = false;
176
- setSourcesSettled(false);
177
- Promise.allSettled([fetchReportMeta(app, reportName), fetchReportScript(app, reportName)]).then(
178
- ([metaResult, scriptResult]) => {
179
- if (cancelled) return;
180
- const doc = metaResult.status === "fulfilled" ? metaResult.value : undefined;
181
- const scriptText = scriptResult.status === "fulfilled" ? scriptResult.value : undefined;
182
- if (doc) setMeta(doc);
183
- if (scriptText) setScript(scriptText);
184
- // Seed filter defaults once per report; user edits (and deep-link
185
- // initialFilters, already in state) win over defaults.
186
- if (seededFor.current !== reportName) {
187
- seededFor.current = reportName;
188
- const extraction = extractScriptFilters(scriptText);
189
- const merged = extraction.declared
190
- ? extraction.filters
191
- : normalizeFilterMeta(doc?.filters);
192
- const defaults = defaultFilterValues(merged);
193
- if (Object.keys(defaults).length > 0)
194
- setFiltersState((previous) => ({ ...defaults, ...previous }));
195
- }
196
- setSourcesSettled(true);
197
- },
198
- );
199
- return () => {
200
- cancelled = true;
201
- };
202
- }, [app, fixture, reportName]);
203
-
204
- const scriptExtraction = useMemo(
205
- () => extractScriptFilters(fixture ? fixture.script : script),
206
- [fixture, script],
207
- );
208
- const docFilterMeta = useMemo(
209
- () => normalizeFilterMeta(fixture ? fixture.meta?.filters : meta?.filters),
210
- [fixture, meta],
211
- );
212
- // Desk precedence (query_report.js): the script module's filters apply
213
- // when the script declares the key at all; doc rows only otherwise.
214
- const filterMeta = useMemo(
215
- () => (scriptExtraction.declared ? scriptExtraction.filters : docFilterMeta),
216
- [scriptExtraction, docFilterMeta],
217
- );
218
- const scriptFilterGap = scriptExtraction.declaredCount > scriptExtraction.filters.length;
219
-
220
- // Fixture branch seeds defaults too (no fetch effect runs for fixtures).
221
- useEffect(() => {
222
- if (!fixture) return;
223
- if (seededFor.current === reportName) return;
224
- seededFor.current = reportName;
225
- const defaults = defaultFilterValues(filterMeta);
226
- if (Object.keys(defaults).length > 0)
227
- setFiltersState((previous) => ({ ...defaults, ...previous }));
228
- }, [fixture, reportName, filterMeta]);
229
-
230
- // Desk parity: defaults SEED the controls (state) once; after that the
231
- // gate reads the live values only — clearing a defaulted mandatory filter
232
- // re-gates instead of firing a doomed run (desk's get_filter_values reads
233
- // control values, not declarations).
234
- const missingRequired = useMemo(
235
- () =>
236
- filterMeta.filter(
237
- (filter) => filter.mandatory === 1 && isEmptyFilterValue(filters[filter.fieldname]),
238
- ),
239
- [filterMeta, filters],
240
- );
241
- const hasMissingRequired = missingRequired.length > 0;
242
- const metaDisabled = (fixture ? fixture.meta?.disabled : meta?.disabled) === 1;
243
-
244
- useEffect(() => {
245
- if (fixture) return;
246
- if (!sourcesSettled) return;
247
- if (metaDisabled) {
248
- setLoading(false);
249
- return;
250
- }
251
- if (hasMissingRequired) {
252
- setColumns([]);
253
- setRows([]);
254
- setError(undefined);
255
- setLoading(false);
256
- return;
257
- }
258
- if (!app) {
259
- setError(new Error("No Frappe connection configured"));
260
- setLoading(false);
261
- return;
262
- }
263
- let cancelled = false;
264
- setRunning(true);
265
- app
266
- .call()
267
- .post<{ message?: QueryReportRunMessage }>(RUN_METHOD, {
268
- report_name: reportName,
269
- filters: buildRunFilters(filterMeta, filters),
270
- ignore_prepared_report: 1,
271
- })
272
- .then((res) => {
273
- if (cancelled) return;
274
- const message = res.payload?.message;
275
- const nextColumns = normalizeQueryReportColumns(message?.columns);
276
- setColumns(nextColumns);
277
- setRows(normalizeQueryReportRows(message?.result, nextColumns));
278
- setServerMessage(typeof message?.message === "string" ? message.message : undefined);
279
- setExecutionTime(
280
- typeof message?.execution_time === "number" ? message.execution_time : undefined,
281
- );
282
- setError(undefined);
283
- })
284
- .catch((err) => {
285
- if (cancelled) return;
286
- setColumns([]);
287
- setRows([]);
288
- setError(humanizeFrappeError(err));
289
- })
290
- .finally(() => {
291
- if (cancelled) return;
292
- setRunning(false);
293
- setLoading(false);
294
- });
295
- return () => {
296
- cancelled = true;
297
- };
298
- }, [
299
- app,
300
- fixture,
301
- reportName,
302
- filters,
303
- filterMeta,
304
- generation,
305
- sourcesSettled,
306
- metaDisabled,
307
- hasMissingRequired,
308
- ]);
309
-
310
- const setFilters = useCallback((next: QueryReportFilters) => {
311
- setFiltersState(next);
312
- }, []);
313
-
314
- const reload = useCallback(() => {
315
- setGeneration((previous) => previous + 1);
316
- }, []);
317
-
318
- // Fixture branch: static data, no RPCs (stories/tests).
319
- const fixtureResult = useMemo(() => {
320
- if (!fixture) return undefined;
321
- const fixtureColumns = normalizeQueryReportColumns(fixture.columns);
322
- return {
323
- columns: fixtureColumns,
324
- rows: normalizeQueryReportRows(fixture.rows, fixtureColumns),
325
- meta: fixture.meta ? ({ name: reportName, ...fixture.meta } as QueryReportMeta) : undefined,
326
- };
327
- }, [fixture, reportName]);
328
-
329
- if (fixtureResult) {
330
- return {
331
- meta: fixtureResult.meta,
332
- columns: fixtureResult.columns,
333
- rows: hasMissingRequired ? [] : fixtureResult.rows,
334
- loading: false,
335
- running: false,
336
- error: undefined,
337
- serverMessage: fixture?.message,
338
- executionTime: undefined,
339
- filterMeta,
340
- scriptFilterGap,
341
- missingRequired,
342
- filters,
343
- setFilters,
344
- reload,
345
- };
346
- }
347
-
348
- return {
349
- meta,
350
- columns,
351
- rows,
352
- loading,
353
- running,
354
- error,
355
- serverMessage,
356
- executionTime,
357
- filterMeta,
358
- scriptFilterGap,
359
- missingRequired,
360
- filters,
361
- setFilters,
362
- reload,
363
- };
364
- }
24
+ UseQueryReportOptions,
25
+ UseQueryReportResult,
26
+ } from "@multiplatform.one/frappe-ui";
@@ -1,11 +1,3 @@
1
- import { sizeRecipeEscape } from "@multiplatform.one/theme";
2
-
3
- export const dialogBodyMinWidth = sizeRecipeEscape(
4
- 280,
5
- "dialog body floor: a reading width for the form inside a dialog or popover",
6
- );
7
-
8
- export const overlayListMaxHeight = sizeRecipeEscape(
9
- 360,
10
- "overlay list cap: a menu or popover list scrolls past this height instead of growing",
11
- );
1
+ // The overlay measures live in @multiplatform.one/frappe-ui beside the report
2
+ // toolbar that moved there (MPO-15, GL-28); this path keeps handing them out.
3
+ export { dialogBodyMinWidth, overlayListMaxHeight } from "@multiplatform.one/frappe-ui";
@@ -1,7 +1,8 @@
1
1
  // The compatibility report view (MPO-414), still exported beside
2
- // DoctypeReportView: it counts and totals the filtered result, opens a record
3
- // from the footer or the grid, applies and clears the ?f= base filters, and
4
- // ANDs the desk scope into its queries (GL-5).
2
+ // DoctypeReportView. Since MPO-15 (GL-28) it is that view under its old name,
3
+ // FrappeReportBuilder under the desk's navigation: it totals the filtered
4
+ // result, opens a record from the grid, applies and clears the ?f= base
5
+ // filters, and ANDs the desk scope into its queries (GL-5).
5
6
  import React from "react";
6
7
  import { InMemoryFixtureProvider } from "@multiplatform.one/frappe";
7
8
  import { renderWithProviders } from "@multiplatform.one/test-utils";
@@ -66,25 +67,29 @@ function renderReport(filtersParam?: string, scope?: FrappeFilters): MemoryNavig
66
67
  return navigation;
67
68
  }
68
69
 
69
- async function counted(count: number) {
70
- expect(await screen.findByText(`${count} record(s)`, {}, { timeout: 5000 })).toBeTruthy();
70
+ function rowNames(): string[] {
71
+ return Array.from(
72
+ document.querySelectorAll('[data-report-row="true"]'),
73
+ (row) => row.getAttribute("aria-label") ?? "",
74
+ );
75
+ }
76
+
77
+ async function rowsAre(names: string[]) {
78
+ await waitFor(() => expect(rowNames().sort()).toEqual([...names].sort()), { timeout: 5000 });
71
79
  }
72
80
 
73
81
  describe("<ReportView />", () => {
74
- it("counts and totals the result", async () => {
82
+ it("is the desk report view, totals row included", async () => {
75
83
  renderReport();
76
- await counted(3);
77
- const strips = screen
78
- .getAllByText("Totals")
79
- .map((label) => label.parentElement?.textContent ?? "");
80
- expect(strips.some((text) => text.includes("Amount: 123"))).toBe(true);
84
+ await rowsAre(["QLO-0001", "QLO-0002", "QLO-0003"]);
85
+ const totals = screen.getByText("Totals").closest("[role=row]") ?? screen.getByText("Totals");
86
+ expect(totals.parentElement?.textContent ?? totals.textContent).toContain("123");
81
87
  });
82
88
 
83
- it("opens a record from the footer's Open menu", async () => {
89
+ it("opens a record from the grid", async () => {
84
90
  const navigation = renderReport();
85
- await counted(3);
86
- fireEvent.click(screen.getByRole("button", { name: "Open" }));
87
- fireEvent.click(await screen.findByText("Globex", { selector: "[role=menuitem] *" }));
91
+ await rowsAre(["QLO-0001", "QLO-0002", "QLO-0003"]);
92
+ fireEvent.click(screen.getByText("Globex"));
88
93
  await waitFor(() =>
89
94
  expect(navigation.getLocation().path).toBe("/backoffice/qalegacyorder/QLO-0002"),
90
95
  );
@@ -92,17 +97,17 @@ describe("<ReportView />", () => {
92
97
 
93
98
  it("applies the ?f= base filters and drops them from the address on clear", async () => {
94
99
  const navigation = renderReport(encodeURIComponent(JSON.stringify([["status", "=", "Draft"]])));
95
- await counted(2);
100
+ await rowsAre(["QLO-0002", "QLO-0003"]);
96
101
  fireEvent.click(screen.getByRole("button", { name: "Clear filters" }));
97
- await counted(3);
102
+ await rowsAre(["QLO-0001", "QLO-0002", "QLO-0003"]);
98
103
  expect(navigation.getLocation().path).toBe("/backoffice/qalegacyorder/view/report");
99
104
  });
100
105
 
101
- it("ANDs the desk scope into the count and the grid", async () => {
106
+ it("ANDs the desk scope into the grid, with nothing to clear", async () => {
102
107
  renderReport(undefined, [["status", "=", "Paid"]]);
103
- await counted(1);
108
+ await rowsAre(["QLO-0001"]);
104
109
  expect(screen.queryByRole("button", { name: "Clear filters" })).toBeNull();
105
- await waitFor(() => expect(screen.queryByText("Globex")).toBeNull());
110
+ expect(screen.queryByText("Globex")).toBeNull();
106
111
  expect(screen.getAllByText("ACME Corp").length).toBeGreaterThan(0);
107
112
  });
108
113
  });