@nubitio/crud 0.5.19 → 0.5.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +74 -40
- package/dist/index.d.cts +3 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +74 -40
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -2110,7 +2110,7 @@ function buildToolbar(options, t, onAddClick, includeAddAction = true) {
|
|
|
2110
2110
|
showRefresh: options.toolbar?.showRefresh ?? true
|
|
2111
2111
|
};
|
|
2112
2112
|
}
|
|
2113
|
-
function SummaryFooter({ fields, hasCheckbox, hasDetail, hasRowActions, rows, summaryFields, footerRef, colWidths }) {
|
|
2113
|
+
function SummaryFooter({ fields, hasCheckbox, hasDetail, hasRowActions, rows, summaryFields, gridSummary, footerRef, colWidths }) {
|
|
2114
2114
|
if (!summaryFields?.length) return null;
|
|
2115
2115
|
const itemsByColumn = new Map(summaryFields.filter((item) => item.column).map((item) => [item.column, item]));
|
|
2116
2116
|
const unboundItems = summaryFields.filter((item) => !item.column);
|
|
@@ -2138,7 +2138,7 @@ function SummaryFooter({ fields, hasCheckbox, hasDetail, hasRowActions, rows, su
|
|
|
2138
2138
|
children: item.label
|
|
2139
2139
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2140
2140
|
className: "nb-datagrid__summary-value",
|
|
2141
|
-
children: resolveSummaryText(rows, item)
|
|
2141
|
+
children: item.column && gridSummary && item.column in gridSummary ? formatSummaryValue(gridSummary[item.column], item) : resolveSummaryText(rows, item)
|
|
2142
2142
|
})]
|
|
2143
2143
|
})
|
|
2144
2144
|
}, field.name);
|
|
@@ -2159,6 +2159,7 @@ const NativeDataGridView = (0, react.forwardRef)((options, ref) => {
|
|
|
2159
2159
|
const [rows, setRows] = (0, react.useState)([]);
|
|
2160
2160
|
const rowsRef = (0, react.useRef)([]);
|
|
2161
2161
|
const [totalCount, setTotalCount] = (0, react.useState)(0);
|
|
2162
|
+
const [gridSummary, setGridSummary] = (0, react.useState)(null);
|
|
2162
2163
|
const [selectedKeys, setSelectedKeys] = (0, react.useState)([]);
|
|
2163
2164
|
const [filters, setFilters] = (0, react.useState)({});
|
|
2164
2165
|
const [filterInputs, setFilterInputs] = (0, react.useState)({});
|
|
@@ -2340,6 +2341,7 @@ const NativeDataGridView = (0, react.forwardRef)((options, ref) => {
|
|
|
2340
2341
|
rowsRef.current = result.data;
|
|
2341
2342
|
setRows(result.data);
|
|
2342
2343
|
setTotalCount(result.totalCount);
|
|
2344
|
+
setGridSummary(result.gridSummary ?? null);
|
|
2343
2345
|
setIsGridLoading(false);
|
|
2344
2346
|
onContentReadyRef.current?.();
|
|
2345
2347
|
return result.data;
|
|
@@ -3135,6 +3137,7 @@ const NativeDataGridView = (0, react.forwardRef)((options, ref) => {
|
|
|
3135
3137
|
hasRowActions,
|
|
3136
3138
|
rows,
|
|
3137
3139
|
summaryFields: options.summaryFields,
|
|
3140
|
+
gridSummary,
|
|
3138
3141
|
footerRef: tfootRef,
|
|
3139
3142
|
colWidths: resolvedColWidths
|
|
3140
3143
|
})
|
|
@@ -3173,7 +3176,7 @@ const NativeDataGridView = (0, react.forwardRef)((options, ref) => {
|
|
|
3173
3176
|
children: item.label
|
|
3174
3177
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3175
3178
|
className: "nb-datagrid__summary-value",
|
|
3176
|
-
children: resolveSummaryText(rows, item)
|
|
3179
|
+
children: item.column && gridSummary && item.column in gridSummary ? formatSummaryValue(gridSummary[item.column], item) : resolveSummaryText(rows, item)
|
|
3177
3180
|
})]
|
|
3178
3181
|
}, index))
|
|
3179
3182
|
}),
|
|
@@ -7466,14 +7469,16 @@ function ResourceSchemaProvider({ children, resolver }) {
|
|
|
7466
7469
|
children
|
|
7467
7470
|
});
|
|
7468
7471
|
}
|
|
7469
|
-
function resolveWithRuntimeErrors(resolver, supportedOperations = [], formLayout) {
|
|
7472
|
+
function resolveWithRuntimeErrors(resolver, supportedOperations = [], formLayout, workflow, summaryFields) {
|
|
7470
7473
|
try {
|
|
7471
7474
|
return {
|
|
7472
7475
|
fields: resolver(),
|
|
7473
7476
|
isLoading: false,
|
|
7474
7477
|
error: void 0,
|
|
7475
7478
|
supportedOperations,
|
|
7476
|
-
formLayout
|
|
7479
|
+
formLayout,
|
|
7480
|
+
workflow,
|
|
7481
|
+
summaryFields
|
|
7477
7482
|
};
|
|
7478
7483
|
} catch (runtimeError) {
|
|
7479
7484
|
return {
|
|
@@ -7481,7 +7486,9 @@ function resolveWithRuntimeErrors(resolver, supportedOperations = [], formLayout
|
|
|
7481
7486
|
isLoading: false,
|
|
7482
7487
|
error: runtimeError instanceof Error ? runtimeError : new Error(String(runtimeError)),
|
|
7483
7488
|
supportedOperations,
|
|
7484
|
-
formLayout
|
|
7489
|
+
formLayout,
|
|
7490
|
+
workflow,
|
|
7491
|
+
summaryFields
|
|
7485
7492
|
};
|
|
7486
7493
|
}
|
|
7487
7494
|
}
|
|
@@ -7507,7 +7514,7 @@ function useResolvedResourceFields({ apiUrl, manualFields, overrides, fieldContr
|
|
|
7507
7514
|
baselineFields: baseline.fields,
|
|
7508
7515
|
contract: fieldContract,
|
|
7509
7516
|
legacyOverrides: fieldContract ? void 0 : overrides
|
|
7510
|
-
}), baseline.supportedOperations, baseline.formLayout);
|
|
7517
|
+
}), baseline.supportedOperations, baseline.formLayout, baseline.workflow, baseline.summaryFields);
|
|
7511
7518
|
}, [
|
|
7512
7519
|
baseline,
|
|
7513
7520
|
fieldContract,
|
|
@@ -7515,6 +7522,28 @@ function useResolvedResourceFields({ apiUrl, manualFields, overrides, fieldContr
|
|
|
7515
7522
|
]);
|
|
7516
7523
|
}
|
|
7517
7524
|
//#endregion
|
|
7525
|
+
//#region packages/crud/workflow/buildWorkflowRowActions.ts
|
|
7526
|
+
function buildWorkflowRowActions(row, workflow, apiUrl, roles, onDone) {
|
|
7527
|
+
if (!workflow) return [];
|
|
7528
|
+
const current = String(row[workflow.field] ?? "");
|
|
7529
|
+
return workflow.transitions.filter((transition) => transition.from.includes(current)).filter((transition) => !transition.roles?.length || transition.roles.some((role) => roles.includes(role))).map((transition) => ({
|
|
7530
|
+
text: transition.label ?? transition.name,
|
|
7531
|
+
onClick: async () => {
|
|
7532
|
+
const base = apiUrl.replace(/\/$/, "");
|
|
7533
|
+
const id = row.id;
|
|
7534
|
+
const response = await fetch(`${base}/${id}/transition/${transition.name}`, {
|
|
7535
|
+
method: "POST",
|
|
7536
|
+
credentials: "include"
|
|
7537
|
+
});
|
|
7538
|
+
if (!response.ok) {
|
|
7539
|
+
const detail = await response.text().catch(() => "");
|
|
7540
|
+
throw new Error(detail || `Transition "${transition.name}" failed (${response.status})`);
|
|
7541
|
+
}
|
|
7542
|
+
onDone?.();
|
|
7543
|
+
}
|
|
7544
|
+
}));
|
|
7545
|
+
}
|
|
7546
|
+
//#endregion
|
|
7518
7547
|
//#region packages/crud/crud/SmartCrudPage.tsx
|
|
7519
7548
|
function CrudSkeleton() {
|
|
7520
7549
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -7587,7 +7616,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
|
|
|
7587
7616
|
const effectiveGridRef = gridRef ?? internalGridRef;
|
|
7588
7617
|
const resolvedBaseResource = (0, react.useMemo)(() => resolveCrudResource(resource), [resource]);
|
|
7589
7618
|
const hasManualFields = !resource.fieldContract && Array.isArray(resource.fields) && resource.fields.length > 0;
|
|
7590
|
-
const { fields, isLoading, error, supportedOperations, formLayout: inferredFormLayout } = useResolvedResourceFields({
|
|
7619
|
+
const { fields, isLoading, error, supportedOperations, formLayout: inferredFormLayout, workflow, summaryFields: inferredSummaryFields } = useResolvedResourceFields({
|
|
7591
7620
|
apiUrl: resolvedBaseResource.apiUrl,
|
|
7592
7621
|
manualFields: hasManualFields ? buildFields(resource.fields) : void 0,
|
|
7593
7622
|
overrides: hasManualFields ? void 0 : fieldOverrides,
|
|
@@ -7611,30 +7640,42 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
|
|
|
7611
7640
|
const routingState = useRouting(resource.routing);
|
|
7612
7641
|
const { activeOperation, formData, handleFormDataChange, startCreate, startEdit, resetOperation } = useSmartCrudOperation(void 0, routingState);
|
|
7613
7642
|
const roles = useSmartCrudRoles();
|
|
7614
|
-
const
|
|
7643
|
+
const stableRoles = (0, react.useMemo)(() => roles ?? [], [roles]);
|
|
7644
|
+
const { gridFields, processedFields, computedValues } = useSmartCrudFields(fields, activeOperation, formData, stableRoles);
|
|
7615
7645
|
const formFields = (0, react.useMemo)(() => applyFormDetailFormFieldOverrides(processedFields, resolvedBaseResource.formDetail), [processedFields, resolvedBaseResource.formDetail]);
|
|
7616
7646
|
(0, _nubitio_core.useMercureSubscription)(resource.apiUrl, () => {
|
|
7617
7647
|
effectiveGridRef.current?.refresh();
|
|
7618
7648
|
}, resolvedBaseResource.mercure !== false);
|
|
7619
7649
|
const normalizedApiUrl = resolvedBaseResource.apiUrl.startsWith("/") ? resolvedBaseResource.apiUrl : `/${resolvedBaseResource.apiUrl}`;
|
|
7620
|
-
const resolvedResource = (0, react.useMemo)(() =>
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7650
|
+
const resolvedResource = (0, react.useMemo)(() => {
|
|
7651
|
+
const rowActions = resolvedBaseResource.rowActions ?? (workflow ? (row) => buildWorkflowRowActions(row, workflow, normalizedApiUrl, stableRoles, () => {
|
|
7652
|
+
effectiveGridRef.current?.refresh();
|
|
7653
|
+
}) : void 0);
|
|
7654
|
+
return {
|
|
7655
|
+
...resolvedBaseResource,
|
|
7656
|
+
...!hasManualFields ? { fields: gridFields } : {},
|
|
7657
|
+
apiUrl: normalizedApiUrl,
|
|
7658
|
+
fields: hasManualFields ? buildFields(resource.fields) : gridFields,
|
|
7659
|
+
formFields,
|
|
7660
|
+
formLayout: resolvedBaseResource.formLayout ?? inferredFormLayout,
|
|
7661
|
+
summaryFields: resolvedBaseResource.summaryFields ?? inferredSummaryFields,
|
|
7662
|
+
_supportedOperations: supportedOperations,
|
|
7663
|
+
rowActions
|
|
7664
|
+
};
|
|
7665
|
+
}, [
|
|
7666
|
+
effectiveGridRef,
|
|
7629
7667
|
fields,
|
|
7630
7668
|
gridFields,
|
|
7631
7669
|
hasManualFields,
|
|
7632
7670
|
inferredFormLayout,
|
|
7671
|
+
inferredSummaryFields,
|
|
7633
7672
|
normalizedApiUrl,
|
|
7634
7673
|
formFields,
|
|
7635
7674
|
resolvedBaseResource,
|
|
7636
7675
|
resource.fields,
|
|
7637
|
-
|
|
7676
|
+
stableRoles,
|
|
7677
|
+
supportedOperations,
|
|
7678
|
+
workflow
|
|
7638
7679
|
]);
|
|
7639
7680
|
if (!hasManualFields && isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CrudSkeleton, {});
|
|
7640
7681
|
if (!hasManualFields && error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CrudError, {
|
|
@@ -8460,24 +8501,6 @@ function ToolbarSelect({ id, label, icon = "ph-funnel", value, options, onChange
|
|
|
8460
8501
|
});
|
|
8461
8502
|
}
|
|
8462
8503
|
//#endregion
|
|
8463
|
-
//#region packages/crud/workflow/buildWorkflowRowActions.ts
|
|
8464
|
-
function buildWorkflowRowActions(row, workflow, apiUrl, roles, onDone) {
|
|
8465
|
-
if (!workflow) return [];
|
|
8466
|
-
const current = String(row[workflow.field] ?? "");
|
|
8467
|
-
return workflow.transitions.filter((transition) => transition.from.includes(current)).filter((transition) => !transition.roles?.length || transition.roles.some((role) => roles.includes(role))).map((transition) => ({
|
|
8468
|
-
text: transition.label ?? transition.name,
|
|
8469
|
-
onClick: async () => {
|
|
8470
|
-
const base = apiUrl.replace(/\/$/, "");
|
|
8471
|
-
const id = row.id;
|
|
8472
|
-
await fetch(`${base}/${id}/transition/${transition.name}`, {
|
|
8473
|
-
method: "POST",
|
|
8474
|
-
credentials: "include"
|
|
8475
|
-
});
|
|
8476
|
-
onDone?.();
|
|
8477
|
-
}
|
|
8478
|
-
}));
|
|
8479
|
-
}
|
|
8480
|
-
//#endregion
|
|
8481
8504
|
//#region packages/crud/adapter/RestAdapter.ts
|
|
8482
8505
|
/**
|
|
8483
8506
|
* Backend adapter for plain OpenAPI / REST backends.
|
|
@@ -8611,15 +8634,26 @@ function createRestResourceStore(dialect = {}) {
|
|
|
8611
8634
|
if (result === null) return {
|
|
8612
8635
|
data: [],
|
|
8613
8636
|
totalCount: 0,
|
|
8614
|
-
summary: null
|
|
8637
|
+
summary: null,
|
|
8638
|
+
gridSummary: null
|
|
8615
8639
|
};
|
|
8616
8640
|
const body = result.data;
|
|
8617
8641
|
const data = Array.isArray(body) ? body : body.items ?? body.data ?? [];
|
|
8618
8642
|
const headerTotal = Number(result.headers.get("x-total-count"));
|
|
8643
|
+
const totalCount = Array.isArray(body) ? Number.isFinite(headerTotal) && headerTotal > 0 ? headerTotal : body.length : body.total ?? body.totalCount ?? data.length;
|
|
8644
|
+
const gridSummaryHeader = result.headers.get("x-grid-summary");
|
|
8645
|
+
let gridSummary = null;
|
|
8646
|
+
if (gridSummaryHeader) try {
|
|
8647
|
+
const parsed = JSON.parse(gridSummaryHeader);
|
|
8648
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) gridSummary = parsed;
|
|
8649
|
+
} catch {
|
|
8650
|
+
gridSummary = null;
|
|
8651
|
+
}
|
|
8619
8652
|
return {
|
|
8620
8653
|
data,
|
|
8621
|
-
totalCount
|
|
8622
|
-
summary: null
|
|
8654
|
+
totalCount,
|
|
8655
|
+
summary: null,
|
|
8656
|
+
gridSummary
|
|
8623
8657
|
};
|
|
8624
8658
|
},
|
|
8625
8659
|
async byKey(key) {
|
package/dist/index.d.cts
CHANGED
|
@@ -1549,6 +1549,8 @@ interface DataGridViewOptions {
|
|
|
1549
1549
|
/** Per-row gate for the Delete action. Absent = allowed. */
|
|
1550
1550
|
canDeleteRow?: (row: DataRecord$1) => boolean;
|
|
1551
1551
|
summaryFields?: DataGridSummaryItem[];
|
|
1552
|
+
/** Server-side aggregates for the current filtered collection. */
|
|
1553
|
+
gridSummary?: Record<string, unknown> | null;
|
|
1552
1554
|
filter?: FilterRule[];
|
|
1553
1555
|
sort?: Array<{
|
|
1554
1556
|
selector: string;
|
|
@@ -1919,6 +1921,7 @@ interface ResourceSchemaResolution {
|
|
|
1919
1921
|
*/
|
|
1920
1922
|
formLayout?: FormLayout;
|
|
1921
1923
|
workflow?: WorkflowSchema;
|
|
1924
|
+
summaryFields?: SummaryItem[];
|
|
1922
1925
|
}
|
|
1923
1926
|
interface ResourceSchemaResolver {
|
|
1924
1927
|
useResourceSchema(request: ResourceSchemaRequest): ResourceSchemaResolution;
|
package/dist/index.d.mts
CHANGED
|
@@ -1549,6 +1549,8 @@ interface DataGridViewOptions {
|
|
|
1549
1549
|
/** Per-row gate for the Delete action. Absent = allowed. */
|
|
1550
1550
|
canDeleteRow?: (row: DataRecord$1) => boolean;
|
|
1551
1551
|
summaryFields?: DataGridSummaryItem[];
|
|
1552
|
+
/** Server-side aggregates for the current filtered collection. */
|
|
1553
|
+
gridSummary?: Record<string, unknown> | null;
|
|
1552
1554
|
filter?: FilterRule[];
|
|
1553
1555
|
sort?: Array<{
|
|
1554
1556
|
selector: string;
|
|
@@ -1919,6 +1921,7 @@ interface ResourceSchemaResolution {
|
|
|
1919
1921
|
*/
|
|
1920
1922
|
formLayout?: FormLayout;
|
|
1921
1923
|
workflow?: WorkflowSchema;
|
|
1924
|
+
summaryFields?: SummaryItem[];
|
|
1922
1925
|
}
|
|
1923
1926
|
interface ResourceSchemaResolver {
|
|
1924
1927
|
useResourceSchema(request: ResourceSchemaRequest): ResourceSchemaResolution;
|
package/dist/index.mjs
CHANGED
|
@@ -2086,7 +2086,7 @@ function buildToolbar(options, t, onAddClick, includeAddAction = true) {
|
|
|
2086
2086
|
showRefresh: options.toolbar?.showRefresh ?? true
|
|
2087
2087
|
};
|
|
2088
2088
|
}
|
|
2089
|
-
function SummaryFooter({ fields, hasCheckbox, hasDetail, hasRowActions, rows, summaryFields, footerRef, colWidths }) {
|
|
2089
|
+
function SummaryFooter({ fields, hasCheckbox, hasDetail, hasRowActions, rows, summaryFields, gridSummary, footerRef, colWidths }) {
|
|
2090
2090
|
if (!summaryFields?.length) return null;
|
|
2091
2091
|
const itemsByColumn = new Map(summaryFields.filter((item) => item.column).map((item) => [item.column, item]));
|
|
2092
2092
|
const unboundItems = summaryFields.filter((item) => !item.column);
|
|
@@ -2114,7 +2114,7 @@ function SummaryFooter({ fields, hasCheckbox, hasDetail, hasRowActions, rows, su
|
|
|
2114
2114
|
children: item.label
|
|
2115
2115
|
}), /* @__PURE__ */ jsx("span", {
|
|
2116
2116
|
className: "nb-datagrid__summary-value",
|
|
2117
|
-
children: resolveSummaryText(rows, item)
|
|
2117
|
+
children: item.column && gridSummary && item.column in gridSummary ? formatSummaryValue(gridSummary[item.column], item) : resolveSummaryText(rows, item)
|
|
2118
2118
|
})]
|
|
2119
2119
|
})
|
|
2120
2120
|
}, field.name);
|
|
@@ -2135,6 +2135,7 @@ const NativeDataGridView = forwardRef((options, ref) => {
|
|
|
2135
2135
|
const [rows, setRows] = useState([]);
|
|
2136
2136
|
const rowsRef = useRef([]);
|
|
2137
2137
|
const [totalCount, setTotalCount] = useState(0);
|
|
2138
|
+
const [gridSummary, setGridSummary] = useState(null);
|
|
2138
2139
|
const [selectedKeys, setSelectedKeys] = useState([]);
|
|
2139
2140
|
const [filters, setFilters] = useState({});
|
|
2140
2141
|
const [filterInputs, setFilterInputs] = useState({});
|
|
@@ -2316,6 +2317,7 @@ const NativeDataGridView = forwardRef((options, ref) => {
|
|
|
2316
2317
|
rowsRef.current = result.data;
|
|
2317
2318
|
setRows(result.data);
|
|
2318
2319
|
setTotalCount(result.totalCount);
|
|
2320
|
+
setGridSummary(result.gridSummary ?? null);
|
|
2319
2321
|
setIsGridLoading(false);
|
|
2320
2322
|
onContentReadyRef.current?.();
|
|
2321
2323
|
return result.data;
|
|
@@ -3111,6 +3113,7 @@ const NativeDataGridView = forwardRef((options, ref) => {
|
|
|
3111
3113
|
hasRowActions,
|
|
3112
3114
|
rows,
|
|
3113
3115
|
summaryFields: options.summaryFields,
|
|
3116
|
+
gridSummary,
|
|
3114
3117
|
footerRef: tfootRef,
|
|
3115
3118
|
colWidths: resolvedColWidths
|
|
3116
3119
|
})
|
|
@@ -3149,7 +3152,7 @@ const NativeDataGridView = forwardRef((options, ref) => {
|
|
|
3149
3152
|
children: item.label
|
|
3150
3153
|
}), /* @__PURE__ */ jsx("span", {
|
|
3151
3154
|
className: "nb-datagrid__summary-value",
|
|
3152
|
-
children: resolveSummaryText(rows, item)
|
|
3155
|
+
children: item.column && gridSummary && item.column in gridSummary ? formatSummaryValue(gridSummary[item.column], item) : resolveSummaryText(rows, item)
|
|
3153
3156
|
})]
|
|
3154
3157
|
}, index))
|
|
3155
3158
|
}),
|
|
@@ -7442,14 +7445,16 @@ function ResourceSchemaProvider({ children, resolver }) {
|
|
|
7442
7445
|
children
|
|
7443
7446
|
});
|
|
7444
7447
|
}
|
|
7445
|
-
function resolveWithRuntimeErrors(resolver, supportedOperations = [], formLayout) {
|
|
7448
|
+
function resolveWithRuntimeErrors(resolver, supportedOperations = [], formLayout, workflow, summaryFields) {
|
|
7446
7449
|
try {
|
|
7447
7450
|
return {
|
|
7448
7451
|
fields: resolver(),
|
|
7449
7452
|
isLoading: false,
|
|
7450
7453
|
error: void 0,
|
|
7451
7454
|
supportedOperations,
|
|
7452
|
-
formLayout
|
|
7455
|
+
formLayout,
|
|
7456
|
+
workflow,
|
|
7457
|
+
summaryFields
|
|
7453
7458
|
};
|
|
7454
7459
|
} catch (runtimeError) {
|
|
7455
7460
|
return {
|
|
@@ -7457,7 +7462,9 @@ function resolveWithRuntimeErrors(resolver, supportedOperations = [], formLayout
|
|
|
7457
7462
|
isLoading: false,
|
|
7458
7463
|
error: runtimeError instanceof Error ? runtimeError : new Error(String(runtimeError)),
|
|
7459
7464
|
supportedOperations,
|
|
7460
|
-
formLayout
|
|
7465
|
+
formLayout,
|
|
7466
|
+
workflow,
|
|
7467
|
+
summaryFields
|
|
7461
7468
|
};
|
|
7462
7469
|
}
|
|
7463
7470
|
}
|
|
@@ -7483,7 +7490,7 @@ function useResolvedResourceFields({ apiUrl, manualFields, overrides, fieldContr
|
|
|
7483
7490
|
baselineFields: baseline.fields,
|
|
7484
7491
|
contract: fieldContract,
|
|
7485
7492
|
legacyOverrides: fieldContract ? void 0 : overrides
|
|
7486
|
-
}), baseline.supportedOperations, baseline.formLayout);
|
|
7493
|
+
}), baseline.supportedOperations, baseline.formLayout, baseline.workflow, baseline.summaryFields);
|
|
7487
7494
|
}, [
|
|
7488
7495
|
baseline,
|
|
7489
7496
|
fieldContract,
|
|
@@ -7491,6 +7498,28 @@ function useResolvedResourceFields({ apiUrl, manualFields, overrides, fieldContr
|
|
|
7491
7498
|
]);
|
|
7492
7499
|
}
|
|
7493
7500
|
//#endregion
|
|
7501
|
+
//#region packages/crud/workflow/buildWorkflowRowActions.ts
|
|
7502
|
+
function buildWorkflowRowActions(row, workflow, apiUrl, roles, onDone) {
|
|
7503
|
+
if (!workflow) return [];
|
|
7504
|
+
const current = String(row[workflow.field] ?? "");
|
|
7505
|
+
return workflow.transitions.filter((transition) => transition.from.includes(current)).filter((transition) => !transition.roles?.length || transition.roles.some((role) => roles.includes(role))).map((transition) => ({
|
|
7506
|
+
text: transition.label ?? transition.name,
|
|
7507
|
+
onClick: async () => {
|
|
7508
|
+
const base = apiUrl.replace(/\/$/, "");
|
|
7509
|
+
const id = row.id;
|
|
7510
|
+
const response = await fetch(`${base}/${id}/transition/${transition.name}`, {
|
|
7511
|
+
method: "POST",
|
|
7512
|
+
credentials: "include"
|
|
7513
|
+
});
|
|
7514
|
+
if (!response.ok) {
|
|
7515
|
+
const detail = await response.text().catch(() => "");
|
|
7516
|
+
throw new Error(detail || `Transition "${transition.name}" failed (${response.status})`);
|
|
7517
|
+
}
|
|
7518
|
+
onDone?.();
|
|
7519
|
+
}
|
|
7520
|
+
}));
|
|
7521
|
+
}
|
|
7522
|
+
//#endregion
|
|
7494
7523
|
//#region packages/crud/crud/SmartCrudPage.tsx
|
|
7495
7524
|
function CrudSkeleton() {
|
|
7496
7525
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -7563,7 +7592,7 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
|
|
|
7563
7592
|
const effectiveGridRef = gridRef ?? internalGridRef;
|
|
7564
7593
|
const resolvedBaseResource = useMemo(() => resolveCrudResource(resource), [resource]);
|
|
7565
7594
|
const hasManualFields = !resource.fieldContract && Array.isArray(resource.fields) && resource.fields.length > 0;
|
|
7566
|
-
const { fields, isLoading, error, supportedOperations, formLayout: inferredFormLayout } = useResolvedResourceFields({
|
|
7595
|
+
const { fields, isLoading, error, supportedOperations, formLayout: inferredFormLayout, workflow, summaryFields: inferredSummaryFields } = useResolvedResourceFields({
|
|
7567
7596
|
apiUrl: resolvedBaseResource.apiUrl,
|
|
7568
7597
|
manualFields: hasManualFields ? buildFields(resource.fields) : void 0,
|
|
7569
7598
|
overrides: hasManualFields ? void 0 : fieldOverrides,
|
|
@@ -7587,30 +7616,42 @@ function SmartCrudPage({ resource, fieldOverrides, formRef, onSelectionChanged,
|
|
|
7587
7616
|
const routingState = useRouting(resource.routing);
|
|
7588
7617
|
const { activeOperation, formData, handleFormDataChange, startCreate, startEdit, resetOperation } = useSmartCrudOperation(void 0, routingState);
|
|
7589
7618
|
const roles = useSmartCrudRoles();
|
|
7590
|
-
const
|
|
7619
|
+
const stableRoles = useMemo(() => roles ?? [], [roles]);
|
|
7620
|
+
const { gridFields, processedFields, computedValues } = useSmartCrudFields(fields, activeOperation, formData, stableRoles);
|
|
7591
7621
|
const formFields = useMemo(() => applyFormDetailFormFieldOverrides(processedFields, resolvedBaseResource.formDetail), [processedFields, resolvedBaseResource.formDetail]);
|
|
7592
7622
|
useMercureSubscription(resource.apiUrl, () => {
|
|
7593
7623
|
effectiveGridRef.current?.refresh();
|
|
7594
7624
|
}, resolvedBaseResource.mercure !== false);
|
|
7595
7625
|
const normalizedApiUrl = resolvedBaseResource.apiUrl.startsWith("/") ? resolvedBaseResource.apiUrl : `/${resolvedBaseResource.apiUrl}`;
|
|
7596
|
-
const resolvedResource = useMemo(() =>
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7626
|
+
const resolvedResource = useMemo(() => {
|
|
7627
|
+
const rowActions = resolvedBaseResource.rowActions ?? (workflow ? (row) => buildWorkflowRowActions(row, workflow, normalizedApiUrl, stableRoles, () => {
|
|
7628
|
+
effectiveGridRef.current?.refresh();
|
|
7629
|
+
}) : void 0);
|
|
7630
|
+
return {
|
|
7631
|
+
...resolvedBaseResource,
|
|
7632
|
+
...!hasManualFields ? { fields: gridFields } : {},
|
|
7633
|
+
apiUrl: normalizedApiUrl,
|
|
7634
|
+
fields: hasManualFields ? buildFields(resource.fields) : gridFields,
|
|
7635
|
+
formFields,
|
|
7636
|
+
formLayout: resolvedBaseResource.formLayout ?? inferredFormLayout,
|
|
7637
|
+
summaryFields: resolvedBaseResource.summaryFields ?? inferredSummaryFields,
|
|
7638
|
+
_supportedOperations: supportedOperations,
|
|
7639
|
+
rowActions
|
|
7640
|
+
};
|
|
7641
|
+
}, [
|
|
7642
|
+
effectiveGridRef,
|
|
7605
7643
|
fields,
|
|
7606
7644
|
gridFields,
|
|
7607
7645
|
hasManualFields,
|
|
7608
7646
|
inferredFormLayout,
|
|
7647
|
+
inferredSummaryFields,
|
|
7609
7648
|
normalizedApiUrl,
|
|
7610
7649
|
formFields,
|
|
7611
7650
|
resolvedBaseResource,
|
|
7612
7651
|
resource.fields,
|
|
7613
|
-
|
|
7652
|
+
stableRoles,
|
|
7653
|
+
supportedOperations,
|
|
7654
|
+
workflow
|
|
7614
7655
|
]);
|
|
7615
7656
|
if (!hasManualFields && isLoading) return /* @__PURE__ */ jsx(CrudSkeleton, {});
|
|
7616
7657
|
if (!hasManualFields && error) return /* @__PURE__ */ jsx(CrudError, {
|
|
@@ -8436,24 +8477,6 @@ function ToolbarSelect({ id, label, icon = "ph-funnel", value, options, onChange
|
|
|
8436
8477
|
});
|
|
8437
8478
|
}
|
|
8438
8479
|
//#endregion
|
|
8439
|
-
//#region packages/crud/workflow/buildWorkflowRowActions.ts
|
|
8440
|
-
function buildWorkflowRowActions(row, workflow, apiUrl, roles, onDone) {
|
|
8441
|
-
if (!workflow) return [];
|
|
8442
|
-
const current = String(row[workflow.field] ?? "");
|
|
8443
|
-
return workflow.transitions.filter((transition) => transition.from.includes(current)).filter((transition) => !transition.roles?.length || transition.roles.some((role) => roles.includes(role))).map((transition) => ({
|
|
8444
|
-
text: transition.label ?? transition.name,
|
|
8445
|
-
onClick: async () => {
|
|
8446
|
-
const base = apiUrl.replace(/\/$/, "");
|
|
8447
|
-
const id = row.id;
|
|
8448
|
-
await fetch(`${base}/${id}/transition/${transition.name}`, {
|
|
8449
|
-
method: "POST",
|
|
8450
|
-
credentials: "include"
|
|
8451
|
-
});
|
|
8452
|
-
onDone?.();
|
|
8453
|
-
}
|
|
8454
|
-
}));
|
|
8455
|
-
}
|
|
8456
|
-
//#endregion
|
|
8457
8480
|
//#region packages/crud/adapter/RestAdapter.ts
|
|
8458
8481
|
/**
|
|
8459
8482
|
* Backend adapter for plain OpenAPI / REST backends.
|
|
@@ -8587,15 +8610,26 @@ function createRestResourceStore(dialect = {}) {
|
|
|
8587
8610
|
if (result === null) return {
|
|
8588
8611
|
data: [],
|
|
8589
8612
|
totalCount: 0,
|
|
8590
|
-
summary: null
|
|
8613
|
+
summary: null,
|
|
8614
|
+
gridSummary: null
|
|
8591
8615
|
};
|
|
8592
8616
|
const body = result.data;
|
|
8593
8617
|
const data = Array.isArray(body) ? body : body.items ?? body.data ?? [];
|
|
8594
8618
|
const headerTotal = Number(result.headers.get("x-total-count"));
|
|
8619
|
+
const totalCount = Array.isArray(body) ? Number.isFinite(headerTotal) && headerTotal > 0 ? headerTotal : body.length : body.total ?? body.totalCount ?? data.length;
|
|
8620
|
+
const gridSummaryHeader = result.headers.get("x-grid-summary");
|
|
8621
|
+
let gridSummary = null;
|
|
8622
|
+
if (gridSummaryHeader) try {
|
|
8623
|
+
const parsed = JSON.parse(gridSummaryHeader);
|
|
8624
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) gridSummary = parsed;
|
|
8625
|
+
} catch {
|
|
8626
|
+
gridSummary = null;
|
|
8627
|
+
}
|
|
8595
8628
|
return {
|
|
8596
8629
|
data,
|
|
8597
|
-
totalCount
|
|
8598
|
-
summary: null
|
|
8630
|
+
totalCount,
|
|
8631
|
+
summary: null,
|
|
8632
|
+
gridSummary
|
|
8599
8633
|
};
|
|
8600
8634
|
},
|
|
8601
8635
|
async byKey(key) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nubitio/crud",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Declarative CRUD engine with field DSL, forms, datagrids, RBAC, conditional logic and pluggable adapters (Hydra/REST).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"react-dom": "^19.0.0",
|
|
57
57
|
"react-i18next": "^14.0.0",
|
|
58
58
|
"react-router-dom": "^6.0.0",
|
|
59
|
-
"@nubitio/core": "^0.5.
|
|
60
|
-
"@nubitio/ui": "^0.5.
|
|
59
|
+
"@nubitio/core": "^0.5.22",
|
|
60
|
+
"@nubitio/ui": "^0.5.22"
|
|
61
61
|
}
|
|
62
62
|
}
|