@nubitio/crud 0.5.0 → 0.5.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/dist/index.cjs +227 -157
- package/dist/index.d.cts +23 -4
- package/dist/index.d.mts +23 -4
- package/dist/index.mjs +228 -158
- package/dist/style.css +155 -2
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -5310,12 +5310,22 @@ function useCrudPage(resource, externalFormRef) {
|
|
|
5310
5310
|
}
|
|
5311
5311
|
//#endregion
|
|
5312
5312
|
//#region packages/crud/crud/AuditTrailPanel.tsx
|
|
5313
|
-
const
|
|
5314
|
-
create: "
|
|
5315
|
-
update: "
|
|
5316
|
-
delete: "
|
|
5313
|
+
const ACTION_BADGE = {
|
|
5314
|
+
create: "success",
|
|
5315
|
+
update: "info",
|
|
5316
|
+
delete: "danger"
|
|
5317
5317
|
};
|
|
5318
|
-
function
|
|
5318
|
+
function formatAuditValue(value, yesLabel, noLabel) {
|
|
5319
|
+
if (value == null || value === "") return "—";
|
|
5320
|
+
if (typeof value === "boolean") return value ? yesLabel : noLabel;
|
|
5321
|
+
if (typeof value === "object") try {
|
|
5322
|
+
return JSON.stringify(value);
|
|
5323
|
+
} catch {
|
|
5324
|
+
return String(value);
|
|
5325
|
+
}
|
|
5326
|
+
return String(value);
|
|
5327
|
+
}
|
|
5328
|
+
function DefaultEntryRenderer({ entry, resolveFieldLabel, yesLabel, noLabel }) {
|
|
5319
5329
|
const { t } = (0, _nubitio_core.useCoreTranslation)();
|
|
5320
5330
|
const date = new Date(entry.timestamp);
|
|
5321
5331
|
const formatted = Number.isNaN(date.getTime()) ? entry.timestamp : date.toLocaleString((0, _nubitio_core.getCoreLocale)(), { timeZone: (0, _nubitio_core.getCoreTimezone)() });
|
|
@@ -5326,175 +5336,191 @@ function DefaultEntryRenderer({ entry }) {
|
|
|
5326
5336
|
};
|
|
5327
5337
|
const changeKeys = Object.keys(entry.changes);
|
|
5328
5338
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
},
|
|
5334
|
-
|
|
5335
|
-
style: {
|
|
5336
|
-
display: "flex",
|
|
5337
|
-
alignItems: "center",
|
|
5338
|
-
gap: 8,
|
|
5339
|
-
marginBottom: 4
|
|
5340
|
-
},
|
|
5339
|
+
className: `nb-audit-trail__entry nb-audit-trail__entry--${entry.action}`,
|
|
5340
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5341
|
+
className: "nb-audit-trail__marker",
|
|
5342
|
+
"aria-hidden": "true"
|
|
5343
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5344
|
+
className: "nb-audit-trail__meta",
|
|
5341
5345
|
children: [
|
|
5342
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
color: "#757575"
|
|
5346
|
-
},
|
|
5346
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
|
|
5347
|
+
className: "nb-audit-trail__timestamp",
|
|
5348
|
+
dateTime: entry.timestamp,
|
|
5347
5349
|
children: formatted
|
|
5348
5350
|
}),
|
|
5349
5351
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5350
|
-
|
|
5351
|
-
fontSize: 12,
|
|
5352
|
-
color: "#424242"
|
|
5353
|
-
},
|
|
5352
|
+
className: "nb-audit-trail__user",
|
|
5354
5353
|
children: entry.user
|
|
5355
5354
|
}),
|
|
5356
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
padding: "1px 6px",
|
|
5361
|
-
borderRadius: 4,
|
|
5362
|
-
backgroundColor: ACTION_COLORS[entry.action],
|
|
5363
|
-
color: "#fff"
|
|
5364
|
-
},
|
|
5355
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Badge, {
|
|
5356
|
+
variant: ACTION_BADGE[entry.action],
|
|
5357
|
+
size: "sm",
|
|
5358
|
+
pill: true,
|
|
5365
5359
|
children: actionLabels[entry.action]
|
|
5366
5360
|
})
|
|
5367
5361
|
]
|
|
5368
5362
|
}), changeKeys.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
5369
|
-
|
|
5370
|
-
margin: "4px 0 0 0",
|
|
5371
|
-
padding: "0 0 0 16px"
|
|
5372
|
-
},
|
|
5363
|
+
className: "nb-audit-trail__changes",
|
|
5373
5364
|
children: changeKeys.map((field) => {
|
|
5374
5365
|
const { before, after } = entry.changes[field];
|
|
5375
5366
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5367
|
+
className: "nb-audit-trail__change",
|
|
5368
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5369
|
+
className: "nb-audit-trail__field",
|
|
5370
|
+
children: resolveFieldLabel(field)
|
|
5371
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5372
|
+
className: "nb-audit-trail__diff",
|
|
5373
|
+
children: [
|
|
5374
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5375
|
+
className: "nb-audit-trail__value nb-audit-trail__value--before",
|
|
5376
|
+
children: formatAuditValue(before, yesLabel, noLabel)
|
|
5377
|
+
}),
|
|
5378
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5379
|
+
className: "nb-audit-trail__arrow",
|
|
5380
|
+
"aria-hidden": "true",
|
|
5381
|
+
children: "→"
|
|
5382
|
+
}),
|
|
5383
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5384
|
+
className: "nb-audit-trail__value nb-audit-trail__value--after",
|
|
5385
|
+
children: formatAuditValue(after, yesLabel, noLabel)
|
|
5386
|
+
})
|
|
5387
|
+
]
|
|
5388
|
+
})]
|
|
5393
5389
|
}, field);
|
|
5394
5390
|
})
|
|
5395
|
-
})]
|
|
5391
|
+
})] })]
|
|
5392
|
+
});
|
|
5393
|
+
}
|
|
5394
|
+
function AuditTrailSkeleton() {
|
|
5395
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5396
|
+
className: "nb-audit-trail__skeleton",
|
|
5397
|
+
"aria-busy": "true",
|
|
5398
|
+
children: [
|
|
5399
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Skeleton, {
|
|
5400
|
+
variant: "rect",
|
|
5401
|
+
height: 72
|
|
5402
|
+
}),
|
|
5403
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Skeleton, {
|
|
5404
|
+
variant: "rect",
|
|
5405
|
+
height: 72
|
|
5406
|
+
}),
|
|
5407
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Skeleton, {
|
|
5408
|
+
variant: "rect",
|
|
5409
|
+
height: 72
|
|
5410
|
+
})
|
|
5411
|
+
]
|
|
5396
5412
|
});
|
|
5397
5413
|
}
|
|
5398
|
-
function AuditTrailPanel({ url, renderEntry, visible, onClose }) {
|
|
5414
|
+
function AuditTrailPanel({ url, renderEntry, visible, onClose, recordSubtitle, resolveFieldLabel, drawerSize = "sm" }) {
|
|
5399
5415
|
const { t } = (0, _nubitio_core.useCoreTranslation)();
|
|
5400
5416
|
const httpClient = (0, _nubitio_core.useCoreHttpClient)();
|
|
5401
5417
|
const [fetchState, setFetchState] = (0, react.useState)({ status: "idle" });
|
|
5402
|
-
(
|
|
5403
|
-
|
|
5418
|
+
const yesLabel = t("common.yes");
|
|
5419
|
+
const noLabel = t("common.no");
|
|
5420
|
+
const loadEntries = (0, react.useCallback)(() => {
|
|
5421
|
+
if (url === null) {
|
|
5404
5422
|
setFetchState({ status: "idle" });
|
|
5405
5423
|
return;
|
|
5406
5424
|
}
|
|
5407
|
-
let cancelled = false;
|
|
5408
5425
|
setFetchState({ status: "loading" });
|
|
5409
5426
|
httpClient.get(url).then((response) => {
|
|
5410
|
-
|
|
5427
|
+
setFetchState({
|
|
5411
5428
|
status: "success",
|
|
5412
5429
|
entries: response.data
|
|
5413
5430
|
});
|
|
5414
5431
|
}).catch(() => {
|
|
5415
|
-
|
|
5432
|
+
setFetchState({ status: "error" });
|
|
5416
5433
|
});
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
]);
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
})
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
children:
|
|
5488
|
-
})
|
|
5489
|
-
style: {
|
|
5490
|
-
margin: 0,
|
|
5491
|
-
padding: 0
|
|
5492
|
-
},
|
|
5493
|
-
children: fetchState.entries.map((entry) => renderEntry ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderEntry(entry) }, String(entry.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultEntryRenderer, { entry }, String(entry.id)))
|
|
5494
|
-
}) : null]
|
|
5434
|
+
}, [httpClient, url]);
|
|
5435
|
+
(0, react.useEffect)(() => {
|
|
5436
|
+
if (!visible) {
|
|
5437
|
+
setFetchState({ status: "idle" });
|
|
5438
|
+
return;
|
|
5439
|
+
}
|
|
5440
|
+
loadEntries();
|
|
5441
|
+
}, [loadEntries, visible]);
|
|
5442
|
+
const drawerTitle = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: t("auditTrail.title") }), recordSubtitle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5443
|
+
className: "nb-audit-trail__subtitle",
|
|
5444
|
+
children: recordSubtitle
|
|
5445
|
+
})] });
|
|
5446
|
+
const body = (() => {
|
|
5447
|
+
if (url === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.EmptyState, {
|
|
5448
|
+
fill: true,
|
|
5449
|
+
icon: "cursor-click",
|
|
5450
|
+
title: t("auditTrail.selectRecord"),
|
|
5451
|
+
description: t("auditTrail.selectRecordHint"),
|
|
5452
|
+
size: "sm"
|
|
5453
|
+
});
|
|
5454
|
+
if (fetchState.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuditTrailSkeleton, {});
|
|
5455
|
+
if (fetchState.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5456
|
+
className: "nb-audit-trail__error",
|
|
5457
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.EmptyState, {
|
|
5458
|
+
fill: true,
|
|
5459
|
+
variant: "danger",
|
|
5460
|
+
icon: "warning-circle",
|
|
5461
|
+
title: t("auditTrail.error"),
|
|
5462
|
+
size: "sm",
|
|
5463
|
+
action: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
5464
|
+
variant: "secondary",
|
|
5465
|
+
size: "sm",
|
|
5466
|
+
onClick: loadEntries,
|
|
5467
|
+
children: t("auditTrail.retry")
|
|
5468
|
+
})
|
|
5469
|
+
})
|
|
5470
|
+
});
|
|
5471
|
+
if (fetchState.status === "success" && fetchState.entries.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.EmptyState, {
|
|
5472
|
+
fill: true,
|
|
5473
|
+
icon: "clock-counter-clockwise",
|
|
5474
|
+
title: t("auditTrail.empty"),
|
|
5475
|
+
description: t("auditTrail.emptyHint"),
|
|
5476
|
+
size: "sm"
|
|
5477
|
+
});
|
|
5478
|
+
if (fetchState.status === "success") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
5479
|
+
className: "nb-audit-trail__timeline",
|
|
5480
|
+
children: fetchState.entries.map((entry) => renderEntry ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
5481
|
+
className: "nb-audit-trail__entry",
|
|
5482
|
+
children: renderEntry(entry)
|
|
5483
|
+
}, String(entry.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultEntryRenderer, {
|
|
5484
|
+
entry,
|
|
5485
|
+
resolveFieldLabel,
|
|
5486
|
+
yesLabel,
|
|
5487
|
+
noLabel
|
|
5488
|
+
}, String(entry.id)))
|
|
5489
|
+
});
|
|
5490
|
+
return null;
|
|
5491
|
+
})();
|
|
5492
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Drawer, {
|
|
5493
|
+
isOpen: visible,
|
|
5494
|
+
onClose,
|
|
5495
|
+
title: drawerTitle,
|
|
5496
|
+
width: resolveDrawerWidth({ drawerSize }),
|
|
5497
|
+
side: "right",
|
|
5498
|
+
scrim: "subtle",
|
|
5499
|
+
closeLabel: t("auditTrail.closeButton"),
|
|
5500
|
+
"aria-label": t("auditTrail.title"),
|
|
5501
|
+
className: "nb-audit-trail-drawer",
|
|
5502
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5503
|
+
className: "nb-audit-trail",
|
|
5504
|
+
children: body
|
|
5505
|
+
})
|
|
5495
5506
|
});
|
|
5496
5507
|
}
|
|
5497
5508
|
//#endregion
|
|
5509
|
+
//#region packages/crud/crud/AuditTrail.ts
|
|
5510
|
+
function createAuditFieldLabelResolver(config, fields) {
|
|
5511
|
+
const fieldLabelByName = new Map(fields.map((field) => [field.name, field.label || field.name]));
|
|
5512
|
+
return (field) => {
|
|
5513
|
+
const fromConfig = config?.fieldLabels;
|
|
5514
|
+
if (fromConfig) {
|
|
5515
|
+
if (typeof fromConfig === "function") {
|
|
5516
|
+
const resolved = fromConfig(field);
|
|
5517
|
+
if (resolved) return resolved;
|
|
5518
|
+
} else if (fromConfig[field]) return fromConfig[field];
|
|
5519
|
+
}
|
|
5520
|
+
return fieldLabelByName.get(field) ?? field;
|
|
5521
|
+
};
|
|
5522
|
+
}
|
|
5523
|
+
//#endregion
|
|
5498
5524
|
//#region packages/crud/crud/dialogStore.ts
|
|
5499
5525
|
function initialDialogState() {
|
|
5500
5526
|
return {
|
|
@@ -5842,13 +5868,30 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5842
5868
|
if (hasAuditTrail) {
|
|
5843
5869
|
const first = rows[0];
|
|
5844
5870
|
if (first != null) setSelectedRowId(first["id"] ?? first["ID"] ?? null);
|
|
5871
|
+
else setSelectedRowId(null);
|
|
5845
5872
|
}
|
|
5846
5873
|
};
|
|
5874
|
+
const selectedRow = selectedRows[0] ?? null;
|
|
5847
5875
|
const auditUrl = (0, react.useMemo)(() => {
|
|
5848
5876
|
if (!resolvedResource.auditTrail?.enabled || selectedRowId == null) return null;
|
|
5849
5877
|
const { apiUrl } = resolvedResource.auditTrail;
|
|
5850
5878
|
return typeof apiUrl === "function" ? apiUrl(selectedRowId) : `${apiUrl}${selectedRowId}`;
|
|
5851
5879
|
}, [resolvedResource.auditTrail, selectedRowId]);
|
|
5880
|
+
const resolveAuditFieldLabel = (0, react.useMemo)(() => createAuditFieldLabelResolver(resolvedResource.auditTrail, routeAwareGridFields), [resolvedResource.auditTrail, routeAwareGridFields]);
|
|
5881
|
+
const auditRecordSubtitle = (0, react.useMemo)(() => {
|
|
5882
|
+
if (!resolvedResource.auditTrail?.recordSubtitle || selectedRow == null) return void 0;
|
|
5883
|
+
return resolvedResource.auditTrail.recordSubtitle(selectedRow);
|
|
5884
|
+
}, [resolvedResource.auditTrail, selectedRow]);
|
|
5885
|
+
const openAuditTrail = (0, react.useCallback)((row) => {
|
|
5886
|
+
if (row != null) {
|
|
5887
|
+
const id = row["id"] ?? row["ID"] ?? null;
|
|
5888
|
+
if (id != null) {
|
|
5889
|
+
setSelectedRowId(id);
|
|
5890
|
+
setSelectedRows([row]);
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
setAuditOpen(true);
|
|
5894
|
+
}, []);
|
|
5852
5895
|
const executeBulkAction = (0, react.useCallback)(async (action) => {
|
|
5853
5896
|
await action.onAction(selectionState.selectedIds);
|
|
5854
5897
|
selectionState.clearSelection();
|
|
@@ -5862,7 +5905,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5862
5905
|
else executeBulkAction(action);
|
|
5863
5906
|
}, [executeBulkAction]);
|
|
5864
5907
|
const toolbar = (0, react.useMemo)(() => {
|
|
5865
|
-
|
|
5908
|
+
const base = resolveResourceToolbar(resolvedResource, {
|
|
5866
5909
|
resource: resolvedResource,
|
|
5867
5910
|
selectedRow: selectedRows[0],
|
|
5868
5911
|
selectedRows,
|
|
@@ -5870,14 +5913,48 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5870
5913
|
formRef,
|
|
5871
5914
|
events,
|
|
5872
5915
|
emit
|
|
5873
|
-
});
|
|
5916
|
+
}) ?? {};
|
|
5917
|
+
if (!hasAuditTrail) return base;
|
|
5918
|
+
return {
|
|
5919
|
+
...base,
|
|
5920
|
+
utility: [...base.utility ?? [], {
|
|
5921
|
+
key: "audit-trail",
|
|
5922
|
+
text: t("crudPage.auditTrailButton"),
|
|
5923
|
+
icon: "ph-clock-counter-clockwise",
|
|
5924
|
+
hint: t("auditTrail.toolbarHint"),
|
|
5925
|
+
disabled: selectedRowId == null,
|
|
5926
|
+
onClick: () => openAuditTrail()
|
|
5927
|
+
}]
|
|
5928
|
+
};
|
|
5874
5929
|
}, [
|
|
5875
5930
|
emit,
|
|
5876
5931
|
events,
|
|
5877
5932
|
formRef,
|
|
5878
5933
|
gridRef,
|
|
5934
|
+
hasAuditTrail,
|
|
5935
|
+
openAuditTrail,
|
|
5879
5936
|
resolvedResource,
|
|
5880
|
-
|
|
5937
|
+
selectedRowId,
|
|
5938
|
+
selectedRows,
|
|
5939
|
+
t
|
|
5940
|
+
]);
|
|
5941
|
+
const rowActions = (0, react.useMemo)(() => {
|
|
5942
|
+
const base = resolvedResource.rowActions;
|
|
5943
|
+
if (!hasAuditTrail || resolvedResource.auditTrail?.rowAction === false) return base;
|
|
5944
|
+
const auditAction = (row) => ({
|
|
5945
|
+
key: "audit-trail",
|
|
5946
|
+
text: t("auditTrail.rowAction"),
|
|
5947
|
+
icon: "ph-clock-counter-clockwise",
|
|
5948
|
+
onClick: () => openAuditTrail(row)
|
|
5949
|
+
});
|
|
5950
|
+
if (typeof base === "function") return (row) => [...base(row), auditAction(row)];
|
|
5951
|
+
return (row) => [...base ?? [], auditAction(row)];
|
|
5952
|
+
}, [
|
|
5953
|
+
hasAuditTrail,
|
|
5954
|
+
openAuditTrail,
|
|
5955
|
+
resolvedResource.auditTrail?.rowAction,
|
|
5956
|
+
resolvedResource.rowActions,
|
|
5957
|
+
t
|
|
5881
5958
|
]);
|
|
5882
5959
|
/** Preset selector rendered as a toolbar slot via `beforeToolbar`. */
|
|
5883
5960
|
const renderPresetSelector = hasPresets ? () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColumnPresetSelector, {
|
|
@@ -5890,7 +5967,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5890
5967
|
}) : void 0;
|
|
5891
5968
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5892
5969
|
id: "wrapper",
|
|
5893
|
-
className: [
|
|
5970
|
+
className: [viewMode.mode === "page" && dialogIsOpen && "wrapper--with-page"].filter(Boolean).join(" ") || void 0,
|
|
5894
5971
|
children: [
|
|
5895
5972
|
(resolvedResource.bulkActions?.length ?? 0) > 0 && selectionState.hasSelection && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5896
5973
|
className: "nb-bulk-toolbar",
|
|
@@ -5907,16 +5984,6 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5907
5984
|
children: action.label
|
|
5908
5985
|
}, action.key))]
|
|
5909
5986
|
}),
|
|
5910
|
-
hasAuditTrail && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5911
|
-
className: "nb-audit-trail-toolbar",
|
|
5912
|
-
style: { marginBottom: 8 },
|
|
5913
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5914
|
-
type: "button",
|
|
5915
|
-
onClick: () => setAuditOpen((prev) => !prev),
|
|
5916
|
-
"aria-pressed": auditOpen,
|
|
5917
|
-
children: t("crudPage.auditTrailButton")
|
|
5918
|
-
})
|
|
5919
|
-
}),
|
|
5920
5987
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NativeDataGridView, {
|
|
5921
5988
|
ref: gridRef,
|
|
5922
5989
|
id: resolvedResource.id,
|
|
@@ -5941,7 +6008,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5941
6008
|
mode: resolvedResource.mode,
|
|
5942
6009
|
stateStoringEnabled: resolvedResource.stateStoring,
|
|
5943
6010
|
toolbar,
|
|
5944
|
-
rowActions
|
|
6011
|
+
rowActions,
|
|
5945
6012
|
onAdd: requestNew,
|
|
5946
6013
|
onEdit: requestEdit,
|
|
5947
6014
|
onView: requestView,
|
|
@@ -6014,7 +6081,10 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
6014
6081
|
url: auditUrl,
|
|
6015
6082
|
renderEntry: resolvedResource.auditTrail.renderEntry,
|
|
6016
6083
|
visible: auditOpen,
|
|
6017
|
-
onClose: () => setAuditOpen(false)
|
|
6084
|
+
onClose: () => setAuditOpen(false),
|
|
6085
|
+
recordSubtitle: auditRecordSubtitle,
|
|
6086
|
+
resolveFieldLabel: resolveAuditFieldLabel,
|
|
6087
|
+
drawerSize: resolvedResource.auditTrail?.drawerSize
|
|
6018
6088
|
}),
|
|
6019
6089
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.ConfirmDialog, {
|
|
6020
6090
|
open: confirmState.open,
|
package/dist/index.d.cts
CHANGED
|
@@ -495,10 +495,23 @@ interface AuditEntry {
|
|
|
495
495
|
after: unknown;
|
|
496
496
|
}>;
|
|
497
497
|
}
|
|
498
|
-
|
|
498
|
+
type AuditFieldLabelResolver = (field: string) => string;
|
|
499
|
+
interface AuditTrailConfig<T extends DataRecord$1 = DataRecord$1> {
|
|
499
500
|
enabled: boolean;
|
|
500
501
|
apiUrl: string | ((id: string | number) => string);
|
|
502
|
+
/** Custom entry renderer. Receives resolved field labels via context in the panel. */
|
|
501
503
|
renderEntry?: (entry: AuditEntry) => ReactNode;
|
|
504
|
+
/**
|
|
505
|
+
* Maps API field keys to display labels. When omitted, CrudPage falls back to
|
|
506
|
+
* matching grid field labels from the active resource schema.
|
|
507
|
+
*/
|
|
508
|
+
fieldLabels?: Record<string, string> | ((field: string) => string | undefined);
|
|
509
|
+
/** One-line context shown under the drawer title for the selected row. */
|
|
510
|
+
recordSubtitle?: (row: T) => string | undefined;
|
|
511
|
+
/** Drawer width token. Default `sm` (480px) — read-only secondary panel. */
|
|
512
|
+
drawerSize?: CrudDrawerSize;
|
|
513
|
+
/** Adds a row-menu action that opens the audit drawer. Default true. */
|
|
514
|
+
rowAction?: boolean;
|
|
502
515
|
}
|
|
503
516
|
//#endregion
|
|
504
517
|
//#region packages/crud/crud/fieldOperationSemantics.d.ts
|
|
@@ -1149,13 +1162,19 @@ interface AuditTrailPanelProps {
|
|
|
1149
1162
|
renderEntry?: (entry: AuditEntry) => ReactNode;
|
|
1150
1163
|
visible: boolean;
|
|
1151
1164
|
onClose: () => void;
|
|
1165
|
+
recordSubtitle?: string;
|
|
1166
|
+
resolveFieldLabel: AuditFieldLabelResolver;
|
|
1167
|
+
drawerSize?: CrudDrawerSize;
|
|
1152
1168
|
}
|
|
1153
1169
|
declare function AuditTrailPanel({
|
|
1154
1170
|
url,
|
|
1155
1171
|
renderEntry,
|
|
1156
1172
|
visible,
|
|
1157
|
-
onClose
|
|
1158
|
-
|
|
1173
|
+
onClose,
|
|
1174
|
+
recordSubtitle,
|
|
1175
|
+
resolveFieldLabel,
|
|
1176
|
+
drawerSize
|
|
1177
|
+
}: AuditTrailPanelProps): React.JSX.Element;
|
|
1159
1178
|
//#endregion
|
|
1160
1179
|
//#region packages/crud/field/FieldBuilders.d.ts
|
|
1161
1180
|
/**
|
|
@@ -1936,4 +1955,4 @@ declare function ResourceStoreProvider({
|
|
|
1936
1955
|
}: ResourceStoreProviderProps): React.JSX.Element;
|
|
1937
1956
|
declare function useResourceStoreFactory(): ResourceStoreFactory;
|
|
1938
1957
|
//#endregion
|
|
1939
|
-
export { type AuditEntry, type AuditTrailConfig, AuditTrailPanel, type AuditTrailPanelProps, type BackendAdapter, type BulkAction, type ColSpan, type ColumnPreset, ColumnPresetSelector, type ColumnPresetState, CrudDialogShell, CrudDrawerShell, type CrudDrawerSize, type CrudDrawerViewEvents, type CrudDrawerViewOptions, CrudFormShell, type CrudFormShellProps, CrudPage, CrudPageShell, type CrudPageViewEvents, type CrudPageViewOptions, type CrudViewMode, type CrudViewModeConfig, DATA_GRID_EVENTS, DEFAULT_DRAWER_SIZE, DEFAULT_DRAWER_WIDTH, DRAWER_WIDTHS, type DataGridSelectionChangedEvent, type DataGridSummaryItem, NativeDataGridView as DataGridView, type DataGridViewOptions, type DataRecord, type DetailSummaryOptions, CrudDialogView as DialogView, type DrawerSize, CrudDrawerView as DrawerView, type EnumOption, FORM_EVENTS, type Field, FieldBuilder, type FieldColSpanContext, type FieldDef, type FieldInput, type FieldOverride, FieldType, type FilterRule, type FormHandle, type FormLayout, type FormLayoutHint, type FormOnChangeFn, type FormPresentationContext, type FormPresentationMode, type FormSection, type FormTab, NativeFormView as FormView, type FormViewOptions, type FormatterFn, type GridCellContext, type GridData, type GridHandle, type GridOnChangeFn, HydraAdapter, type ItemFormatterFn, type LoadOption, type OnChangeFn, CrudPageView as PageView, type ResolvedViewMode, type ResourceConfig, type ResourceEmptyState, type ResourceFilterDescriptor, type ResourceFilterRule, type ResourceFormDetail, type ResourceGridDetail, type ResourceLoadOption, type ResourceLoadOptions, type ResourcePermissions, type ResourceRouting, type ResourceRowActions, ResourceSchemaProvider, type ResourceSchemaProviderProps, type ResourceSchemaResolution, type ResourceSchemaResolver, type ResourceSortDescriptor, type ResourceStore, type ResourceStoreFactory, type ResourceStoreOptions, ResourceStoreProvider, type ResourceStoreProviderProps, type ResourceToolbar, type ResourceToolbarAction, type ResourceToolbarActionVariant, type ResourceToolbarContext, type ResourceToolbarItems, RestAdapter, type SmartCrudFieldContract, type SmartCrudFieldOperation, type SmartCrudFieldPatch, type SmartCrudHydraFieldContract, type SmartCrudHydraFieldDirective, type SmartCrudManualField, type SmartCrudManualFieldContract, type SmartCrudOperation, SmartCrudPage, SmartCrudRolesProvider, type SummaryCalculateContext, type SummaryFormat, type SummaryItem, type SummaryTextContext, type SummaryType, ToolbarSelect, type ToolbarSelectOption, type ToolbarSelectProps, type ValidationRule, buildFieldColSpanContext, buildFields, checkboxField, computeSummaryValue, createCrudEvents, crudRoute, currencyField, dateField, datetimeField, defineFieldContract, defineFields, defineResource, entityField, enumField, fileField, formatSummaryValue, identityField, imageField, isLongTextField, isShortField, noneField, numberField, parseDrawerWidthPx, passwordField, resolveDrawerLayoutBucket, resolveDrawerSize, resolveDrawerWidth, resolveFieldColSpan, resolveFieldsColSpans, resolveSummaryText, resolveViewMode, selectField, switchField, textField, textareaField, useColumnPreset, useResourceStoreFactory, useSmartCrudRoles, validateFieldContract };
|
|
1958
|
+
export { type AuditEntry, type AuditFieldLabelResolver, type AuditTrailConfig, AuditTrailPanel, type AuditTrailPanelProps, type BackendAdapter, type BulkAction, type ColSpan, type ColumnPreset, ColumnPresetSelector, type ColumnPresetState, CrudDialogShell, CrudDrawerShell, type CrudDrawerSize, type CrudDrawerViewEvents, type CrudDrawerViewOptions, CrudFormShell, type CrudFormShellProps, CrudPage, CrudPageShell, type CrudPageViewEvents, type CrudPageViewOptions, type CrudViewMode, type CrudViewModeConfig, DATA_GRID_EVENTS, DEFAULT_DRAWER_SIZE, DEFAULT_DRAWER_WIDTH, DRAWER_WIDTHS, type DataGridSelectionChangedEvent, type DataGridSummaryItem, NativeDataGridView as DataGridView, type DataGridViewOptions, type DataRecord, type DetailSummaryOptions, CrudDialogView as DialogView, type DrawerSize, CrudDrawerView as DrawerView, type EnumOption, FORM_EVENTS, type Field, FieldBuilder, type FieldColSpanContext, type FieldDef, type FieldInput, type FieldOverride, FieldType, type FilterRule, type FormHandle, type FormLayout, type FormLayoutHint, type FormOnChangeFn, type FormPresentationContext, type FormPresentationMode, type FormSection, type FormTab, NativeFormView as FormView, type FormViewOptions, type FormatterFn, type GridCellContext, type GridData, type GridHandle, type GridOnChangeFn, HydraAdapter, type ItemFormatterFn, type LoadOption, type OnChangeFn, CrudPageView as PageView, type ResolvedViewMode, type ResourceConfig, type ResourceEmptyState, type ResourceFilterDescriptor, type ResourceFilterRule, type ResourceFormDetail, type ResourceGridDetail, type ResourceLoadOption, type ResourceLoadOptions, type ResourcePermissions, type ResourceRouting, type ResourceRowActions, ResourceSchemaProvider, type ResourceSchemaProviderProps, type ResourceSchemaResolution, type ResourceSchemaResolver, type ResourceSortDescriptor, type ResourceStore, type ResourceStoreFactory, type ResourceStoreOptions, ResourceStoreProvider, type ResourceStoreProviderProps, type ResourceToolbar, type ResourceToolbarAction, type ResourceToolbarActionVariant, type ResourceToolbarContext, type ResourceToolbarItems, RestAdapter, type SmartCrudFieldContract, type SmartCrudFieldOperation, type SmartCrudFieldPatch, type SmartCrudHydraFieldContract, type SmartCrudHydraFieldDirective, type SmartCrudManualField, type SmartCrudManualFieldContract, type SmartCrudOperation, SmartCrudPage, SmartCrudRolesProvider, type SummaryCalculateContext, type SummaryFormat, type SummaryItem, type SummaryTextContext, type SummaryType, ToolbarSelect, type ToolbarSelectOption, type ToolbarSelectProps, type ValidationRule, buildFieldColSpanContext, buildFields, checkboxField, computeSummaryValue, createCrudEvents, crudRoute, currencyField, dateField, datetimeField, defineFieldContract, defineFields, defineResource, entityField, enumField, fileField, formatSummaryValue, identityField, imageField, isLongTextField, isShortField, noneField, numberField, parseDrawerWidthPx, passwordField, resolveDrawerLayoutBucket, resolveDrawerSize, resolveDrawerWidth, resolveFieldColSpan, resolveFieldsColSpans, resolveSummaryText, resolveViewMode, selectField, switchField, textField, textareaField, useColumnPreset, useResourceStoreFactory, useSmartCrudRoles, validateFieldContract };
|
package/dist/index.d.mts
CHANGED
|
@@ -495,10 +495,23 @@ interface AuditEntry {
|
|
|
495
495
|
after: unknown;
|
|
496
496
|
}>;
|
|
497
497
|
}
|
|
498
|
-
|
|
498
|
+
type AuditFieldLabelResolver = (field: string) => string;
|
|
499
|
+
interface AuditTrailConfig<T extends DataRecord$1 = DataRecord$1> {
|
|
499
500
|
enabled: boolean;
|
|
500
501
|
apiUrl: string | ((id: string | number) => string);
|
|
502
|
+
/** Custom entry renderer. Receives resolved field labels via context in the panel. */
|
|
501
503
|
renderEntry?: (entry: AuditEntry) => ReactNode;
|
|
504
|
+
/**
|
|
505
|
+
* Maps API field keys to display labels. When omitted, CrudPage falls back to
|
|
506
|
+
* matching grid field labels from the active resource schema.
|
|
507
|
+
*/
|
|
508
|
+
fieldLabels?: Record<string, string> | ((field: string) => string | undefined);
|
|
509
|
+
/** One-line context shown under the drawer title for the selected row. */
|
|
510
|
+
recordSubtitle?: (row: T) => string | undefined;
|
|
511
|
+
/** Drawer width token. Default `sm` (480px) — read-only secondary panel. */
|
|
512
|
+
drawerSize?: CrudDrawerSize;
|
|
513
|
+
/** Adds a row-menu action that opens the audit drawer. Default true. */
|
|
514
|
+
rowAction?: boolean;
|
|
502
515
|
}
|
|
503
516
|
//#endregion
|
|
504
517
|
//#region packages/crud/crud/fieldOperationSemantics.d.ts
|
|
@@ -1149,13 +1162,19 @@ interface AuditTrailPanelProps {
|
|
|
1149
1162
|
renderEntry?: (entry: AuditEntry) => ReactNode;
|
|
1150
1163
|
visible: boolean;
|
|
1151
1164
|
onClose: () => void;
|
|
1165
|
+
recordSubtitle?: string;
|
|
1166
|
+
resolveFieldLabel: AuditFieldLabelResolver;
|
|
1167
|
+
drawerSize?: CrudDrawerSize;
|
|
1152
1168
|
}
|
|
1153
1169
|
declare function AuditTrailPanel({
|
|
1154
1170
|
url,
|
|
1155
1171
|
renderEntry,
|
|
1156
1172
|
visible,
|
|
1157
|
-
onClose
|
|
1158
|
-
|
|
1173
|
+
onClose,
|
|
1174
|
+
recordSubtitle,
|
|
1175
|
+
resolveFieldLabel,
|
|
1176
|
+
drawerSize
|
|
1177
|
+
}: AuditTrailPanelProps): React.JSX.Element;
|
|
1159
1178
|
//#endregion
|
|
1160
1179
|
//#region packages/crud/field/FieldBuilders.d.ts
|
|
1161
1180
|
/**
|
|
@@ -1936,4 +1955,4 @@ declare function ResourceStoreProvider({
|
|
|
1936
1955
|
}: ResourceStoreProviderProps): React.JSX.Element;
|
|
1937
1956
|
declare function useResourceStoreFactory(): ResourceStoreFactory;
|
|
1938
1957
|
//#endregion
|
|
1939
|
-
export { type AuditEntry, type AuditTrailConfig, AuditTrailPanel, type AuditTrailPanelProps, type BackendAdapter, type BulkAction, type ColSpan, type ColumnPreset, ColumnPresetSelector, type ColumnPresetState, CrudDialogShell, CrudDrawerShell, type CrudDrawerSize, type CrudDrawerViewEvents, type CrudDrawerViewOptions, CrudFormShell, type CrudFormShellProps, CrudPage, CrudPageShell, type CrudPageViewEvents, type CrudPageViewOptions, type CrudViewMode, type CrudViewModeConfig, DATA_GRID_EVENTS, DEFAULT_DRAWER_SIZE, DEFAULT_DRAWER_WIDTH, DRAWER_WIDTHS, type DataGridSelectionChangedEvent, type DataGridSummaryItem, NativeDataGridView as DataGridView, type DataGridViewOptions, type DataRecord, type DetailSummaryOptions, CrudDialogView as DialogView, type DrawerSize, CrudDrawerView as DrawerView, type EnumOption, FORM_EVENTS, type Field, FieldBuilder, type FieldColSpanContext, type FieldDef, type FieldInput, type FieldOverride, FieldType, type FilterRule, type FormHandle, type FormLayout, type FormLayoutHint, type FormOnChangeFn, type FormPresentationContext, type FormPresentationMode, type FormSection, type FormTab, NativeFormView as FormView, type FormViewOptions, type FormatterFn, type GridCellContext, type GridData, type GridHandle, type GridOnChangeFn, HydraAdapter, type ItemFormatterFn, type LoadOption, type OnChangeFn, CrudPageView as PageView, type ResolvedViewMode, type ResourceConfig, type ResourceEmptyState, type ResourceFilterDescriptor, type ResourceFilterRule, type ResourceFormDetail, type ResourceGridDetail, type ResourceLoadOption, type ResourceLoadOptions, type ResourcePermissions, type ResourceRouting, type ResourceRowActions, ResourceSchemaProvider, type ResourceSchemaProviderProps, type ResourceSchemaResolution, type ResourceSchemaResolver, type ResourceSortDescriptor, type ResourceStore, type ResourceStoreFactory, type ResourceStoreOptions, ResourceStoreProvider, type ResourceStoreProviderProps, type ResourceToolbar, type ResourceToolbarAction, type ResourceToolbarActionVariant, type ResourceToolbarContext, type ResourceToolbarItems, RestAdapter, type SmartCrudFieldContract, type SmartCrudFieldOperation, type SmartCrudFieldPatch, type SmartCrudHydraFieldContract, type SmartCrudHydraFieldDirective, type SmartCrudManualField, type SmartCrudManualFieldContract, type SmartCrudOperation, SmartCrudPage, SmartCrudRolesProvider, type SummaryCalculateContext, type SummaryFormat, type SummaryItem, type SummaryTextContext, type SummaryType, ToolbarSelect, type ToolbarSelectOption, type ToolbarSelectProps, type ValidationRule, buildFieldColSpanContext, buildFields, checkboxField, computeSummaryValue, createCrudEvents, crudRoute, currencyField, dateField, datetimeField, defineFieldContract, defineFields, defineResource, entityField, enumField, fileField, formatSummaryValue, identityField, imageField, isLongTextField, isShortField, noneField, numberField, parseDrawerWidthPx, passwordField, resolveDrawerLayoutBucket, resolveDrawerSize, resolveDrawerWidth, resolveFieldColSpan, resolveFieldsColSpans, resolveSummaryText, resolveViewMode, selectField, switchField, textField, textareaField, useColumnPreset, useResourceStoreFactory, useSmartCrudRoles, validateFieldContract };
|
|
1958
|
+
export { type AuditEntry, type AuditFieldLabelResolver, type AuditTrailConfig, AuditTrailPanel, type AuditTrailPanelProps, type BackendAdapter, type BulkAction, type ColSpan, type ColumnPreset, ColumnPresetSelector, type ColumnPresetState, CrudDialogShell, CrudDrawerShell, type CrudDrawerSize, type CrudDrawerViewEvents, type CrudDrawerViewOptions, CrudFormShell, type CrudFormShellProps, CrudPage, CrudPageShell, type CrudPageViewEvents, type CrudPageViewOptions, type CrudViewMode, type CrudViewModeConfig, DATA_GRID_EVENTS, DEFAULT_DRAWER_SIZE, DEFAULT_DRAWER_WIDTH, DRAWER_WIDTHS, type DataGridSelectionChangedEvent, type DataGridSummaryItem, NativeDataGridView as DataGridView, type DataGridViewOptions, type DataRecord, type DetailSummaryOptions, CrudDialogView as DialogView, type DrawerSize, CrudDrawerView as DrawerView, type EnumOption, FORM_EVENTS, type Field, FieldBuilder, type FieldColSpanContext, type FieldDef, type FieldInput, type FieldOverride, FieldType, type FilterRule, type FormHandle, type FormLayout, type FormLayoutHint, type FormOnChangeFn, type FormPresentationContext, type FormPresentationMode, type FormSection, type FormTab, NativeFormView as FormView, type FormViewOptions, type FormatterFn, type GridCellContext, type GridData, type GridHandle, type GridOnChangeFn, HydraAdapter, type ItemFormatterFn, type LoadOption, type OnChangeFn, CrudPageView as PageView, type ResolvedViewMode, type ResourceConfig, type ResourceEmptyState, type ResourceFilterDescriptor, type ResourceFilterRule, type ResourceFormDetail, type ResourceGridDetail, type ResourceLoadOption, type ResourceLoadOptions, type ResourcePermissions, type ResourceRouting, type ResourceRowActions, ResourceSchemaProvider, type ResourceSchemaProviderProps, type ResourceSchemaResolution, type ResourceSchemaResolver, type ResourceSortDescriptor, type ResourceStore, type ResourceStoreFactory, type ResourceStoreOptions, ResourceStoreProvider, type ResourceStoreProviderProps, type ResourceToolbar, type ResourceToolbarAction, type ResourceToolbarActionVariant, type ResourceToolbarContext, type ResourceToolbarItems, RestAdapter, type SmartCrudFieldContract, type SmartCrudFieldOperation, type SmartCrudFieldPatch, type SmartCrudHydraFieldContract, type SmartCrudHydraFieldDirective, type SmartCrudManualField, type SmartCrudManualFieldContract, type SmartCrudOperation, SmartCrudPage, SmartCrudRolesProvider, type SummaryCalculateContext, type SummaryFormat, type SummaryItem, type SummaryTextContext, type SummaryType, ToolbarSelect, type ToolbarSelectOption, type ToolbarSelectProps, type ValidationRule, buildFieldColSpanContext, buildFields, checkboxField, computeSummaryValue, createCrudEvents, crudRoute, currencyField, dateField, datetimeField, defineFieldContract, defineFields, defineResource, entityField, enumField, fileField, formatSummaryValue, identityField, imageField, isLongTextField, isShortField, noneField, numberField, parseDrawerWidthPx, passwordField, resolveDrawerLayoutBucket, resolveDrawerSize, resolveDrawerWidth, resolveFieldColSpan, resolveFieldsColSpans, resolveSummaryText, resolveViewMode, selectField, switchField, textField, textareaField, useColumnPreset, useResourceStoreFactory, useSmartCrudRoles, validateFieldContract };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { createContext, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
2
2
|
import { Route, useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
|
-
import { AppDialog, AppDropdown, Button, ConfirmDialog, DatePicker, DateRangePicker, Drawer, EmptyState, IconButton, Skeleton } from "@nubitio/ui";
|
|
4
|
+
import { AppDialog, AppDropdown, Badge, Button, ConfirmDialog, DatePicker, DateRangePicker, Drawer, EmptyState, IconButton, Skeleton } from "@nubitio/ui";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { createCrudEvents, createScopedEventBus, getCoreCurrency, getCoreLocale, getCoreTimezone, useCoreHttpClient, useCoreRuntime, useCoreTranslation, useEvents, useMercureSubscription } from "@nubitio/core";
|
|
7
7
|
import { useDropzone } from "react-dropzone";
|
|
@@ -5286,12 +5286,22 @@ function useCrudPage(resource, externalFormRef) {
|
|
|
5286
5286
|
}
|
|
5287
5287
|
//#endregion
|
|
5288
5288
|
//#region packages/crud/crud/AuditTrailPanel.tsx
|
|
5289
|
-
const
|
|
5290
|
-
create: "
|
|
5291
|
-
update: "
|
|
5292
|
-
delete: "
|
|
5289
|
+
const ACTION_BADGE = {
|
|
5290
|
+
create: "success",
|
|
5291
|
+
update: "info",
|
|
5292
|
+
delete: "danger"
|
|
5293
5293
|
};
|
|
5294
|
-
function
|
|
5294
|
+
function formatAuditValue(value, yesLabel, noLabel) {
|
|
5295
|
+
if (value == null || value === "") return "—";
|
|
5296
|
+
if (typeof value === "boolean") return value ? yesLabel : noLabel;
|
|
5297
|
+
if (typeof value === "object") try {
|
|
5298
|
+
return JSON.stringify(value);
|
|
5299
|
+
} catch {
|
|
5300
|
+
return String(value);
|
|
5301
|
+
}
|
|
5302
|
+
return String(value);
|
|
5303
|
+
}
|
|
5304
|
+
function DefaultEntryRenderer({ entry, resolveFieldLabel, yesLabel, noLabel }) {
|
|
5295
5305
|
const { t } = useCoreTranslation();
|
|
5296
5306
|
const date = new Date(entry.timestamp);
|
|
5297
5307
|
const formatted = Number.isNaN(date.getTime()) ? entry.timestamp : date.toLocaleString(getCoreLocale(), { timeZone: getCoreTimezone() });
|
|
@@ -5302,175 +5312,191 @@ function DefaultEntryRenderer({ entry }) {
|
|
|
5302
5312
|
};
|
|
5303
5313
|
const changeKeys = Object.keys(entry.changes);
|
|
5304
5314
|
return /* @__PURE__ */ jsxs("li", {
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
},
|
|
5310
|
-
|
|
5311
|
-
style: {
|
|
5312
|
-
display: "flex",
|
|
5313
|
-
alignItems: "center",
|
|
5314
|
-
gap: 8,
|
|
5315
|
-
marginBottom: 4
|
|
5316
|
-
},
|
|
5315
|
+
className: `nb-audit-trail__entry nb-audit-trail__entry--${entry.action}`,
|
|
5316
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5317
|
+
className: "nb-audit-trail__marker",
|
|
5318
|
+
"aria-hidden": "true"
|
|
5319
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
|
|
5320
|
+
className: "nb-audit-trail__meta",
|
|
5317
5321
|
children: [
|
|
5318
|
-
/* @__PURE__ */ jsx("
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
color: "#757575"
|
|
5322
|
-
},
|
|
5322
|
+
/* @__PURE__ */ jsx("time", {
|
|
5323
|
+
className: "nb-audit-trail__timestamp",
|
|
5324
|
+
dateTime: entry.timestamp,
|
|
5323
5325
|
children: formatted
|
|
5324
5326
|
}),
|
|
5325
5327
|
/* @__PURE__ */ jsx("span", {
|
|
5326
|
-
|
|
5327
|
-
fontSize: 12,
|
|
5328
|
-
color: "#424242"
|
|
5329
|
-
},
|
|
5328
|
+
className: "nb-audit-trail__user",
|
|
5330
5329
|
children: entry.user
|
|
5331
5330
|
}),
|
|
5332
|
-
/* @__PURE__ */ jsx(
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
padding: "1px 6px",
|
|
5337
|
-
borderRadius: 4,
|
|
5338
|
-
backgroundColor: ACTION_COLORS[entry.action],
|
|
5339
|
-
color: "#fff"
|
|
5340
|
-
},
|
|
5331
|
+
/* @__PURE__ */ jsx(Badge, {
|
|
5332
|
+
variant: ACTION_BADGE[entry.action],
|
|
5333
|
+
size: "sm",
|
|
5334
|
+
pill: true,
|
|
5341
5335
|
children: actionLabels[entry.action]
|
|
5342
5336
|
})
|
|
5343
5337
|
]
|
|
5344
5338
|
}), changeKeys.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
5345
|
-
|
|
5346
|
-
margin: "4px 0 0 0",
|
|
5347
|
-
padding: "0 0 0 16px"
|
|
5348
|
-
},
|
|
5339
|
+
className: "nb-audit-trail__changes",
|
|
5349
5340
|
children: changeKeys.map((field) => {
|
|
5350
5341
|
const { before, after } = entry.changes[field];
|
|
5351
5342
|
return /* @__PURE__ */ jsxs("li", {
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5343
|
+
className: "nb-audit-trail__change",
|
|
5344
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5345
|
+
className: "nb-audit-trail__field",
|
|
5346
|
+
children: resolveFieldLabel(field)
|
|
5347
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
5348
|
+
className: "nb-audit-trail__diff",
|
|
5349
|
+
children: [
|
|
5350
|
+
/* @__PURE__ */ jsx("span", {
|
|
5351
|
+
className: "nb-audit-trail__value nb-audit-trail__value--before",
|
|
5352
|
+
children: formatAuditValue(before, yesLabel, noLabel)
|
|
5353
|
+
}),
|
|
5354
|
+
/* @__PURE__ */ jsx("span", {
|
|
5355
|
+
className: "nb-audit-trail__arrow",
|
|
5356
|
+
"aria-hidden": "true",
|
|
5357
|
+
children: "→"
|
|
5358
|
+
}),
|
|
5359
|
+
/* @__PURE__ */ jsx("span", {
|
|
5360
|
+
className: "nb-audit-trail__value nb-audit-trail__value--after",
|
|
5361
|
+
children: formatAuditValue(after, yesLabel, noLabel)
|
|
5362
|
+
})
|
|
5363
|
+
]
|
|
5364
|
+
})]
|
|
5369
5365
|
}, field);
|
|
5370
5366
|
})
|
|
5371
|
-
})]
|
|
5367
|
+
})] })]
|
|
5368
|
+
});
|
|
5369
|
+
}
|
|
5370
|
+
function AuditTrailSkeleton() {
|
|
5371
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5372
|
+
className: "nb-audit-trail__skeleton",
|
|
5373
|
+
"aria-busy": "true",
|
|
5374
|
+
children: [
|
|
5375
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
5376
|
+
variant: "rect",
|
|
5377
|
+
height: 72
|
|
5378
|
+
}),
|
|
5379
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
5380
|
+
variant: "rect",
|
|
5381
|
+
height: 72
|
|
5382
|
+
}),
|
|
5383
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
5384
|
+
variant: "rect",
|
|
5385
|
+
height: 72
|
|
5386
|
+
})
|
|
5387
|
+
]
|
|
5372
5388
|
});
|
|
5373
5389
|
}
|
|
5374
|
-
function AuditTrailPanel({ url, renderEntry, visible, onClose }) {
|
|
5390
|
+
function AuditTrailPanel({ url, renderEntry, visible, onClose, recordSubtitle, resolveFieldLabel, drawerSize = "sm" }) {
|
|
5375
5391
|
const { t } = useCoreTranslation();
|
|
5376
5392
|
const httpClient = useCoreHttpClient();
|
|
5377
5393
|
const [fetchState, setFetchState] = useState({ status: "idle" });
|
|
5378
|
-
|
|
5379
|
-
|
|
5394
|
+
const yesLabel = t("common.yes");
|
|
5395
|
+
const noLabel = t("common.no");
|
|
5396
|
+
const loadEntries = useCallback(() => {
|
|
5397
|
+
if (url === null) {
|
|
5380
5398
|
setFetchState({ status: "idle" });
|
|
5381
5399
|
return;
|
|
5382
5400
|
}
|
|
5383
|
-
let cancelled = false;
|
|
5384
5401
|
setFetchState({ status: "loading" });
|
|
5385
5402
|
httpClient.get(url).then((response) => {
|
|
5386
|
-
|
|
5403
|
+
setFetchState({
|
|
5387
5404
|
status: "success",
|
|
5388
5405
|
entries: response.data
|
|
5389
5406
|
});
|
|
5390
5407
|
}).catch(() => {
|
|
5391
|
-
|
|
5408
|
+
setFetchState({ status: "error" });
|
|
5392
5409
|
});
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
]);
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
})
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
children:
|
|
5464
|
-
})
|
|
5465
|
-
style: {
|
|
5466
|
-
margin: 0,
|
|
5467
|
-
padding: 0
|
|
5468
|
-
},
|
|
5469
|
-
children: fetchState.entries.map((entry) => renderEntry ? /* @__PURE__ */ jsx(React.Fragment, { children: renderEntry(entry) }, String(entry.id)) : /* @__PURE__ */ jsx(DefaultEntryRenderer, { entry }, String(entry.id)))
|
|
5470
|
-
}) : null]
|
|
5410
|
+
}, [httpClient, url]);
|
|
5411
|
+
useEffect(() => {
|
|
5412
|
+
if (!visible) {
|
|
5413
|
+
setFetchState({ status: "idle" });
|
|
5414
|
+
return;
|
|
5415
|
+
}
|
|
5416
|
+
loadEntries();
|
|
5417
|
+
}, [loadEntries, visible]);
|
|
5418
|
+
const drawerTitle = /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: t("auditTrail.title") }), recordSubtitle && /* @__PURE__ */ jsx("p", {
|
|
5419
|
+
className: "nb-audit-trail__subtitle",
|
|
5420
|
+
children: recordSubtitle
|
|
5421
|
+
})] });
|
|
5422
|
+
const body = (() => {
|
|
5423
|
+
if (url === null) return /* @__PURE__ */ jsx(EmptyState, {
|
|
5424
|
+
fill: true,
|
|
5425
|
+
icon: "cursor-click",
|
|
5426
|
+
title: t("auditTrail.selectRecord"),
|
|
5427
|
+
description: t("auditTrail.selectRecordHint"),
|
|
5428
|
+
size: "sm"
|
|
5429
|
+
});
|
|
5430
|
+
if (fetchState.status === "loading") return /* @__PURE__ */ jsx(AuditTrailSkeleton, {});
|
|
5431
|
+
if (fetchState.status === "error") return /* @__PURE__ */ jsx("div", {
|
|
5432
|
+
className: "nb-audit-trail__error",
|
|
5433
|
+
children: /* @__PURE__ */ jsx(EmptyState, {
|
|
5434
|
+
fill: true,
|
|
5435
|
+
variant: "danger",
|
|
5436
|
+
icon: "warning-circle",
|
|
5437
|
+
title: t("auditTrail.error"),
|
|
5438
|
+
size: "sm",
|
|
5439
|
+
action: /* @__PURE__ */ jsx(Button, {
|
|
5440
|
+
variant: "secondary",
|
|
5441
|
+
size: "sm",
|
|
5442
|
+
onClick: loadEntries,
|
|
5443
|
+
children: t("auditTrail.retry")
|
|
5444
|
+
})
|
|
5445
|
+
})
|
|
5446
|
+
});
|
|
5447
|
+
if (fetchState.status === "success" && fetchState.entries.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
|
|
5448
|
+
fill: true,
|
|
5449
|
+
icon: "clock-counter-clockwise",
|
|
5450
|
+
title: t("auditTrail.empty"),
|
|
5451
|
+
description: t("auditTrail.emptyHint"),
|
|
5452
|
+
size: "sm"
|
|
5453
|
+
});
|
|
5454
|
+
if (fetchState.status === "success") return /* @__PURE__ */ jsx("ul", {
|
|
5455
|
+
className: "nb-audit-trail__timeline",
|
|
5456
|
+
children: fetchState.entries.map((entry) => renderEntry ? /* @__PURE__ */ jsx("li", {
|
|
5457
|
+
className: "nb-audit-trail__entry",
|
|
5458
|
+
children: renderEntry(entry)
|
|
5459
|
+
}, String(entry.id)) : /* @__PURE__ */ jsx(DefaultEntryRenderer, {
|
|
5460
|
+
entry,
|
|
5461
|
+
resolveFieldLabel,
|
|
5462
|
+
yesLabel,
|
|
5463
|
+
noLabel
|
|
5464
|
+
}, String(entry.id)))
|
|
5465
|
+
});
|
|
5466
|
+
return null;
|
|
5467
|
+
})();
|
|
5468
|
+
return /* @__PURE__ */ jsx(Drawer, {
|
|
5469
|
+
isOpen: visible,
|
|
5470
|
+
onClose,
|
|
5471
|
+
title: drawerTitle,
|
|
5472
|
+
width: resolveDrawerWidth({ drawerSize }),
|
|
5473
|
+
side: "right",
|
|
5474
|
+
scrim: "subtle",
|
|
5475
|
+
closeLabel: t("auditTrail.closeButton"),
|
|
5476
|
+
"aria-label": t("auditTrail.title"),
|
|
5477
|
+
className: "nb-audit-trail-drawer",
|
|
5478
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
5479
|
+
className: "nb-audit-trail",
|
|
5480
|
+
children: body
|
|
5481
|
+
})
|
|
5471
5482
|
});
|
|
5472
5483
|
}
|
|
5473
5484
|
//#endregion
|
|
5485
|
+
//#region packages/crud/crud/AuditTrail.ts
|
|
5486
|
+
function createAuditFieldLabelResolver(config, fields) {
|
|
5487
|
+
const fieldLabelByName = new Map(fields.map((field) => [field.name, field.label || field.name]));
|
|
5488
|
+
return (field) => {
|
|
5489
|
+
const fromConfig = config?.fieldLabels;
|
|
5490
|
+
if (fromConfig) {
|
|
5491
|
+
if (typeof fromConfig === "function") {
|
|
5492
|
+
const resolved = fromConfig(field);
|
|
5493
|
+
if (resolved) return resolved;
|
|
5494
|
+
} else if (fromConfig[field]) return fromConfig[field];
|
|
5495
|
+
}
|
|
5496
|
+
return fieldLabelByName.get(field) ?? field;
|
|
5497
|
+
};
|
|
5498
|
+
}
|
|
5499
|
+
//#endregion
|
|
5474
5500
|
//#region packages/crud/crud/dialogStore.ts
|
|
5475
5501
|
function initialDialogState() {
|
|
5476
5502
|
return {
|
|
@@ -5818,13 +5844,30 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5818
5844
|
if (hasAuditTrail) {
|
|
5819
5845
|
const first = rows[0];
|
|
5820
5846
|
if (first != null) setSelectedRowId(first["id"] ?? first["ID"] ?? null);
|
|
5847
|
+
else setSelectedRowId(null);
|
|
5821
5848
|
}
|
|
5822
5849
|
};
|
|
5850
|
+
const selectedRow = selectedRows[0] ?? null;
|
|
5823
5851
|
const auditUrl = useMemo(() => {
|
|
5824
5852
|
if (!resolvedResource.auditTrail?.enabled || selectedRowId == null) return null;
|
|
5825
5853
|
const { apiUrl } = resolvedResource.auditTrail;
|
|
5826
5854
|
return typeof apiUrl === "function" ? apiUrl(selectedRowId) : `${apiUrl}${selectedRowId}`;
|
|
5827
5855
|
}, [resolvedResource.auditTrail, selectedRowId]);
|
|
5856
|
+
const resolveAuditFieldLabel = useMemo(() => createAuditFieldLabelResolver(resolvedResource.auditTrail, routeAwareGridFields), [resolvedResource.auditTrail, routeAwareGridFields]);
|
|
5857
|
+
const auditRecordSubtitle = useMemo(() => {
|
|
5858
|
+
if (!resolvedResource.auditTrail?.recordSubtitle || selectedRow == null) return void 0;
|
|
5859
|
+
return resolvedResource.auditTrail.recordSubtitle(selectedRow);
|
|
5860
|
+
}, [resolvedResource.auditTrail, selectedRow]);
|
|
5861
|
+
const openAuditTrail = useCallback((row) => {
|
|
5862
|
+
if (row != null) {
|
|
5863
|
+
const id = row["id"] ?? row["ID"] ?? null;
|
|
5864
|
+
if (id != null) {
|
|
5865
|
+
setSelectedRowId(id);
|
|
5866
|
+
setSelectedRows([row]);
|
|
5867
|
+
}
|
|
5868
|
+
}
|
|
5869
|
+
setAuditOpen(true);
|
|
5870
|
+
}, []);
|
|
5828
5871
|
const executeBulkAction = useCallback(async (action) => {
|
|
5829
5872
|
await action.onAction(selectionState.selectedIds);
|
|
5830
5873
|
selectionState.clearSelection();
|
|
@@ -5838,7 +5881,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5838
5881
|
else executeBulkAction(action);
|
|
5839
5882
|
}, [executeBulkAction]);
|
|
5840
5883
|
const toolbar = useMemo(() => {
|
|
5841
|
-
|
|
5884
|
+
const base = resolveResourceToolbar(resolvedResource, {
|
|
5842
5885
|
resource: resolvedResource,
|
|
5843
5886
|
selectedRow: selectedRows[0],
|
|
5844
5887
|
selectedRows,
|
|
@@ -5846,14 +5889,48 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5846
5889
|
formRef,
|
|
5847
5890
|
events,
|
|
5848
5891
|
emit
|
|
5849
|
-
});
|
|
5892
|
+
}) ?? {};
|
|
5893
|
+
if (!hasAuditTrail) return base;
|
|
5894
|
+
return {
|
|
5895
|
+
...base,
|
|
5896
|
+
utility: [...base.utility ?? [], {
|
|
5897
|
+
key: "audit-trail",
|
|
5898
|
+
text: t("crudPage.auditTrailButton"),
|
|
5899
|
+
icon: "ph-clock-counter-clockwise",
|
|
5900
|
+
hint: t("auditTrail.toolbarHint"),
|
|
5901
|
+
disabled: selectedRowId == null,
|
|
5902
|
+
onClick: () => openAuditTrail()
|
|
5903
|
+
}]
|
|
5904
|
+
};
|
|
5850
5905
|
}, [
|
|
5851
5906
|
emit,
|
|
5852
5907
|
events,
|
|
5853
5908
|
formRef,
|
|
5854
5909
|
gridRef,
|
|
5910
|
+
hasAuditTrail,
|
|
5911
|
+
openAuditTrail,
|
|
5855
5912
|
resolvedResource,
|
|
5856
|
-
|
|
5913
|
+
selectedRowId,
|
|
5914
|
+
selectedRows,
|
|
5915
|
+
t
|
|
5916
|
+
]);
|
|
5917
|
+
const rowActions = useMemo(() => {
|
|
5918
|
+
const base = resolvedResource.rowActions;
|
|
5919
|
+
if (!hasAuditTrail || resolvedResource.auditTrail?.rowAction === false) return base;
|
|
5920
|
+
const auditAction = (row) => ({
|
|
5921
|
+
key: "audit-trail",
|
|
5922
|
+
text: t("auditTrail.rowAction"),
|
|
5923
|
+
icon: "ph-clock-counter-clockwise",
|
|
5924
|
+
onClick: () => openAuditTrail(row)
|
|
5925
|
+
});
|
|
5926
|
+
if (typeof base === "function") return (row) => [...base(row), auditAction(row)];
|
|
5927
|
+
return (row) => [...base ?? [], auditAction(row)];
|
|
5928
|
+
}, [
|
|
5929
|
+
hasAuditTrail,
|
|
5930
|
+
openAuditTrail,
|
|
5931
|
+
resolvedResource.auditTrail?.rowAction,
|
|
5932
|
+
resolvedResource.rowActions,
|
|
5933
|
+
t
|
|
5857
5934
|
]);
|
|
5858
5935
|
/** Preset selector rendered as a toolbar slot via `beforeToolbar`. */
|
|
5859
5936
|
const renderPresetSelector = hasPresets ? () => /* @__PURE__ */ jsx(ColumnPresetSelector, {
|
|
@@ -5866,7 +5943,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5866
5943
|
}) : void 0;
|
|
5867
5944
|
return /* @__PURE__ */ jsxs("div", {
|
|
5868
5945
|
id: "wrapper",
|
|
5869
|
-
className: [
|
|
5946
|
+
className: [viewMode.mode === "page" && dialogIsOpen && "wrapper--with-page"].filter(Boolean).join(" ") || void 0,
|
|
5870
5947
|
children: [
|
|
5871
5948
|
(resolvedResource.bulkActions?.length ?? 0) > 0 && selectionState.hasSelection && /* @__PURE__ */ jsxs("div", {
|
|
5872
5949
|
className: "nb-bulk-toolbar",
|
|
@@ -5883,16 +5960,6 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5883
5960
|
children: action.label
|
|
5884
5961
|
}, action.key))]
|
|
5885
5962
|
}),
|
|
5886
|
-
hasAuditTrail && /* @__PURE__ */ jsx("div", {
|
|
5887
|
-
className: "nb-audit-trail-toolbar",
|
|
5888
|
-
style: { marginBottom: 8 },
|
|
5889
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
5890
|
-
type: "button",
|
|
5891
|
-
onClick: () => setAuditOpen((prev) => !prev),
|
|
5892
|
-
"aria-pressed": auditOpen,
|
|
5893
|
-
children: t("crudPage.auditTrailButton")
|
|
5894
|
-
})
|
|
5895
|
-
}),
|
|
5896
5963
|
/* @__PURE__ */ jsx(NativeDataGridView, {
|
|
5897
5964
|
ref: gridRef,
|
|
5898
5965
|
id: resolvedResource.id,
|
|
@@ -5917,7 +5984,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5917
5984
|
mode: resolvedResource.mode,
|
|
5918
5985
|
stateStoringEnabled: resolvedResource.stateStoring,
|
|
5919
5986
|
toolbar,
|
|
5920
|
-
rowActions
|
|
5987
|
+
rowActions,
|
|
5921
5988
|
onAdd: requestNew,
|
|
5922
5989
|
onEdit: requestEdit,
|
|
5923
5990
|
onView: requestView,
|
|
@@ -5990,7 +6057,10 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5990
6057
|
url: auditUrl,
|
|
5991
6058
|
renderEntry: resolvedResource.auditTrail.renderEntry,
|
|
5992
6059
|
visible: auditOpen,
|
|
5993
|
-
onClose: () => setAuditOpen(false)
|
|
6060
|
+
onClose: () => setAuditOpen(false),
|
|
6061
|
+
recordSubtitle: auditRecordSubtitle,
|
|
6062
|
+
resolveFieldLabel: resolveAuditFieldLabel,
|
|
6063
|
+
drawerSize: resolvedResource.auditTrail?.drawerSize
|
|
5994
6064
|
}),
|
|
5995
6065
|
/* @__PURE__ */ jsx(ConfirmDialog, {
|
|
5996
6066
|
open: confirmState.open,
|
package/dist/style.css
CHANGED
|
@@ -3276,8 +3276,7 @@ html[data-density=compact] .nb-datagrid .nb-badge {
|
|
|
3276
3276
|
|
|
3277
3277
|
#wrapper.wrapper--with-page > .data-grid-view,
|
|
3278
3278
|
#wrapper.wrapper--with-page > .view.datagrid-list,
|
|
3279
|
-
#wrapper.wrapper--with-page > .nb-bulk-toolbar
|
|
3280
|
-
#wrapper.wrapper--with-page > .nb-audit-trail-toolbar {
|
|
3279
|
+
#wrapper.wrapper--with-page > .nb-bulk-toolbar {
|
|
3281
3280
|
display: none;
|
|
3282
3281
|
}
|
|
3283
3282
|
|
|
@@ -3292,6 +3291,160 @@ html[data-density=compact] .nb-crud-page-shell__footer {
|
|
|
3292
3291
|
gap: var(--space-1);
|
|
3293
3292
|
padding: var(--space-2) var(--space-3);
|
|
3294
3293
|
}
|
|
3294
|
+
.nb-audit-trail {
|
|
3295
|
+
display: flex;
|
|
3296
|
+
flex-direction: column;
|
|
3297
|
+
gap: var(--space-3);
|
|
3298
|
+
min-height: 0;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
.nb-audit-trail__subtitle {
|
|
3302
|
+
color: var(--text-secondary);
|
|
3303
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
3304
|
+
line-height: 1.4;
|
|
3305
|
+
margin: calc(var(--space-1) * -1) 0 0;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
.nb-audit-trail__timeline {
|
|
3309
|
+
display: flex;
|
|
3310
|
+
flex-direction: column;
|
|
3311
|
+
gap: 0;
|
|
3312
|
+
list-style: none;
|
|
3313
|
+
margin: 0;
|
|
3314
|
+
padding: 0;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
.nb-audit-trail__entry {
|
|
3318
|
+
display: grid;
|
|
3319
|
+
gap: var(--space-2);
|
|
3320
|
+
grid-template-columns: 12px 1fr;
|
|
3321
|
+
padding: var(--space-3) 0;
|
|
3322
|
+
position: relative;
|
|
3323
|
+
}
|
|
3324
|
+
.nb-audit-trail__entry:not(:last-child) {
|
|
3325
|
+
border-bottom: 1px solid var(--border-color);
|
|
3326
|
+
}
|
|
3327
|
+
.nb-audit-trail__entry::before {
|
|
3328
|
+
background: var(--border-color);
|
|
3329
|
+
bottom: 0;
|
|
3330
|
+
content: "";
|
|
3331
|
+
left: 5px;
|
|
3332
|
+
position: absolute;
|
|
3333
|
+
top: calc(var(--space-3) + 6px);
|
|
3334
|
+
width: 2px;
|
|
3335
|
+
}
|
|
3336
|
+
.nb-audit-trail__entry:last-child::before {
|
|
3337
|
+
display: none;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
.nb-audit-trail__marker {
|
|
3341
|
+
background: var(--surface-2);
|
|
3342
|
+
border: 2px solid var(--accent-color);
|
|
3343
|
+
border-radius: 50%;
|
|
3344
|
+
height: 12px;
|
|
3345
|
+
margin-top: 4px;
|
|
3346
|
+
position: relative;
|
|
3347
|
+
width: 12px;
|
|
3348
|
+
z-index: 1;
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
.nb-audit-trail__entry--create .nb-audit-trail__marker {
|
|
3352
|
+
border-color: var(--success-color);
|
|
3353
|
+
}
|
|
3354
|
+
|
|
3355
|
+
.nb-audit-trail__entry--update .nb-audit-trail__marker {
|
|
3356
|
+
border-color: var(--info-color, var(--accent-color));
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
.nb-audit-trail__entry--delete .nb-audit-trail__marker {
|
|
3360
|
+
border-color: var(--danger-color);
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
.nb-audit-trail__meta {
|
|
3364
|
+
align-items: center;
|
|
3365
|
+
display: flex;
|
|
3366
|
+
flex-wrap: wrap;
|
|
3367
|
+
gap: var(--space-2);
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
.nb-audit-trail__timestamp {
|
|
3371
|
+
color: var(--text-secondary);
|
|
3372
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
3373
|
+
font-variant-numeric: tabular-nums;
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
.nb-audit-trail__user {
|
|
3377
|
+
color: var(--text-primary);
|
|
3378
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
3379
|
+
font-weight: 500;
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
.nb-audit-trail__changes {
|
|
3383
|
+
display: flex;
|
|
3384
|
+
flex-direction: column;
|
|
3385
|
+
gap: var(--space-2);
|
|
3386
|
+
list-style: none;
|
|
3387
|
+
margin: 0;
|
|
3388
|
+
padding: 0;
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
.nb-audit-trail__change {
|
|
3392
|
+
background: var(--surface-2);
|
|
3393
|
+
border: 1px solid var(--border-color);
|
|
3394
|
+
border-radius: var(--radius-md, 6px);
|
|
3395
|
+
display: grid;
|
|
3396
|
+
gap: var(--space-1);
|
|
3397
|
+
padding: var(--space-2) var(--space-3);
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
.nb-audit-trail__field {
|
|
3401
|
+
color: var(--text-primary);
|
|
3402
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
3403
|
+
font-weight: 600;
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
.nb-audit-trail__diff {
|
|
3407
|
+
align-items: center;
|
|
3408
|
+
color: var(--text-secondary);
|
|
3409
|
+
display: flex;
|
|
3410
|
+
flex-wrap: wrap;
|
|
3411
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
3412
|
+
gap: var(--space-2);
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
.nb-audit-trail__value {
|
|
3416
|
+
font-variant-numeric: tabular-nums;
|
|
3417
|
+
max-width: 100%;
|
|
3418
|
+
overflow-wrap: anywhere;
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
.nb-audit-trail__value--before {
|
|
3422
|
+
color: var(--danger-color);
|
|
3423
|
+
text-decoration: line-through;
|
|
3424
|
+
text-decoration-color: color-mix(in srgb, var(--danger-color) 55%, transparent);
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
.nb-audit-trail__value--after {
|
|
3428
|
+
color: var(--success-color);
|
|
3429
|
+
font-weight: 500;
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
.nb-audit-trail__arrow {
|
|
3433
|
+
color: var(--text-tertiary);
|
|
3434
|
+
flex: 0 0 auto;
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
.nb-audit-trail__skeleton {
|
|
3438
|
+
display: flex;
|
|
3439
|
+
flex-direction: column;
|
|
3440
|
+
gap: var(--space-3);
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
.nb-audit-trail__error {
|
|
3444
|
+
display: flex;
|
|
3445
|
+
flex-direction: column;
|
|
3446
|
+
gap: var(--space-3);
|
|
3447
|
+
}
|
|
3295
3448
|
.nb-smart-crud-fallback {
|
|
3296
3449
|
display: flex;
|
|
3297
3450
|
flex-direction: column;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nubitio/crud",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
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,8 +56,8 @@
|
|
|
56
56
|
"react-dom": "^19.0.0",
|
|
57
57
|
"react-i18next": "^14.0.0",
|
|
58
58
|
"react-router-dom": "^6.0.0",
|
|
59
|
-
"@nubitio/
|
|
60
|
-
"@nubitio/
|
|
59
|
+
"@nubitio/core": "^0.5.1",
|
|
60
|
+
"@nubitio/ui": "^0.5.1"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"react-dropzone": "^15.0.0"
|