@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
|
@@ -1,529 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
// Composed from @multiplatform.one/table's Table primitives (exported for
|
|
7
|
-
// standalone use) rather than DataTable: DataTable owns a single internal
|
|
8
|
-
// scrollport and wraps header content in Text, so it cannot host an input
|
|
9
|
-
// row under its headers today — capability gap flagged to the table
|
|
10
|
-
// workstream; this grid is the interim desk-parity surface.
|
|
11
|
-
import { ScrollView, Text, Tooltip, View, XStack, isWeb } from "@multiplatform.one/components";
|
|
12
|
-
import { Input } from "@multiplatform.one/forms";
|
|
13
|
-
import { Table } from "@multiplatform.one/table";
|
|
14
|
-
import {
|
|
15
|
-
ensureFocusVisibleRing,
|
|
16
|
-
formatCurrency,
|
|
17
|
-
formatNumber,
|
|
18
|
-
formatPercent,
|
|
19
|
-
} from "@multiplatform.one/theme";
|
|
20
|
-
import { CaretDownIcon, CaretUpDownIcon } from "@phosphor-icons/react";
|
|
21
|
-
import React, { useCallback, useState } from "react";
|
|
22
|
-
import { useTranslation } from "react-i18next";
|
|
23
|
-
import { BACKOFFICE_I18N_NAMESPACE } from "../i18n";
|
|
24
|
-
import { formatDateCell } from "@multiplatform.one/frappe-ui";
|
|
25
|
-
import {
|
|
26
|
-
REPORT_NUMERIC_FIELDTYPES,
|
|
27
|
-
type ReportColumnDef,
|
|
28
|
-
type ReportGridRow,
|
|
29
|
-
type ReportRow,
|
|
30
|
-
type ReportSortState,
|
|
31
|
-
type ReportTotalsCell,
|
|
32
|
-
} from "./reportViewModel";
|
|
33
|
-
|
|
34
|
-
const ROW_NUMBER_WIDTH = 56;
|
|
35
|
-
|
|
36
|
-
// ── LC-49/LC-50 row keyboard contract ───────────────────────────────
|
|
37
|
-
// Local mirror of the table package's `isInteractiveInnerTarget`
|
|
38
|
-
// (public/table/src/utils/rowPress.ts — internal util, not on the
|
|
39
|
-
// barrel): when the whole row is one pressable unit, presses and keys
|
|
40
|
-
// that originate on inner interactive content (Link cells, future
|
|
41
|
-
// editors) must keep working without also firing the row press.
|
|
42
|
-
const INTERACTIVE_SELECTOR = [
|
|
43
|
-
"a[href]",
|
|
44
|
-
"button",
|
|
45
|
-
"input",
|
|
46
|
-
"select",
|
|
47
|
-
"textarea",
|
|
48
|
-
"label",
|
|
49
|
-
'[contenteditable="true"]',
|
|
50
|
-
'[role="button"]',
|
|
51
|
-
'[role="checkbox"]',
|
|
52
|
-
'[role="combobox"]',
|
|
53
|
-
'[role="link"]',
|
|
54
|
-
'[role="menuitem"]',
|
|
55
|
-
'[role="option"]',
|
|
56
|
-
'[role="radio"]',
|
|
57
|
-
'[role="slider"]',
|
|
58
|
-
'[role="spinbutton"]',
|
|
59
|
-
'[role="switch"]',
|
|
60
|
-
'[role="textbox"]',
|
|
61
|
-
"[aria-expanded]",
|
|
62
|
-
].join(", ");
|
|
63
|
-
|
|
64
|
-
function isInteractiveInnerTarget(target: unknown, boundary: unknown): boolean {
|
|
65
|
-
if (typeof Element === "undefined") return false;
|
|
66
|
-
if (!(target instanceof Element)) return false;
|
|
67
|
-
const boundaryEl = boundary instanceof Element ? boundary : null;
|
|
68
|
-
let node: Element | null = target;
|
|
69
|
-
while (node && node !== boundaryEl) {
|
|
70
|
-
if (node.matches(INTERACTIVE_SELECTOR)) return true;
|
|
71
|
-
const tabIndex = node.getAttribute("tabindex");
|
|
72
|
-
// A tab stop of its own (0 or positive) marks a focusable control; the
|
|
73
|
-
// row's roving -1/0 tabindex lives on the boundary, not inside it.
|
|
74
|
-
if (tabIndex != null && Number(tabIndex) >= 0) return true;
|
|
75
|
-
node = node.parentElement;
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface ReportGridProps {
|
|
81
|
-
columns: readonly ReportColumnDef[];
|
|
82
|
-
rows: readonly ReportGridRow[];
|
|
83
|
-
sort: ReportSortState;
|
|
84
|
-
onSortChange: (sort: ReportSortState) => void;
|
|
85
|
-
/** Filter-row DRAFT values by fieldname (uncommitted text). */
|
|
86
|
-
columnFilters: Readonly<Record<string, string>>;
|
|
87
|
-
onColumnFilterChange: (fieldname: string, value: string) => void;
|
|
88
|
-
/** Open the row's document form. */
|
|
89
|
-
onOpenRow: (name: string) => void;
|
|
90
|
-
/** Open a Link cell's target document. */
|
|
91
|
-
onOpenLink?: (doctype: string, name: string) => void;
|
|
92
|
-
/** Totals over the full filtered result — rendered as a bottom row. */
|
|
93
|
-
totals: readonly ReportTotalsCell[];
|
|
94
|
-
/** Group-by column (group header rows span the grid). */
|
|
95
|
-
groupByColumn?: ReportColumnDef;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function alignToJustify(align: ReportColumnDef["align"]) {
|
|
99
|
-
return align === "right" ? "flex-end" : align === "center" ? "center" : "flex-start";
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function formatNumericCell(value: number, fieldtype: string): string {
|
|
103
|
-
switch (fieldtype) {
|
|
104
|
-
case "Currency":
|
|
105
|
-
return formatCurrency(value);
|
|
106
|
-
case "Int":
|
|
107
|
-
return formatNumber(Math.round(value), { maximumFractionDigits: 0 });
|
|
108
|
-
case "Percent":
|
|
109
|
-
return formatPercent(value, { precision: 1 });
|
|
110
|
-
default:
|
|
111
|
-
return formatNumber(value);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** Fieldtype-aware cell content (query-report cell channel, D-04 numbers). */
|
|
116
|
-
function ReportCellValue({
|
|
117
|
-
column,
|
|
118
|
-
row,
|
|
119
|
-
onOpenLink,
|
|
120
|
-
}: {
|
|
121
|
-
column: ReportColumnDef;
|
|
122
|
-
row: ReportRow;
|
|
123
|
-
onOpenLink?: (doctype: string, name: string) => void;
|
|
124
|
-
}) {
|
|
125
|
-
const raw = row[column.fieldname];
|
|
126
|
-
if (raw === null || raw === undefined || raw === "") {
|
|
127
|
-
return (
|
|
128
|
-
<Text fontSize="$3" color="$color8">
|
|
129
|
-
—
|
|
130
|
-
</Text>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
if (column.fieldtype === "Check") {
|
|
134
|
-
return (
|
|
135
|
-
<Text fontSize="$3" color={raw ? "$green11" : "$color8"}>
|
|
136
|
-
{raw ? "✓" : "—"}
|
|
137
|
-
</Text>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
if (REPORT_NUMERIC_FIELDTYPES.has(column.fieldtype)) {
|
|
141
|
-
const numeric = Number(raw);
|
|
142
|
-
return (
|
|
143
|
-
<Text fontSize="$3" color="$color12" fontFamily="$mono">
|
|
144
|
-
{Number.isFinite(numeric) ? formatNumericCell(numeric, column.fieldtype) : String(raw)}
|
|
145
|
-
</Text>
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
const value = String(raw);
|
|
149
|
-
switch (column.fieldtype) {
|
|
150
|
-
case "Date":
|
|
151
|
-
case "Datetime":
|
|
152
|
-
return (
|
|
153
|
-
<Text fontSize="$3" color="$color11" numberOfLines={1}>
|
|
154
|
-
{formatDateCell(value)}
|
|
155
|
-
</Text>
|
|
156
|
-
);
|
|
157
|
-
case "Link": {
|
|
158
|
-
const target = column.options;
|
|
159
|
-
if (target && onOpenLink) {
|
|
160
|
-
return (
|
|
161
|
-
<Text
|
|
162
|
-
fontSize="$3"
|
|
163
|
-
color="$color11"
|
|
164
|
-
textDecorationLine="underline"
|
|
165
|
-
cursor="pointer"
|
|
166
|
-
numberOfLines={1}
|
|
167
|
-
role="link"
|
|
168
|
-
onPress={(event) => {
|
|
169
|
-
event?.stopPropagation?.();
|
|
170
|
-
onOpenLink(target, value);
|
|
171
|
-
}}
|
|
172
|
-
>
|
|
173
|
-
{value}
|
|
174
|
-
</Text>
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
return (
|
|
178
|
-
<Text fontSize="$3" color="$color11" numberOfLines={1}>
|
|
179
|
-
{value}
|
|
180
|
-
</Text>
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
default:
|
|
184
|
-
return (
|
|
185
|
-
<Text fontSize="$3" color="$color11" numberOfLines={1}>
|
|
186
|
-
{value}
|
|
187
|
-
</Text>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export function ReportGrid({
|
|
193
|
-
columns,
|
|
194
|
-
rows,
|
|
195
|
-
sort,
|
|
196
|
-
onSortChange,
|
|
197
|
-
columnFilters,
|
|
198
|
-
onColumnFilterChange,
|
|
199
|
-
onOpenRow,
|
|
200
|
-
onOpenLink,
|
|
201
|
-
totals,
|
|
202
|
-
groupByColumn,
|
|
203
|
-
}: ReportGridProps) {
|
|
204
|
-
const { t } = useTranslation(BACKOFFICE_I18N_NAMESPACE);
|
|
205
|
-
|
|
206
|
-
const handleSortPress = useCallback(
|
|
207
|
-
(fieldname: string) => {
|
|
208
|
-
if (sort.field === fieldname) {
|
|
209
|
-
onSortChange({ field: fieldname, order: sort.order === "asc" ? "desc" : "asc" });
|
|
210
|
-
} else {
|
|
211
|
-
onSortChange({ field: fieldname, order: "asc" });
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
[sort, onSortChange],
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
// ── Row press + keyboard (LC-49 ONE-TAB-STOP, LC-50 KEY-ACTIVATION) ──
|
|
218
|
-
// Mirrors DataTable's getRowPressProps contract (DataTableRoot): the
|
|
219
|
-
// DATA-row set exposes exactly one tab stop (roving anchor — the focused
|
|
220
|
-
// row while focus is inside the set, else the first row); Enter AND
|
|
221
|
-
// Space each fire exactly one activation; arrows/Home/End rove focus;
|
|
222
|
-
// inner interactive content (Link cells) wins over the row press.
|
|
223
|
-
const isSSR = typeof window === "undefined";
|
|
224
|
-
const [rowAnchor, setRowAnchor] = useState(0);
|
|
225
|
-
const dataRowCount = rows.reduce((count, row) => (row.kind === "row" ? count + 1 : count), 0);
|
|
226
|
-
const effectiveRowAnchor = Math.min(rowAnchor, Math.max(dataRowCount - 1, 0));
|
|
227
|
-
|
|
228
|
-
const getRowPressProps = useCallback(
|
|
229
|
-
(row: ReportRow, pressIndex: number) => {
|
|
230
|
-
const handlePress = (
|
|
231
|
-
event:
|
|
232
|
-
| { defaultPrevented?: boolean; target?: unknown; nativeEvent?: { target?: unknown } }
|
|
233
|
-
| undefined,
|
|
234
|
-
) => {
|
|
235
|
-
if (event?.defaultPrevented) return;
|
|
236
|
-
const targetEl = event?.target ?? event?.nativeEvent?.target;
|
|
237
|
-
if (isWeb && typeof Element !== "undefined" && targetEl instanceof Element) {
|
|
238
|
-
const boundary = targetEl.closest('[data-report-row="true"]');
|
|
239
|
-
if (isInteractiveInnerTarget(targetEl, boundary)) return;
|
|
240
|
-
}
|
|
241
|
-
onOpenRow(row.name);
|
|
242
|
-
};
|
|
243
|
-
return {
|
|
244
|
-
// Accessible row name from doc identity (DG-A11Y-06); positional
|
|
245
|
-
// fallback for the defensive empty-name case.
|
|
246
|
-
"aria-label":
|
|
247
|
-
row.name || t("report.rowLabel", { defaultValue: "Row {{row}}", row: pressIndex + 1 }),
|
|
248
|
-
onPress: handlePress,
|
|
249
|
-
cursor: "pointer" as const,
|
|
250
|
-
hoverStyle: { backgroundColor: "$color2" },
|
|
251
|
-
pressStyle: { backgroundColor: "$color4" },
|
|
252
|
-
...(isWeb && !isSSR
|
|
253
|
-
? {
|
|
254
|
-
tabIndex: pressIndex === effectiveRowAnchor ? 0 : -1,
|
|
255
|
-
outlineWidth: 0,
|
|
256
|
-
focusStyle: { outlineWidth: 0 },
|
|
257
|
-
focusVisibleStyle: ensureFocusVisibleRing({ outlineOffset: -2 }),
|
|
258
|
-
"data-report-row": "true",
|
|
259
|
-
onFocus: (event: React.FocusEvent) => {
|
|
260
|
-
// Only a focused ROW moves the anchor; focus on an inner
|
|
261
|
-
// control (own tab stop) leaves the roving state alone.
|
|
262
|
-
if (event.target === event.currentTarget) setRowAnchor(pressIndex);
|
|
263
|
-
},
|
|
264
|
-
onBlur: (event: React.FocusEvent) => {
|
|
265
|
-
const next = event.relatedTarget;
|
|
266
|
-
if (!(next instanceof Element) || !next.closest('[data-report-row="true"]')) {
|
|
267
|
-
setRowAnchor(0);
|
|
268
|
-
}
|
|
269
|
-
},
|
|
270
|
-
onKeyDown: (event: React.KeyboardEvent) => {
|
|
271
|
-
if (event.defaultPrevented) return;
|
|
272
|
-
// Keys on focused inner controls belong to those controls.
|
|
273
|
-
if (event.target !== event.currentTarget) return;
|
|
274
|
-
const key = event.key;
|
|
275
|
-
if (key === "Enter" || key === " " || key === "Spacebar") {
|
|
276
|
-
// preventDefault stops Space page-scroll; one physical
|
|
277
|
-
// key press = one activation (auto-repeat guarded).
|
|
278
|
-
event.preventDefault();
|
|
279
|
-
event.stopPropagation();
|
|
280
|
-
if (event.repeat) return;
|
|
281
|
-
onOpenRow(row.name);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
if (key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End") {
|
|
285
|
-
event.preventDefault();
|
|
286
|
-
event.stopPropagation();
|
|
287
|
-
const rowEl = event.currentTarget as HTMLElement;
|
|
288
|
-
const scope = rowEl.closest('[data-report-grid="true"]') ?? rowEl.ownerDocument;
|
|
289
|
-
const pressRows = Array.from(
|
|
290
|
-
scope.querySelectorAll('[data-report-row="true"]'),
|
|
291
|
-
) as HTMLElement[];
|
|
292
|
-
const currentIdx = pressRows.indexOf(rowEl);
|
|
293
|
-
if (currentIdx < 0) return;
|
|
294
|
-
const nextIdx =
|
|
295
|
-
key === "Home"
|
|
296
|
-
? 0
|
|
297
|
-
: key === "End"
|
|
298
|
-
? pressRows.length - 1
|
|
299
|
-
: Math.min(
|
|
300
|
-
Math.max(currentIdx + (key === "ArrowDown" ? 1 : -1), 0),
|
|
301
|
-
pressRows.length - 1,
|
|
302
|
-
);
|
|
303
|
-
pressRows[nextIdx]?.focus();
|
|
304
|
-
}
|
|
305
|
-
},
|
|
306
|
-
}
|
|
307
|
-
: {}),
|
|
308
|
-
};
|
|
309
|
-
},
|
|
310
|
-
[onOpenRow, effectiveRowAnchor, isSSR, t],
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
const totalsByField = new Map(totals.map((total) => [total.fieldname, total]));
|
|
314
|
-
const gridWidth = ROW_NUMBER_WIDTH + columns.reduce((sum, column) => sum + column.width, 0);
|
|
315
|
-
|
|
316
|
-
return (
|
|
317
|
-
<ScrollView horizontal showsHorizontalScrollIndicator flexGrow={0} flexShrink={1}>
|
|
318
|
-
<Table
|
|
319
|
-
minWidth={gridWidth}
|
|
320
|
-
width={gridWidth}
|
|
321
|
-
displayMode="flex"
|
|
322
|
-
cellHeight="$3"
|
|
323
|
-
// Roving scope marker: arrow-key navigation queries pressable rows
|
|
324
|
-
// within this container (getRowPressProps).
|
|
325
|
-
{...({ "data-report-grid": "true" } as Record<string, unknown>)}
|
|
326
|
-
>
|
|
327
|
-
<Table.Head>
|
|
328
|
-
{/* Header row: label + sort affordance (aria-sort, Enter/Space). */}
|
|
329
|
-
<Table.Row>
|
|
330
|
-
<Table.HeaderCell
|
|
331
|
-
cellWidth={ROW_NUMBER_WIDTH}
|
|
332
|
-
aria-label={t("report.rowNumber", "Row number")}
|
|
333
|
-
>
|
|
334
|
-
<Text fontSize="$2" color="$color9">
|
|
335
|
-
#
|
|
336
|
-
</Text>
|
|
337
|
-
</Table.HeaderCell>
|
|
338
|
-
{columns.map((column) => {
|
|
339
|
-
const sorted = sort.field === column.fieldname ? sort.order : undefined;
|
|
340
|
-
return (
|
|
341
|
-
<Table.HeaderCell
|
|
342
|
-
key={column.fieldname}
|
|
343
|
-
cellWidth={column.width}
|
|
344
|
-
justifyContent={alignToJustify(column.align)}
|
|
345
|
-
aria-sort={
|
|
346
|
-
sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : "none"
|
|
347
|
-
}
|
|
348
|
-
>
|
|
349
|
-
<Tooltip
|
|
350
|
-
content={
|
|
351
|
-
sorted === "asc"
|
|
352
|
-
? t("report.sortDesc", "Sorted ascending (press to sort descending)")
|
|
353
|
-
: sorted === "desc"
|
|
354
|
-
? t("report.sortAsc", "Sorted descending (press to sort ascending)")
|
|
355
|
-
: t("report.sortColumn", "Sort by this column")
|
|
356
|
-
}
|
|
357
|
-
>
|
|
358
|
-
<XStack
|
|
359
|
-
gap="$1.5"
|
|
360
|
-
alignItems="center"
|
|
361
|
-
cursor="pointer"
|
|
362
|
-
role="button"
|
|
363
|
-
tabIndex={0}
|
|
364
|
-
outlineWidth={0}
|
|
365
|
-
focusStyle={{ outlineWidth: 0 }}
|
|
366
|
-
focusVisibleStyle={ensureFocusVisibleRing({ outlineOffset: -2 })}
|
|
367
|
-
aria-label={t("report.sortBy", {
|
|
368
|
-
defaultValue: "Sort by {{column}}",
|
|
369
|
-
column: column.label,
|
|
370
|
-
})}
|
|
371
|
-
onPress={() => handleSortPress(column.fieldname)}
|
|
372
|
-
onKeyDown={(event: { key?: string; preventDefault?: () => void }) => {
|
|
373
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
374
|
-
event.preventDefault?.();
|
|
375
|
-
handleSortPress(column.fieldname);
|
|
376
|
-
}
|
|
377
|
-
}}
|
|
378
|
-
hoverStyle={{ opacity: 0.8 }}
|
|
379
|
-
>
|
|
380
|
-
<Text fontSize="$3" fontWeight="400" numberOfLines={1}>
|
|
381
|
-
{column.label}
|
|
382
|
-
</Text>
|
|
383
|
-
{/* Caret orientation mirrors DataTable's headers:
|
|
384
|
-
asc = caret-down, desc = rotated 180. */}
|
|
385
|
-
<View aria-hidden role="presentation">
|
|
386
|
-
{sorted ? (
|
|
387
|
-
<CaretDownIcon
|
|
388
|
-
size={12}
|
|
389
|
-
color="var(--color10)"
|
|
390
|
-
style={sorted === "desc" ? { transform: "rotate(180deg)" } : undefined}
|
|
391
|
-
/>
|
|
392
|
-
) : (
|
|
393
|
-
<CaretUpDownIcon size={12} color="var(--color8)" />
|
|
394
|
-
)}
|
|
395
|
-
</View>
|
|
396
|
-
</XStack>
|
|
397
|
-
</Tooltip>
|
|
398
|
-
</Table.HeaderCell>
|
|
399
|
-
);
|
|
400
|
-
})}
|
|
401
|
-
</Table.Row>
|
|
402
|
-
{/* Filter row — desk parity: one input per column, standard filter
|
|
403
|
-
pipeline. Number/date accept comparison prefixes (>, <, >=…). */}
|
|
404
|
-
<Table.Row>
|
|
405
|
-
<Table.HeaderCell cellWidth={ROW_NUMBER_WIDTH} paddingVertical="$1.5" />
|
|
406
|
-
{columns.map((column) => {
|
|
407
|
-
const placeholder =
|
|
408
|
-
column.kind === "number" || column.kind === "date"
|
|
409
|
-
? t("report.filterComparablePlaceholder", "= > <…")
|
|
410
|
-
: column.kind === "check"
|
|
411
|
-
? t("report.filterCheckPlaceholder", "yes / no")
|
|
412
|
-
: t("report.filterPlaceholder", "Filter…");
|
|
413
|
-
return (
|
|
414
|
-
<Table.HeaderCell
|
|
415
|
-
key={column.fieldname}
|
|
416
|
-
cellWidth={column.width}
|
|
417
|
-
paddingVertical="$1.5"
|
|
418
|
-
overflow="visible"
|
|
419
|
-
>
|
|
420
|
-
<View flex={1} minWidth={0} overflow="visible">
|
|
421
|
-
<Input
|
|
422
|
-
size="$2"
|
|
423
|
-
compact
|
|
424
|
-
value={columnFilters[column.fieldname] ?? ""}
|
|
425
|
-
onChangeText={(next) => onColumnFilterChange(column.fieldname, next)}
|
|
426
|
-
placeholder={placeholder}
|
|
427
|
-
// inputProps is the eject that reaches the real input
|
|
428
|
-
// element — a top-level aria-label stays on the wrapper.
|
|
429
|
-
inputProps={{
|
|
430
|
-
"aria-label": t("report.filterColumn", {
|
|
431
|
-
defaultValue: "Filter {{column}}",
|
|
432
|
-
column: column.label,
|
|
433
|
-
}),
|
|
434
|
-
}}
|
|
435
|
-
/>
|
|
436
|
-
</View>
|
|
437
|
-
</Table.HeaderCell>
|
|
438
|
-
);
|
|
439
|
-
})}
|
|
440
|
-
</Table.Row>
|
|
441
|
-
</Table.Head>
|
|
442
|
-
<Table.Body>
|
|
443
|
-
{(() => {
|
|
444
|
-
// Position within the pressable DATA-row set (group headers are
|
|
445
|
-
// not pressable and hold no tab stop).
|
|
446
|
-
let pressCursor = -1;
|
|
447
|
-
return rows.map((gridRow) => {
|
|
448
|
-
if (gridRow.kind === "group") {
|
|
449
|
-
const label =
|
|
450
|
-
gridRow.value === null || gridRow.value === undefined || gridRow.value === ""
|
|
451
|
-
? t("report.groupEmpty", "(blank)")
|
|
452
|
-
: String(gridRow.value);
|
|
453
|
-
return (
|
|
454
|
-
<Table.Row key={gridRow.key} backgroundColor="$color2">
|
|
455
|
-
<Table.Cell cellWidth={gridWidth} paddingVertical="$1.5" role="rowheader">
|
|
456
|
-
<Text fontSize="$2" fontWeight="400" color="$color11" numberOfLines={1}>
|
|
457
|
-
{groupByColumn
|
|
458
|
-
? t("report.groupHeader", {
|
|
459
|
-
defaultValue: "{{field}}: {{value}} · {{count}}",
|
|
460
|
-
field: groupByColumn.label,
|
|
461
|
-
value: label,
|
|
462
|
-
count: gridRow.count,
|
|
463
|
-
})
|
|
464
|
-
: label}
|
|
465
|
-
</Text>
|
|
466
|
-
</Table.Cell>
|
|
467
|
-
</Table.Row>
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
pressCursor += 1;
|
|
471
|
-
return (
|
|
472
|
-
// Spread LAST so the roving tabIndex/handlers stay in
|
|
473
|
-
// charge of this row's interactivity (DataTable precedent).
|
|
474
|
-
<Table.Row
|
|
475
|
-
key={gridRow.key}
|
|
476
|
-
{...(getRowPressProps(gridRow.row, pressCursor) as Record<string, unknown>)}
|
|
477
|
-
>
|
|
478
|
-
<Table.Cell cellWidth={ROW_NUMBER_WIDTH} paddingVertical="$2">
|
|
479
|
-
<Text fontSize="$2" color="$color9" fontFamily="$mono">
|
|
480
|
-
{gridRow.index}
|
|
481
|
-
</Text>
|
|
482
|
-
</Table.Cell>
|
|
483
|
-
{columns.map((column) => (
|
|
484
|
-
<Table.Cell
|
|
485
|
-
key={column.fieldname}
|
|
486
|
-
cellWidth={column.width}
|
|
487
|
-
paddingVertical="$2"
|
|
488
|
-
justifyContent={alignToJustify(column.align)}
|
|
489
|
-
>
|
|
490
|
-
<ReportCellValue column={column} row={gridRow.row} onOpenLink={onOpenLink} />
|
|
491
|
-
</Table.Cell>
|
|
492
|
-
))}
|
|
493
|
-
</Table.Row>
|
|
494
|
-
);
|
|
495
|
-
});
|
|
496
|
-
})()}
|
|
497
|
-
</Table.Body>
|
|
498
|
-
{totals.length > 0 && (
|
|
499
|
-
<Table.Foot borderTopWidth={1} borderTopColor="$color6">
|
|
500
|
-
<Table.Row backgroundColor="$color2">
|
|
501
|
-
<Table.Cell cellWidth={ROW_NUMBER_WIDTH} paddingVertical="$2">
|
|
502
|
-
<Text fontSize="$2" fontWeight="400" color="$color10">
|
|
503
|
-
{t("report.totals", "Totals")}
|
|
504
|
-
</Text>
|
|
505
|
-
</Table.Cell>
|
|
506
|
-
{columns.map((column) => {
|
|
507
|
-
const total = totalsByField.get(column.fieldname);
|
|
508
|
-
return (
|
|
509
|
-
<Table.Cell
|
|
510
|
-
key={column.fieldname}
|
|
511
|
-
cellWidth={column.width}
|
|
512
|
-
paddingVertical="$2"
|
|
513
|
-
justifyContent={alignToJustify(column.align)}
|
|
514
|
-
>
|
|
515
|
-
{total ? (
|
|
516
|
-
<Text fontSize="$3" fontWeight="400" color="$color12" fontFamily="$mono">
|
|
517
|
-
{formatNumericCell(total.value, total.fieldtype)}
|
|
518
|
-
</Text>
|
|
519
|
-
) : null}
|
|
520
|
-
</Table.Cell>
|
|
521
|
-
);
|
|
522
|
-
})}
|
|
523
|
-
</Table.Row>
|
|
524
|
-
</Table.Foot>
|
|
525
|
-
)}
|
|
526
|
-
</Table>
|
|
527
|
-
</ScrollView>
|
|
528
|
-
);
|
|
529
|
-
}
|
|
1
|
+
// The report grid lives in @multiplatform.one/frappe-ui (MPO-15, GL-28);
|
|
2
|
+
// this path keeps handing out the same names.
|
|
3
|
+
export { ReportGrid } from "@multiplatform.one/frappe-ui";
|
|
4
|
+
export type { ReportGridProps } from "@multiplatform.one/frappe-ui";
|