@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.
- package/README.md +1 -1
- package/dist/cjs/desk/DeskSurface.native.js.map +1 -1
- package/dist/cjs/form/DocFormScreen.cjs +167 -224
- package/dist/cjs/form/DocFormScreen.native.js +182 -259
- package/dist/cjs/form/DocFormScreen.native.js.map +1 -1
- package/dist/cjs/i18n/en.cjs +1 -0
- package/dist/cjs/i18n/en.native.js +1 -0
- package/dist/cjs/i18n/en.native.js.map +1 -1
- package/dist/cjs/list/ListBulkActionsBar.cjs +30 -18
- package/dist/cjs/list/ListBulkActionsBar.native.js +43 -31
- package/dist/cjs/list/ListBulkActionsBar.native.js.map +1 -1
- package/dist/cjs/list/sidebar-section.cjs +15 -7
- package/dist/cjs/list/sidebar-section.native.js +16 -7
- package/dist/cjs/list/sidebar-section.native.js.map +1 -1
- package/dist/cjs/report/DoctypeReportView.cjs +20 -374
- package/dist/cjs/report/DoctypeReportView.native.js +23 -455
- package/dist/cjs/report/DoctypeReportView.native.js.map +1 -1
- package/dist/cjs/report/QueryReportScreen.cjs +33 -23
- package/dist/cjs/report/QueryReportScreen.native.js +33 -23
- package/dist/cjs/report/QueryReportScreen.native.js.map +1 -1
- package/dist/cjs/report/queryReportColumns.cjs +3 -105
- package/dist/cjs/report/queryReportColumns.native.js +2 -129
- package/dist/cjs/report/queryReportColumns.native.js.map +1 -1
- package/dist/cjs/report/queryReportFilterBar.cjs +3 -4
- package/dist/cjs/report/queryReportFilterBar.native.js +3 -4
- package/dist/cjs/report/queryReportFilterBar.native.js.map +1 -1
- package/dist/cjs/report/queryReportUrlState.cjs +2 -2
- package/dist/cjs/report/queryReportUrlState.native.js +2 -2
- package/dist/cjs/report/queryReportUrlState.native.js.map +1 -1
- package/dist/cjs/report/report-grid.cjs +2 -426
- package/dist/cjs/report/report-grid.native.js +1 -479
- package/dist/cjs/report/report-grid.native.js.map +1 -1
- package/dist/cjs/report/report-toolbar.cjs +2 -228
- package/dist/cjs/report/report-toolbar.native.js +1 -274
- package/dist/cjs/report/report-toolbar.native.js.map +1 -1
- package/dist/cjs/report/reportShapes.cjs +12 -383
- package/dist/cjs/report/reportShapes.native.js +12 -501
- package/dist/cjs/report/reportShapes.native.js.map +1 -1
- package/dist/cjs/report/reportViewModel.cjs +20 -360
- package/dist/cjs/report/reportViewModel.native.js +19 -627
- package/dist/cjs/report/reportViewModel.native.js.map +1 -1
- package/dist/cjs/report/savedReports.cjs +2 -106
- package/dist/cjs/report/savedReports.native.js +1 -132
- package/dist/cjs/report/savedReports.native.js.map +1 -1
- package/dist/cjs/report/useQueryReport.cjs +11 -219
- package/dist/cjs/report/useQueryReport.native.js +11 -244
- package/dist/cjs/report/useQueryReport.native.js.map +1 -1
- package/dist/cjs/shared/measures.cjs +3 -5
- package/dist/cjs/shared/measures.native.js +3 -5
- package/dist/cjs/shared/measures.native.js.map +1 -1
- package/dist/cjs/views/ReportView.cjs +3 -256
- package/dist/cjs/views/ReportView.native.js +3 -382
- package/dist/cjs/views/ReportView.native.js.map +1 -1
- package/dist/cjs/views/report-controls.cjs +6 -6
- package/dist/cjs/views/report-controls.native.js +6 -6
- package/dist/cjs/views/report-controls.native.js.map +1 -1
- package/dist/esm/desk/DeskSurface.mjs.map +1 -1
- package/dist/esm/desk/DeskSurface.native.js.map +1 -1
- package/dist/esm/form/DocFormScreen.mjs +169 -226
- package/dist/esm/form/DocFormScreen.mjs.map +1 -1
- package/dist/esm/form/DocFormScreen.native.js +184 -261
- package/dist/esm/form/DocFormScreen.native.js.map +1 -1
- package/dist/esm/i18n/en.mjs +1 -0
- package/dist/esm/i18n/en.mjs.map +1 -1
- package/dist/esm/i18n/en.native.js +1 -0
- package/dist/esm/i18n/en.native.js.map +1 -1
- package/dist/esm/list/ListBulkActionsBar.mjs +31 -19
- package/dist/esm/list/ListBulkActionsBar.mjs.map +1 -1
- package/dist/esm/list/ListBulkActionsBar.native.js +37 -25
- package/dist/esm/list/ListBulkActionsBar.native.js.map +1 -1
- package/dist/esm/list/sidebar-section.mjs +16 -8
- package/dist/esm/list/sidebar-section.mjs.map +1 -1
- package/dist/esm/list/sidebar-section.native.js +17 -8
- package/dist/esm/list/sidebar-section.native.js.map +1 -1
- package/dist/esm/report/DoctypeReportView.mjs +21 -375
- package/dist/esm/report/DoctypeReportView.mjs.map +1 -1
- package/dist/esm/report/DoctypeReportView.native.js +24 -456
- package/dist/esm/report/DoctypeReportView.native.js.map +1 -1
- package/dist/esm/report/QueryReportScreen.mjs +26 -16
- package/dist/esm/report/QueryReportScreen.mjs.map +1 -1
- package/dist/esm/report/QueryReportScreen.native.js +26 -16
- package/dist/esm/report/QueryReportScreen.native.js.map +1 -1
- package/dist/esm/report/queryReportColumns.mjs +1 -103
- package/dist/esm/report/queryReportColumns.mjs.map +1 -1
- package/dist/esm/report/queryReportColumns.native.js +1 -117
- package/dist/esm/report/queryReportColumns.native.js.map +1 -1
- package/dist/esm/report/queryReportFilterBar.mjs +1 -2
- package/dist/esm/report/queryReportFilterBar.mjs.map +1 -1
- package/dist/esm/report/queryReportFilterBar.native.js +1 -2
- package/dist/esm/report/queryReportFilterBar.native.js.map +1 -1
- package/dist/esm/report/queryReportUrlState.mjs +1 -1
- package/dist/esm/report/queryReportUrlState.native.js +1 -1
- package/dist/esm/report/report-grid.mjs +1 -425
- package/dist/esm/report/report-grid.mjs.map +1 -1
- package/dist/esm/report/report-grid.native.js +1 -468
- package/dist/esm/report/report-grid.native.js.map +1 -1
- package/dist/esm/report/report-toolbar.mjs +2 -228
- package/dist/esm/report/report-toolbar.mjs.map +1 -1
- package/dist/esm/report/report-toolbar.native.js +2 -264
- package/dist/esm/report/report-toolbar.native.js.map +1 -1
- package/dist/esm/report/reportShapes.mjs +2 -373
- package/dist/esm/report/reportShapes.mjs.map +1 -1
- package/dist/esm/report/reportShapes.native.js +2 -491
- package/dist/esm/report/reportShapes.native.js.map +1 -1
- package/dist/esm/report/reportViewModel.mjs +2 -342
- package/dist/esm/report/reportViewModel.mjs.map +1 -1
- package/dist/esm/report/reportViewModel.native.js +2 -610
- package/dist/esm/report/reportViewModel.native.js.map +1 -1
- package/dist/esm/report/savedReports.mjs +1 -105
- package/dist/esm/report/savedReports.mjs.map +1 -1
- package/dist/esm/report/savedReports.native.js +1 -132
- package/dist/esm/report/savedReports.native.js.map +1 -1
- package/dist/esm/report/useQueryReport.mjs +2 -210
- package/dist/esm/report/useQueryReport.mjs.map +1 -1
- package/dist/esm/report/useQueryReport.native.js +2 -235
- package/dist/esm/report/useQueryReport.native.js.map +1 -1
- package/dist/esm/shared/measures.mjs +1 -3
- package/dist/esm/shared/measures.mjs.map +1 -1
- package/dist/esm/shared/measures.native.js +1 -3
- package/dist/esm/shared/measures.native.js.map +1 -1
- package/dist/esm/views/ReportView.mjs +3 -256
- package/dist/esm/views/ReportView.mjs.map +1 -1
- package/dist/esm/views/ReportView.native.js +3 -382
- package/dist/esm/views/ReportView.native.js.map +1 -1
- package/dist/esm/views/report-controls.mjs +3 -3
- package/dist/esm/views/report-controls.mjs.map +1 -1
- package/dist/esm/views/report-controls.native.js +3 -3
- package/dist/esm/views/report-controls.native.js.map +1 -1
- package/dist/jsx/desk/DeskSurface.mjs.map +1 -1
- package/dist/jsx/desk/DeskSurface.native.js.map +1 -1
- package/dist/jsx/form/DocFormScreen.mjs +169 -226
- package/dist/jsx/form/DocFormScreen.mjs.map +1 -1
- package/dist/jsx/form/DocFormScreen.native.js +182 -259
- package/dist/jsx/form/DocFormScreen.native.js.map +1 -1
- package/dist/jsx/i18n/en.mjs +1 -0
- package/dist/jsx/i18n/en.mjs.map +1 -1
- package/dist/jsx/i18n/en.native.js +1 -0
- package/dist/jsx/i18n/en.native.js.map +1 -1
- package/dist/jsx/list/ListBulkActionsBar.mjs +31 -19
- package/dist/jsx/list/ListBulkActionsBar.mjs.map +1 -1
- package/dist/jsx/list/ListBulkActionsBar.native.js +43 -31
- package/dist/jsx/list/ListBulkActionsBar.native.js.map +1 -1
- package/dist/jsx/list/sidebar-section.mjs +16 -8
- package/dist/jsx/list/sidebar-section.mjs.map +1 -1
- package/dist/jsx/list/sidebar-section.native.js +16 -7
- package/dist/jsx/list/sidebar-section.native.js.map +1 -1
- package/dist/jsx/report/DoctypeReportView.mjs +21 -375
- package/dist/jsx/report/DoctypeReportView.mjs.map +1 -1
- package/dist/jsx/report/DoctypeReportView.native.js +23 -455
- package/dist/jsx/report/DoctypeReportView.native.js.map +1 -1
- package/dist/jsx/report/QueryReportScreen.mjs +26 -16
- package/dist/jsx/report/QueryReportScreen.mjs.map +1 -1
- package/dist/jsx/report/QueryReportScreen.native.js +33 -23
- package/dist/jsx/report/QueryReportScreen.native.js.map +1 -1
- package/dist/jsx/report/queryReportColumns.mjs +1 -103
- package/dist/jsx/report/queryReportColumns.mjs.map +1 -1
- package/dist/jsx/report/queryReportColumns.native.js +2 -129
- package/dist/jsx/report/queryReportColumns.native.js.map +1 -1
- package/dist/jsx/report/queryReportFilterBar.mjs +1 -2
- package/dist/jsx/report/queryReportFilterBar.mjs.map +1 -1
- package/dist/jsx/report/queryReportFilterBar.native.js +3 -4
- package/dist/jsx/report/queryReportFilterBar.native.js.map +1 -1
- package/dist/jsx/report/queryReportUrlState.mjs +1 -1
- package/dist/jsx/report/queryReportUrlState.native.js +2 -2
- package/dist/jsx/report/queryReportUrlState.native.js.map +1 -1
- package/dist/jsx/report/report-grid.mjs +1 -425
- package/dist/jsx/report/report-grid.mjs.map +1 -1
- package/dist/jsx/report/report-grid.native.js +1 -479
- package/dist/jsx/report/report-grid.native.js.map +1 -1
- package/dist/jsx/report/report-toolbar.mjs +2 -228
- package/dist/jsx/report/report-toolbar.mjs.map +1 -1
- package/dist/jsx/report/report-toolbar.native.js +1 -274
- package/dist/jsx/report/report-toolbar.native.js.map +1 -1
- package/dist/jsx/report/reportShapes.mjs +2 -373
- package/dist/jsx/report/reportShapes.mjs.map +1 -1
- package/dist/jsx/report/reportShapes.native.js +12 -501
- package/dist/jsx/report/reportShapes.native.js.map +1 -1
- package/dist/jsx/report/reportViewModel.mjs +2 -342
- package/dist/jsx/report/reportViewModel.mjs.map +1 -1
- package/dist/jsx/report/reportViewModel.native.js +19 -627
- package/dist/jsx/report/reportViewModel.native.js.map +1 -1
- package/dist/jsx/report/savedReports.mjs +1 -105
- package/dist/jsx/report/savedReports.mjs.map +1 -1
- package/dist/jsx/report/savedReports.native.js +1 -132
- package/dist/jsx/report/savedReports.native.js.map +1 -1
- package/dist/jsx/report/useQueryReport.mjs +2 -210
- package/dist/jsx/report/useQueryReport.mjs.map +1 -1
- package/dist/jsx/report/useQueryReport.native.js +11 -244
- package/dist/jsx/report/useQueryReport.native.js.map +1 -1
- package/dist/jsx/shared/measures.mjs +1 -3
- package/dist/jsx/shared/measures.mjs.map +1 -1
- package/dist/jsx/shared/measures.native.js +3 -5
- package/dist/jsx/shared/measures.native.js.map +1 -1
- package/dist/jsx/views/ReportView.mjs +3 -256
- package/dist/jsx/views/ReportView.mjs.map +1 -1
- package/dist/jsx/views/ReportView.native.js +3 -382
- package/dist/jsx/views/ReportView.native.js.map +1 -1
- package/dist/jsx/views/report-controls.mjs +3 -3
- package/dist/jsx/views/report-controls.mjs.map +1 -1
- package/dist/jsx/views/report-controls.native.js +6 -6
- package/dist/jsx/views/report-controls.native.js.map +1 -1
- package/package.json +12 -12
- package/src/backofficeScreens.designLaw.spec.ts +0 -5
- package/src/desk/DeskSurface.tsx +4 -7
- package/src/form/DocFormScreen.chrome.spec.ts +77 -0
- package/src/form/DocFormScreen.guest.screen.spec.tsx +175 -0
- package/src/form/DocFormScreen.rights.spec.tsx +7 -3
- package/src/form/DocFormScreen.tsx +214 -315
- package/src/i18n/en.ts +1 -0
- package/src/list/ListBulkActionsBar.rights.spec.tsx +134 -0
- package/src/list/ListBulkActionsBar.screen.spec.tsx +7 -1
- package/src/list/ListBulkActionsBar.tsx +76 -57
- package/src/list/ListFilterBar.screen.spec.tsx +14 -13
- package/src/list/sidebar-section.tsx +11 -3
- package/src/report/DoctypeReportView.tsx +28 -550
- package/src/report/QueryReportScreen.spec.tsx +75 -1
- package/src/report/QueryReportScreen.tsx +38 -22
- package/src/report/queryReportColumns.tsx +4 -123
- package/src/report/queryReportFilterBar.tsx +3 -5
- package/src/report/queryReportUrlState.ts +1 -1
- package/src/report/report-grid.tsx +4 -529
- package/src/report/report-toolbar.tsx +4 -308
- package/src/report/reportShapes.ts +25 -613
- package/src/report/reportShim.spec.ts +162 -0
- package/src/report/reportViewModel.ts +32 -605
- package/src/report/savedReports.ts +4 -161
- package/src/report/useQueryReport.ts +9 -347
- package/src/shared/measures.ts +3 -11
- package/src/views/ReportView.screen.spec.tsx +25 -20
- package/src/views/ReportView.tsx +7 -439
- package/src/views/report-controls.tsx +12 -20
- package/types/desk/DeskSurface.d.ts.map +1 -1
- package/types/form/DocFormScreen.d.ts +2 -2
- package/types/form/DocFormScreen.d.ts.map +1 -1
- package/types/i18n/en.d.ts +1 -0
- package/types/i18n/en.d.ts.map +1 -1
- package/types/list/ListBulkActionsBar.d.ts.map +1 -1
- package/types/list/sidebar-section.d.ts.map +1 -1
- package/types/report/DoctypeReportView.d.ts.map +1 -1
- package/types/report/QueryReportScreen.d.ts +1 -1
- package/types/report/QueryReportScreen.d.ts.map +1 -1
- package/types/report/queryReportColumns.d.ts +2 -11
- package/types/report/queryReportColumns.d.ts.map +1 -1
- package/types/report/queryReportFilterBar.d.ts +1 -1
- package/types/report/queryReportFilterBar.d.ts.map +1 -1
- package/types/report/queryReportUrlState.d.ts +1 -1
- package/types/report/queryReportUrlState.d.ts.map +1 -1
- package/types/report/report-grid.d.ts +2 -19
- package/types/report/report-grid.d.ts.map +1 -1
- package/types/report/report-toolbar.d.ts +2 -29
- package/types/report/report-toolbar.d.ts.map +1 -1
- package/types/report/reportShapes.d.ts +2 -115
- package/types/report/reportShapes.d.ts.map +1 -1
- package/types/report/reportViewModel.d.ts +2 -135
- package/types/report/reportViewModel.d.ts.map +1 -1
- package/types/report/savedReports.d.ts +2 -18
- package/types/report/savedReports.d.ts.map +1 -1
- package/types/report/useQueryReport.d.ts +2 -35
- package/types/report/useQueryReport.d.ts.map +1 -1
- package/types/shared/measures.d.ts +1 -2
- package/types/shared/measures.d.ts.map +1 -1
- package/types/views/ReportView.d.ts +3 -22
- package/types/views/ReportView.d.ts.map +1 -1
- package/types/views/report-controls.d.ts +2 -5
- package/types/views/report-controls.d.ts.map +1 -1
- package/src/report/report-grid.spec.tsx +0 -175
- package/src/report/report-toolbar.spec.tsx +0 -90
- package/src/report/reportShapes.spec.ts +0 -391
- package/src/report/reportViewModel.spec.ts +0 -393
package/src/i18n/en.ts
CHANGED
|
@@ -272,6 +272,7 @@ export const en = {
|
|
|
272
272
|
executionTime: "ran in {{seconds}}s",
|
|
273
273
|
filtersRequiredDescription: "Set the required filter(s) to run this report: {{filters}}",
|
|
274
274
|
filtersRequiredTitle: "Filters required",
|
|
275
|
+
notPermitted: 'You can\'t run "{{report}}"',
|
|
275
276
|
refresh: "Refresh",
|
|
276
277
|
retry: "Retry",
|
|
277
278
|
rowCount: "{{count}} row(s)",
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// The bulk actions bar offers Bulk Edit / Submit / Cancel / Delete only as
|
|
2
|
+
// far as the session's roles grant them on the doctype (MPO-428), measured
|
|
3
|
+
// against a live-shaped bench: the session RPCs for the user and roles, with
|
|
4
|
+
// meta (and its DocPerm rows) passed in as a prop, so the bar never asks for
|
|
5
|
+
// meta itself.
|
|
6
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
7
|
+
import { act, screen, waitFor, within } from "@testing-library/react";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import { BackofficeProvider } from "../config/BackofficeProvider";
|
|
10
|
+
import { createMemoryNavigator } from "../navigation/navigator";
|
|
11
|
+
import type { DoctypeMeta } from "../shared/useDoctypeMeta";
|
|
12
|
+
import { ListBulkActionsBar } from "./ListBulkActionsBar";
|
|
13
|
+
|
|
14
|
+
const doctype = "QA Submittable";
|
|
15
|
+
|
|
16
|
+
const meta: DoctypeMeta = {
|
|
17
|
+
name: doctype,
|
|
18
|
+
is_submittable: 1,
|
|
19
|
+
fields: [{ fieldname: "subject", fieldtype: "Data", label: "Subject" }],
|
|
20
|
+
permissions: [
|
|
21
|
+
{
|
|
22
|
+
role: "Pokemon Editor",
|
|
23
|
+
read: 1,
|
|
24
|
+
write: 1,
|
|
25
|
+
create: 1,
|
|
26
|
+
delete: 1,
|
|
27
|
+
submit: 1,
|
|
28
|
+
cancel: 1,
|
|
29
|
+
},
|
|
30
|
+
{ role: "Pokemon Writer", read: 1, write: 1, create: 1 },
|
|
31
|
+
{ role: "Guest", read: 1 },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
let run = 0;
|
|
36
|
+
let roles: string[] = [];
|
|
37
|
+
let sessionAnswered = false;
|
|
38
|
+
let requested: string[] = [];
|
|
39
|
+
|
|
40
|
+
function json(body: unknown, status = 200) {
|
|
41
|
+
return new Response(JSON.stringify(body), {
|
|
42
|
+
status,
|
|
43
|
+
headers: { "content-type": "application/json" },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
run += 1;
|
|
49
|
+
sessionAnswered = false;
|
|
50
|
+
requested = [];
|
|
51
|
+
vi.stubGlobal("fetch", async (input: string | URL | Request) => {
|
|
52
|
+
const url = new URL(String(input instanceof Request ? input.url : input));
|
|
53
|
+
const method = url.pathname.replace(/^\/api\/method\//, "");
|
|
54
|
+
requested.push(method);
|
|
55
|
+
if (method === "frappe.auth.get_logged_user") return json({ message: "user@pokemon.test" });
|
|
56
|
+
if (method === "frappe.core.doctype.user.user.get_roles") {
|
|
57
|
+
sessionAnswered = true;
|
|
58
|
+
return json({ message: roles });
|
|
59
|
+
}
|
|
60
|
+
return json({ data: [], message: [] });
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
vi.restoreAllMocks();
|
|
66
|
+
vi.unstubAllGlobals();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function renderBar(barMeta: DoctypeMeta = meta) {
|
|
70
|
+
return renderWithProviders(
|
|
71
|
+
<BackofficeProvider
|
|
72
|
+
frappe={{ baseURL: `https://list-bulk-rights-${run}.spec.test` }}
|
|
73
|
+
navigation={createMemoryNavigator("/backoffice")}
|
|
74
|
+
>
|
|
75
|
+
<ListBulkActionsBar
|
|
76
|
+
doctype={barMeta.name}
|
|
77
|
+
meta={barMeta}
|
|
78
|
+
selectedNames={["SUB-001", "SUB-002"]}
|
|
79
|
+
onClearSelection={() => {}}
|
|
80
|
+
onActionComplete={() => {}}
|
|
81
|
+
/>
|
|
82
|
+
</BackofficeProvider>,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function settle() {
|
|
87
|
+
await waitFor(() => expect(sessionAnswered).toBe(true));
|
|
88
|
+
await act(async () => {
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function writeActionLabels(): string[] {
|
|
94
|
+
const root = within(document.body);
|
|
95
|
+
return ["Bulk Edit", "Submit", "Cancel", "Delete"].filter(
|
|
96
|
+
(label) => root.queryByRole("button", { name: label }) !== null,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
describe("ListBulkActionsBar rights", () => {
|
|
101
|
+
it("offers an editor Bulk Edit, Submit, Cancel and Delete", async () => {
|
|
102
|
+
roles = ["Pokemon Editor", "Guest"];
|
|
103
|
+
renderBar();
|
|
104
|
+
await settle();
|
|
105
|
+
await waitFor(() =>
|
|
106
|
+
expect(writeActionLabels()).toEqual(["Bulk Edit", "Submit", "Cancel", "Delete"]),
|
|
107
|
+
);
|
|
108
|
+
expect(screen.getByText("2 selected")).toBeTruthy();
|
|
109
|
+
expect(requested.filter((method) => method.includes("getdoctype"))).toEqual([]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("offers a writer without delete Bulk Edit alone", async () => {
|
|
113
|
+
roles = ["Pokemon Writer", "Guest"];
|
|
114
|
+
renderBar();
|
|
115
|
+
await settle();
|
|
116
|
+
await waitFor(() => expect(writeActionLabels()).toEqual(["Bulk Edit"]));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("keeps Submit and Cancel off a doctype that isn't submittable, even for an editor", async () => {
|
|
120
|
+
roles = ["Pokemon Editor", "Guest"];
|
|
121
|
+
renderBar({ ...meta, name: "QA Plain", is_submittable: 0 });
|
|
122
|
+
await settle();
|
|
123
|
+
await waitFor(() => expect(writeActionLabels()).toEqual(["Bulk Edit", "Delete"]));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("offers a reader none of Bulk Edit, Submit, Cancel or Delete", async () => {
|
|
127
|
+
roles = ["Guest"];
|
|
128
|
+
renderBar();
|
|
129
|
+
await settle();
|
|
130
|
+
expect(writeActionLabels()).toEqual([]);
|
|
131
|
+
expect(screen.getByText("2 selected")).toBeTruthy();
|
|
132
|
+
expect(screen.getByRole("button", { name: "Export CSV" })).toBeTruthy();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// ListBulkActionsBar over a selection (MPO-414): the bar's gates, its
|
|
2
2
|
// confirm-guarded delete and docstatus RPCs, the Bulk Edit dialog it opens,
|
|
3
3
|
// and the host list actions it offers for the doctype on screen.
|
|
4
|
+
// Fixture mode withholds no session rights (MPO-428); reader/editor coverage
|
|
5
|
+
// lives in ListBulkActionsBar.rights.spec.tsx.
|
|
6
|
+
import { InMemoryFixtureProvider } from "@multiplatform.one/frappe";
|
|
4
7
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
5
8
|
import { fireEvent, screen, waitFor, within } from "@testing-library/react";
|
|
6
9
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
@@ -72,7 +75,10 @@ function renderBar({
|
|
|
72
75
|
const onActionComplete = vi.fn();
|
|
73
76
|
const view = renderWithProviders(
|
|
74
77
|
<BackofficeProvider
|
|
75
|
-
frappe={{
|
|
78
|
+
frappe={{
|
|
79
|
+
baseURL: `https://list-bulk-${run}.spec.test`,
|
|
80
|
+
fixtures: new InMemoryFixtureProvider({}),
|
|
81
|
+
}}
|
|
76
82
|
navigation={createMemoryNavigator("/backoffice/ToDo")}
|
|
77
83
|
capabilities={capabilities}
|
|
78
84
|
actions={actions}
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
// DataTable's built-in selection toolbar (house DG-TBL-02 pattern) using
|
|
7
7
|
// the shared dialogs/helpers below; this standalone bar exists for surfaces
|
|
8
8
|
// that manage selection themselves (report/kanban streams).
|
|
9
|
-
import React from "react";
|
|
9
|
+
import React, { useMemo, useState } from "react";
|
|
10
10
|
import { ConfirmDialog, Text, XStack, notify } from "@multiplatform.one/components";
|
|
11
11
|
// Catalog action Button — `error` renders the destructive intent from the
|
|
12
12
|
// semantic ramp instead of a hardcoded named hue (LC-05 ONE-EMPHASIS).
|
|
13
13
|
import { Button } from "@multiplatform.one/forms";
|
|
14
|
+
import { sessionDocRights, useFrappeConfig } from "@multiplatform.one/frappe-ui";
|
|
14
15
|
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
15
|
-
import { useState } from "react";
|
|
16
16
|
import { useTranslation } from "react-i18next";
|
|
17
17
|
import {
|
|
18
18
|
actionsForDoctype,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { BACKOFFICE_I18N_NAMESPACE } from "../i18n";
|
|
23
23
|
import type { DoctypeMeta } from "../shared/useDoctypeMeta";
|
|
24
24
|
import { useFrappeApp } from "../shared/useFrappeApp";
|
|
25
|
+
import { useUserRoles } from "../shared/useUserRoles";
|
|
25
26
|
import { AddTagsDialog } from "./AddTagsDialog";
|
|
26
27
|
import { AssignToDialog } from "./AssignToDialog";
|
|
27
28
|
import { bulkDelete } from "./bulkActions";
|
|
@@ -51,6 +52,16 @@ export function ListBulkActionsBar({
|
|
|
51
52
|
const app = useFrappeApp();
|
|
52
53
|
const capabilities = useBackofficeCapabilities();
|
|
53
54
|
const { actions } = useBackofficeConfig();
|
|
55
|
+
const config = useFrappeConfig();
|
|
56
|
+
const fixtureMode = Boolean(config?.fixtures);
|
|
57
|
+
// Session roles × meta.permissions: write/submit/cancel/delete bulk actions
|
|
58
|
+
// follow the doctype's DocPerm rows (DoctypeListScreen / MPO-390). Fixture
|
|
59
|
+
// mode has no session and withholds nothing.
|
|
60
|
+
const { roles, user: rolesUser } = useUserRoles();
|
|
61
|
+
const rights = useMemo(
|
|
62
|
+
() => sessionDocRights({ fixtures: fixtureMode, meta, roles, user: rolesUser }),
|
|
63
|
+
[fixtureMode, meta, roles, rolesUser],
|
|
64
|
+
);
|
|
54
65
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
55
66
|
const [assignOpen, setAssignOpen] = useState(false);
|
|
56
67
|
const [tagOpen, setTagOpen] = useState(false);
|
|
@@ -222,9 +233,11 @@ export function ListBulkActionsBar({
|
|
|
222
233
|
count: selectedNames.length,
|
|
223
234
|
})}
|
|
224
235
|
</Text>
|
|
225
|
-
|
|
226
|
-
{
|
|
227
|
-
|
|
236
|
+
{rights.write && (
|
|
237
|
+
<Button size={knobProps.sizeToken} onPress={() => setEditOpen(true)}>
|
|
238
|
+
{t("list.editTitle", "Bulk Edit")}
|
|
239
|
+
</Button>
|
|
240
|
+
)}
|
|
228
241
|
<Button
|
|
229
242
|
size={knobProps.sizeToken}
|
|
230
243
|
disabled={exporting}
|
|
@@ -253,53 +266,57 @@ export function ListBulkActionsBar({
|
|
|
253
266
|
{action.label}
|
|
254
267
|
</Button>
|
|
255
268
|
))}
|
|
256
|
-
{isSubmittable && (
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
{isSubmittable && rights.submit && (
|
|
270
|
+
<Button
|
|
271
|
+
size={knobProps.sizeToken}
|
|
272
|
+
disabled={docstatusBusy}
|
|
273
|
+
onPress={() => setDocstatusAction("submit")}
|
|
274
|
+
>
|
|
275
|
+
{t("list.submit", "Submit")}
|
|
276
|
+
</Button>
|
|
277
|
+
)}
|
|
278
|
+
{isSubmittable && rights.cancel && (
|
|
279
|
+
<Button
|
|
280
|
+
size={knobProps.sizeToken}
|
|
281
|
+
disabled={docstatusBusy}
|
|
282
|
+
onPress={() => setDocstatusAction("cancel")}
|
|
283
|
+
>
|
|
284
|
+
{t("list.cancel", "Cancel")}
|
|
285
|
+
</Button>
|
|
273
286
|
)}
|
|
274
287
|
{/* Busy is derived state — `loading` shows the spinner and skips the
|
|
275
288
|
DG-ST-03 bare-disabled guardrail (catalog Toolbar precedent). */}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
289
|
+
{rights.delete && (
|
|
290
|
+
<Button
|
|
291
|
+
size={knobProps.sizeToken}
|
|
292
|
+
error
|
|
293
|
+
loading={deleting}
|
|
294
|
+
onPress={() => setConfirmDelete(true)}
|
|
295
|
+
>
|
|
296
|
+
{t("list.delete", "Delete")}
|
|
297
|
+
</Button>
|
|
298
|
+
)}
|
|
284
299
|
<Button size={knobProps.sizeToken} chromeless onPress={onClearSelection}>
|
|
285
300
|
{t("list.clearSelection", "Clear selection")}
|
|
286
301
|
</Button>
|
|
287
302
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
+
{rights.delete && (
|
|
304
|
+
<ConfirmDialog
|
|
305
|
+
open={confirmDelete}
|
|
306
|
+
onOpenChange={setConfirmDelete}
|
|
307
|
+
destructive
|
|
308
|
+
title={t("list.deleteTitle", "Delete documents")}
|
|
309
|
+
body={t("list.deleteBody", {
|
|
310
|
+
defaultValue: "Permanently delete {{count}} {{doctype}} document(s)?",
|
|
311
|
+
count: selectedNames.length,
|
|
312
|
+
doctype,
|
|
313
|
+
})}
|
|
314
|
+
confirmLabel={t("list.delete", "Delete")}
|
|
315
|
+
onConfirm={() => {
|
|
316
|
+
void handleDelete();
|
|
317
|
+
}}
|
|
318
|
+
/>
|
|
319
|
+
)}
|
|
303
320
|
{showAssign && (
|
|
304
321
|
<AssignToDialog
|
|
305
322
|
doctype={doctype}
|
|
@@ -324,18 +341,20 @@ export function ListBulkActionsBar({
|
|
|
324
341
|
}}
|
|
325
342
|
/>
|
|
326
343
|
)}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
344
|
+
{rights.write && (
|
|
345
|
+
<BulkEditDialog
|
|
346
|
+
doctype={doctype}
|
|
347
|
+
meta={meta}
|
|
348
|
+
names={selectedNames}
|
|
349
|
+
open={editOpen}
|
|
350
|
+
onOpenChange={setEditOpen}
|
|
351
|
+
onDone={() => {
|
|
352
|
+
onClearSelection();
|
|
353
|
+
onActionComplete();
|
|
354
|
+
}}
|
|
355
|
+
/>
|
|
356
|
+
)}
|
|
357
|
+
{isSubmittable && (rights.submit || rights.cancel) && (
|
|
339
358
|
<ConfirmDialog
|
|
340
359
|
open={docstatusAction !== undefined}
|
|
341
360
|
onOpenChange={(open) => {
|
|
@@ -29,7 +29,7 @@ const todoMeta: DoctypeMeta = {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
let run = 0;
|
|
32
|
-
let
|
|
32
|
+
let savedFiltersRead: Promise<URL>;
|
|
33
33
|
|
|
34
34
|
function json(body: unknown, status = 200) {
|
|
35
35
|
return new Response(JSON.stringify(body), {
|
|
@@ -40,10 +40,18 @@ function json(body: unknown, status = 200) {
|
|
|
40
40
|
|
|
41
41
|
beforeEach(() => {
|
|
42
42
|
run += 1;
|
|
43
|
-
|
|
43
|
+
let markSavedFiltersRead: (url: URL) => void = () => {};
|
|
44
|
+
savedFiltersRead = new Promise((resolve) => {
|
|
45
|
+
markSavedFiltersRead = resolve;
|
|
46
|
+
});
|
|
44
47
|
vi.stubGlobal("fetch", async (input: string | URL | Request) => {
|
|
45
48
|
const url = new URL(String(input instanceof Request ? input.url : input));
|
|
46
|
-
|
|
49
|
+
if (
|
|
50
|
+
url.pathname.endsWith("frappe.client.get_list") &&
|
|
51
|
+
url.searchParams.get("doctype") === "List Filter"
|
|
52
|
+
) {
|
|
53
|
+
markSavedFiltersRead(url);
|
|
54
|
+
}
|
|
47
55
|
if (url.pathname.endsWith("frappe.auth.get_logged_user")) {
|
|
48
56
|
return json({ message: "jane@example.com" });
|
|
49
57
|
}
|
|
@@ -153,16 +161,9 @@ describe("ListFilterBar", () => {
|
|
|
153
161
|
|
|
154
162
|
it("reads the doctype's saved filters and offers Save Filter only while filters apply", async () => {
|
|
155
163
|
renderBar();
|
|
156
|
-
await
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
(url) =>
|
|
160
|
-
url.pathname.endsWith("frappe.client.get_list") &&
|
|
161
|
-
url.searchParams.get("doctype") === "List Filter" &&
|
|
162
|
-
url.searchParams.get("filters") ===
|
|
163
|
-
JSON.stringify([["reference_doctype", "=", "ToDo"]]),
|
|
164
|
-
),
|
|
165
|
-
).toBe(true),
|
|
164
|
+
const read = await savedFiltersRead;
|
|
165
|
+
expect(read.searchParams.get("filters")).toBe(
|
|
166
|
+
JSON.stringify([["reference_doctype", "=", "ToDo"]]),
|
|
166
167
|
);
|
|
167
168
|
expect(screen.queryByRole("button", { name: /save filter/i })).toBeNull();
|
|
168
169
|
});
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Skeleton,
|
|
12
12
|
XStack,
|
|
13
13
|
YStack,
|
|
14
|
+
useAccordionTriggerInk,
|
|
14
15
|
} from "@multiplatform.one/components";
|
|
15
16
|
import {
|
|
16
17
|
GroupPositionContext,
|
|
@@ -34,6 +35,15 @@ export interface SidebarSectionProps {
|
|
|
34
35
|
children: ReactNode;
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
function SidebarSectionTitle({ title }: { title: string }) {
|
|
39
|
+
const color = useAccordionTriggerInk("$color11");
|
|
40
|
+
return (
|
|
41
|
+
<SizableText size="$2" fontWeight="400" color={color as never} numberOfLines={1} flexShrink={1}>
|
|
42
|
+
{title}
|
|
43
|
+
</SizableText>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
/** Trigger + facet stack. ListSidebar wraps this in Accordion.Item so the
|
|
38
48
|
* Accordion root can count items (a custom Item wrapper is opaque). */
|
|
39
49
|
export function SidebarSectionChrome({ title, children }: Omit<SidebarSectionProps, "value">) {
|
|
@@ -41,9 +51,7 @@ export function SidebarSectionChrome({ title, children }: Omit<SidebarSectionPro
|
|
|
41
51
|
return (
|
|
42
52
|
<>
|
|
43
53
|
<Accordion.Trigger minHeight="$4" paddingVertical="$1.5" paddingHorizontal="$2">
|
|
44
|
-
<
|
|
45
|
-
{title}
|
|
46
|
-
</SizableText>
|
|
54
|
+
<SidebarSectionTitle title={title} />
|
|
47
55
|
</Accordion.Trigger>
|
|
48
56
|
<Accordion.Content padding={0}>
|
|
49
57
|
<YStack>
|