@nubitio/crud 0.5.0 → 0.5.2
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 +248 -173
- package/dist/index.d.cts +23 -4
- package/dist/index.d.mts +23 -4
- package/dist/index.mjs +249 -174
- package/dist/style.css +94 -2
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -5310,12 +5310,61 @@ 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
|
-
|
|
5318
|
+
const ACTION_TONE = {
|
|
5319
|
+
create: "success",
|
|
5320
|
+
update: "info",
|
|
5321
|
+
delete: "danger"
|
|
5322
|
+
};
|
|
5323
|
+
function formatAuditValue(value, yesLabel, noLabel) {
|
|
5324
|
+
if (value == null || value === "") return "—";
|
|
5325
|
+
if (typeof value === "boolean") return value ? yesLabel : noLabel;
|
|
5326
|
+
if (typeof value === "object") try {
|
|
5327
|
+
return JSON.stringify(value);
|
|
5328
|
+
} catch {
|
|
5329
|
+
return String(value);
|
|
5330
|
+
}
|
|
5331
|
+
return String(value);
|
|
5332
|
+
}
|
|
5333
|
+
function DefaultEntryContent({ entry, resolveFieldLabel, yesLabel, noLabel }) {
|
|
5334
|
+
const changeKeys = Object.keys(entry.changes);
|
|
5335
|
+
if (changeKeys.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
|
|
5336
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
5337
|
+
className: "nb-audit-trail__changes",
|
|
5338
|
+
children: changeKeys.map((field) => {
|
|
5339
|
+
const { before, after } = entry.changes[field];
|
|
5340
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
5341
|
+
className: "nb-audit-trail__change",
|
|
5342
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5343
|
+
className: "nb-audit-trail__field",
|
|
5344
|
+
children: resolveFieldLabel(field)
|
|
5345
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5346
|
+
className: "nb-audit-trail__diff",
|
|
5347
|
+
children: [
|
|
5348
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5349
|
+
className: "nb-audit-trail__value nb-audit-trail__value--before",
|
|
5350
|
+
children: formatAuditValue(before, yesLabel, noLabel)
|
|
5351
|
+
}),
|
|
5352
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5353
|
+
className: "nb-audit-trail__arrow",
|
|
5354
|
+
"aria-hidden": "true",
|
|
5355
|
+
children: "→"
|
|
5356
|
+
}),
|
|
5357
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5358
|
+
className: "nb-audit-trail__value nb-audit-trail__value--after",
|
|
5359
|
+
children: formatAuditValue(after, yesLabel, noLabel)
|
|
5360
|
+
})
|
|
5361
|
+
]
|
|
5362
|
+
})]
|
|
5363
|
+
}, field);
|
|
5364
|
+
})
|
|
5365
|
+
});
|
|
5366
|
+
}
|
|
5367
|
+
function DefaultEntryRenderer({ entry, resolveFieldLabel, yesLabel, noLabel }) {
|
|
5319
5368
|
const { t } = (0, _nubitio_core.useCoreTranslation)();
|
|
5320
5369
|
const date = new Date(entry.timestamp);
|
|
5321
5370
|
const formatted = Number.isNaN(date.getTime()) ? entry.timestamp : date.toLocaleString((0, _nubitio_core.getCoreLocale)(), { timeZone: (0, _nubitio_core.getCoreTimezone)() });
|
|
@@ -5324,177 +5373,159 @@ function DefaultEntryRenderer({ entry }) {
|
|
|
5324
5373
|
update: t("auditTrail.action.update"),
|
|
5325
5374
|
delete: t("auditTrail.action.delete")
|
|
5326
5375
|
};
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
}
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
}), changeKeys.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
5369
|
-
style: {
|
|
5370
|
-
margin: "4px 0 0 0",
|
|
5371
|
-
padding: "0 0 0 16px"
|
|
5372
|
-
},
|
|
5373
|
-
children: changeKeys.map((field) => {
|
|
5374
|
-
const { before, after } = entry.changes[field];
|
|
5375
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
5376
|
-
style: {
|
|
5377
|
-
fontSize: 12,
|
|
5378
|
-
color: "#616161"
|
|
5379
|
-
},
|
|
5380
|
-
children: [
|
|
5381
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("strong", { children: [field, ":"] }),
|
|
5382
|
-
" ",
|
|
5383
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5384
|
-
style: { color: "#c62828" },
|
|
5385
|
-
children: String(before ?? "—")
|
|
5386
|
-
}),
|
|
5387
|
-
" → ",
|
|
5388
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5389
|
-
style: { color: "#2e7d32" },
|
|
5390
|
-
children: String(after ?? "—")
|
|
5391
|
-
})
|
|
5392
|
-
]
|
|
5393
|
-
}, field);
|
|
5376
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TimelineItem, {
|
|
5377
|
+
status: "complete",
|
|
5378
|
+
tone: ACTION_TONE[entry.action],
|
|
5379
|
+
title: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5380
|
+
className: "nb-audit-trail__meta",
|
|
5381
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5382
|
+
className: "nb-audit-trail__user",
|
|
5383
|
+
children: entry.user
|
|
5384
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Badge, {
|
|
5385
|
+
variant: ACTION_BADGE[entry.action],
|
|
5386
|
+
size: "sm",
|
|
5387
|
+
pill: true,
|
|
5388
|
+
children: actionLabels[entry.action]
|
|
5389
|
+
})]
|
|
5390
|
+
}),
|
|
5391
|
+
timestamp: formatted,
|
|
5392
|
+
dateTime: entry.timestamp,
|
|
5393
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultEntryContent, {
|
|
5394
|
+
entry,
|
|
5395
|
+
resolveFieldLabel,
|
|
5396
|
+
yesLabel,
|
|
5397
|
+
noLabel
|
|
5398
|
+
})
|
|
5399
|
+
});
|
|
5400
|
+
}
|
|
5401
|
+
function AuditTrailSkeleton() {
|
|
5402
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5403
|
+
className: "nb-audit-trail__skeleton",
|
|
5404
|
+
"aria-busy": "true",
|
|
5405
|
+
children: [
|
|
5406
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Skeleton, {
|
|
5407
|
+
variant: "rect",
|
|
5408
|
+
height: 72
|
|
5409
|
+
}),
|
|
5410
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Skeleton, {
|
|
5411
|
+
variant: "rect",
|
|
5412
|
+
height: 72
|
|
5413
|
+
}),
|
|
5414
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Skeleton, {
|
|
5415
|
+
variant: "rect",
|
|
5416
|
+
height: 72
|
|
5394
5417
|
})
|
|
5395
|
-
|
|
5418
|
+
]
|
|
5396
5419
|
});
|
|
5397
5420
|
}
|
|
5398
|
-
function AuditTrailPanel({ url, renderEntry, visible, onClose }) {
|
|
5421
|
+
function AuditTrailPanel({ url, renderEntry, visible, onClose, recordSubtitle, resolveFieldLabel, drawerSize = "sm" }) {
|
|
5399
5422
|
const { t } = (0, _nubitio_core.useCoreTranslation)();
|
|
5400
5423
|
const httpClient = (0, _nubitio_core.useCoreHttpClient)();
|
|
5401
5424
|
const [fetchState, setFetchState] = (0, react.useState)({ status: "idle" });
|
|
5402
|
-
(
|
|
5403
|
-
|
|
5425
|
+
const yesLabel = t("common.yes");
|
|
5426
|
+
const noLabel = t("common.no");
|
|
5427
|
+
const loadEntries = (0, react.useCallback)(() => {
|
|
5428
|
+
if (url === null) {
|
|
5404
5429
|
setFetchState({ status: "idle" });
|
|
5405
5430
|
return;
|
|
5406
5431
|
}
|
|
5407
|
-
let cancelled = false;
|
|
5408
5432
|
setFetchState({ status: "loading" });
|
|
5409
5433
|
httpClient.get(url).then((response) => {
|
|
5410
|
-
|
|
5434
|
+
setFetchState({
|
|
5411
5435
|
status: "success",
|
|
5412
5436
|
entries: response.data
|
|
5413
5437
|
});
|
|
5414
5438
|
}).catch(() => {
|
|
5415
|
-
|
|
5439
|
+
setFetchState({ status: "error" });
|
|
5416
5440
|
});
|
|
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: t("auditTrail.empty")
|
|
5488
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
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]
|
|
5441
|
+
}, [httpClient, url]);
|
|
5442
|
+
(0, react.useEffect)(() => {
|
|
5443
|
+
if (!visible) {
|
|
5444
|
+
setFetchState({ status: "idle" });
|
|
5445
|
+
return;
|
|
5446
|
+
}
|
|
5447
|
+
loadEntries();
|
|
5448
|
+
}, [loadEntries, visible]);
|
|
5449
|
+
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", {
|
|
5450
|
+
className: "nb-audit-trail__subtitle",
|
|
5451
|
+
children: recordSubtitle
|
|
5452
|
+
})] });
|
|
5453
|
+
const body = (() => {
|
|
5454
|
+
if (url === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.EmptyState, {
|
|
5455
|
+
fill: true,
|
|
5456
|
+
icon: "cursor-click",
|
|
5457
|
+
title: t("auditTrail.selectRecord"),
|
|
5458
|
+
description: t("auditTrail.selectRecordHint"),
|
|
5459
|
+
size: "sm"
|
|
5460
|
+
});
|
|
5461
|
+
if (fetchState.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuditTrailSkeleton, {});
|
|
5462
|
+
if (fetchState.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5463
|
+
className: "nb-audit-trail__error",
|
|
5464
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.EmptyState, {
|
|
5465
|
+
fill: true,
|
|
5466
|
+
variant: "danger",
|
|
5467
|
+
icon: "warning-circle",
|
|
5468
|
+
title: t("auditTrail.error"),
|
|
5469
|
+
size: "sm",
|
|
5470
|
+
action: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
5471
|
+
variant: "secondary",
|
|
5472
|
+
size: "sm",
|
|
5473
|
+
onClick: loadEntries,
|
|
5474
|
+
children: t("auditTrail.retry")
|
|
5475
|
+
})
|
|
5476
|
+
})
|
|
5477
|
+
});
|
|
5478
|
+
if (fetchState.status === "success" && fetchState.entries.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.EmptyState, {
|
|
5479
|
+
fill: true,
|
|
5480
|
+
icon: "clock-counter-clockwise",
|
|
5481
|
+
title: t("auditTrail.empty"),
|
|
5482
|
+
description: t("auditTrail.emptyHint"),
|
|
5483
|
+
size: "sm"
|
|
5484
|
+
});
|
|
5485
|
+
if (fetchState.status === "success") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Timeline, {
|
|
5486
|
+
variant: "log",
|
|
5487
|
+
"aria-label": t("auditTrail.title"),
|
|
5488
|
+
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, {
|
|
5489
|
+
entry,
|
|
5490
|
+
resolveFieldLabel,
|
|
5491
|
+
yesLabel,
|
|
5492
|
+
noLabel
|
|
5493
|
+
}, String(entry.id)))
|
|
5494
|
+
});
|
|
5495
|
+
return null;
|
|
5496
|
+
})();
|
|
5497
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Drawer, {
|
|
5498
|
+
isOpen: visible,
|
|
5499
|
+
onClose,
|
|
5500
|
+
title: drawerTitle,
|
|
5501
|
+
width: resolveDrawerWidth({ drawerSize }),
|
|
5502
|
+
side: "right",
|
|
5503
|
+
scrim: "subtle",
|
|
5504
|
+
closeLabel: t("auditTrail.closeButton"),
|
|
5505
|
+
"aria-label": t("auditTrail.title"),
|
|
5506
|
+
className: "nb-audit-trail-drawer",
|
|
5507
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5508
|
+
className: "nb-audit-trail",
|
|
5509
|
+
children: body
|
|
5510
|
+
})
|
|
5495
5511
|
});
|
|
5496
5512
|
}
|
|
5497
5513
|
//#endregion
|
|
5514
|
+
//#region packages/crud/crud/AuditTrail.ts
|
|
5515
|
+
function createAuditFieldLabelResolver(config, fields) {
|
|
5516
|
+
const fieldLabelByName = new Map(fields.map((field) => [field.name, field.label || field.name]));
|
|
5517
|
+
return (field) => {
|
|
5518
|
+
const fromConfig = config?.fieldLabels;
|
|
5519
|
+
if (fromConfig) {
|
|
5520
|
+
if (typeof fromConfig === "function") {
|
|
5521
|
+
const resolved = fromConfig(field);
|
|
5522
|
+
if (resolved) return resolved;
|
|
5523
|
+
} else if (fromConfig[field]) return fromConfig[field];
|
|
5524
|
+
}
|
|
5525
|
+
return fieldLabelByName.get(field) ?? field;
|
|
5526
|
+
};
|
|
5527
|
+
}
|
|
5528
|
+
//#endregion
|
|
5498
5529
|
//#region packages/crud/crud/dialogStore.ts
|
|
5499
5530
|
function initialDialogState() {
|
|
5500
5531
|
return {
|
|
@@ -5842,13 +5873,30 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5842
5873
|
if (hasAuditTrail) {
|
|
5843
5874
|
const first = rows[0];
|
|
5844
5875
|
if (first != null) setSelectedRowId(first["id"] ?? first["ID"] ?? null);
|
|
5876
|
+
else setSelectedRowId(null);
|
|
5845
5877
|
}
|
|
5846
5878
|
};
|
|
5879
|
+
const selectedRow = selectedRows[0] ?? null;
|
|
5847
5880
|
const auditUrl = (0, react.useMemo)(() => {
|
|
5848
5881
|
if (!resolvedResource.auditTrail?.enabled || selectedRowId == null) return null;
|
|
5849
5882
|
const { apiUrl } = resolvedResource.auditTrail;
|
|
5850
5883
|
return typeof apiUrl === "function" ? apiUrl(selectedRowId) : `${apiUrl}${selectedRowId}`;
|
|
5851
5884
|
}, [resolvedResource.auditTrail, selectedRowId]);
|
|
5885
|
+
const resolveAuditFieldLabel = (0, react.useMemo)(() => createAuditFieldLabelResolver(resolvedResource.auditTrail, routeAwareGridFields), [resolvedResource.auditTrail, routeAwareGridFields]);
|
|
5886
|
+
const auditRecordSubtitle = (0, react.useMemo)(() => {
|
|
5887
|
+
if (!resolvedResource.auditTrail?.recordSubtitle || selectedRow == null) return void 0;
|
|
5888
|
+
return resolvedResource.auditTrail.recordSubtitle(selectedRow);
|
|
5889
|
+
}, [resolvedResource.auditTrail, selectedRow]);
|
|
5890
|
+
const openAuditTrail = (0, react.useCallback)((row) => {
|
|
5891
|
+
if (row != null) {
|
|
5892
|
+
const id = row["id"] ?? row["ID"] ?? null;
|
|
5893
|
+
if (id != null) {
|
|
5894
|
+
setSelectedRowId(id);
|
|
5895
|
+
setSelectedRows([row]);
|
|
5896
|
+
}
|
|
5897
|
+
}
|
|
5898
|
+
setAuditOpen(true);
|
|
5899
|
+
}, []);
|
|
5852
5900
|
const executeBulkAction = (0, react.useCallback)(async (action) => {
|
|
5853
5901
|
await action.onAction(selectionState.selectedIds);
|
|
5854
5902
|
selectionState.clearSelection();
|
|
@@ -5862,7 +5910,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5862
5910
|
else executeBulkAction(action);
|
|
5863
5911
|
}, [executeBulkAction]);
|
|
5864
5912
|
const toolbar = (0, react.useMemo)(() => {
|
|
5865
|
-
|
|
5913
|
+
const base = resolveResourceToolbar(resolvedResource, {
|
|
5866
5914
|
resource: resolvedResource,
|
|
5867
5915
|
selectedRow: selectedRows[0],
|
|
5868
5916
|
selectedRows,
|
|
@@ -5870,14 +5918,48 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5870
5918
|
formRef,
|
|
5871
5919
|
events,
|
|
5872
5920
|
emit
|
|
5873
|
-
});
|
|
5921
|
+
}) ?? {};
|
|
5922
|
+
if (!hasAuditTrail) return base;
|
|
5923
|
+
return {
|
|
5924
|
+
...base,
|
|
5925
|
+
utility: [...base.utility ?? [], {
|
|
5926
|
+
key: "audit-trail",
|
|
5927
|
+
text: t("crudPage.auditTrailButton"),
|
|
5928
|
+
icon: "ph-clock-counter-clockwise",
|
|
5929
|
+
hint: t("auditTrail.toolbarHint"),
|
|
5930
|
+
disabled: selectedRowId == null,
|
|
5931
|
+
onClick: () => openAuditTrail()
|
|
5932
|
+
}]
|
|
5933
|
+
};
|
|
5874
5934
|
}, [
|
|
5875
5935
|
emit,
|
|
5876
5936
|
events,
|
|
5877
5937
|
formRef,
|
|
5878
5938
|
gridRef,
|
|
5939
|
+
hasAuditTrail,
|
|
5940
|
+
openAuditTrail,
|
|
5879
5941
|
resolvedResource,
|
|
5880
|
-
|
|
5942
|
+
selectedRowId,
|
|
5943
|
+
selectedRows,
|
|
5944
|
+
t
|
|
5945
|
+
]);
|
|
5946
|
+
const rowActions = (0, react.useMemo)(() => {
|
|
5947
|
+
const base = resolvedResource.rowActions;
|
|
5948
|
+
if (!hasAuditTrail || resolvedResource.auditTrail?.rowAction === false) return base;
|
|
5949
|
+
const auditAction = (row) => ({
|
|
5950
|
+
key: "audit-trail",
|
|
5951
|
+
text: t("auditTrail.rowAction"),
|
|
5952
|
+
icon: "ph-clock-counter-clockwise",
|
|
5953
|
+
onClick: () => openAuditTrail(row)
|
|
5954
|
+
});
|
|
5955
|
+
if (typeof base === "function") return (row) => [...base(row), auditAction(row)];
|
|
5956
|
+
return (row) => [...base ?? [], auditAction(row)];
|
|
5957
|
+
}, [
|
|
5958
|
+
hasAuditTrail,
|
|
5959
|
+
openAuditTrail,
|
|
5960
|
+
resolvedResource.auditTrail?.rowAction,
|
|
5961
|
+
resolvedResource.rowActions,
|
|
5962
|
+
t
|
|
5881
5963
|
]);
|
|
5882
5964
|
/** Preset selector rendered as a toolbar slot via `beforeToolbar`. */
|
|
5883
5965
|
const renderPresetSelector = hasPresets ? () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColumnPresetSelector, {
|
|
@@ -5890,7 +5972,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5890
5972
|
}) : void 0;
|
|
5891
5973
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5892
5974
|
id: "wrapper",
|
|
5893
|
-
className: [
|
|
5975
|
+
className: [viewMode.mode === "page" && dialogIsOpen && "wrapper--with-page"].filter(Boolean).join(" ") || void 0,
|
|
5894
5976
|
children: [
|
|
5895
5977
|
(resolvedResource.bulkActions?.length ?? 0) > 0 && selectionState.hasSelection && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5896
5978
|
className: "nb-bulk-toolbar",
|
|
@@ -5907,16 +5989,6 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5907
5989
|
children: action.label
|
|
5908
5990
|
}, action.key))]
|
|
5909
5991
|
}),
|
|
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
5992
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NativeDataGridView, {
|
|
5921
5993
|
ref: gridRef,
|
|
5922
5994
|
id: resolvedResource.id,
|
|
@@ -5941,7 +6013,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5941
6013
|
mode: resolvedResource.mode,
|
|
5942
6014
|
stateStoringEnabled: resolvedResource.stateStoring,
|
|
5943
6015
|
toolbar,
|
|
5944
|
-
rowActions
|
|
6016
|
+
rowActions,
|
|
5945
6017
|
onAdd: requestNew,
|
|
5946
6018
|
onEdit: requestEdit,
|
|
5947
6019
|
onView: requestView,
|
|
@@ -6014,7 +6086,10 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
6014
6086
|
url: auditUrl,
|
|
6015
6087
|
renderEntry: resolvedResource.auditTrail.renderEntry,
|
|
6016
6088
|
visible: auditOpen,
|
|
6017
|
-
onClose: () => setAuditOpen(false)
|
|
6089
|
+
onClose: () => setAuditOpen(false),
|
|
6090
|
+
recordSubtitle: auditRecordSubtitle,
|
|
6091
|
+
resolveFieldLabel: resolveAuditFieldLabel,
|
|
6092
|
+
drawerSize: resolvedResource.auditTrail?.drawerSize
|
|
6018
6093
|
}),
|
|
6019
6094
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.ConfirmDialog, {
|
|
6020
6095
|
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, Timeline, TimelineItem } 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,61 @@ 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
|
-
|
|
5294
|
+
const ACTION_TONE = {
|
|
5295
|
+
create: "success",
|
|
5296
|
+
update: "info",
|
|
5297
|
+
delete: "danger"
|
|
5298
|
+
};
|
|
5299
|
+
function formatAuditValue(value, yesLabel, noLabel) {
|
|
5300
|
+
if (value == null || value === "") return "—";
|
|
5301
|
+
if (typeof value === "boolean") return value ? yesLabel : noLabel;
|
|
5302
|
+
if (typeof value === "object") try {
|
|
5303
|
+
return JSON.stringify(value);
|
|
5304
|
+
} catch {
|
|
5305
|
+
return String(value);
|
|
5306
|
+
}
|
|
5307
|
+
return String(value);
|
|
5308
|
+
}
|
|
5309
|
+
function DefaultEntryContent({ entry, resolveFieldLabel, yesLabel, noLabel }) {
|
|
5310
|
+
const changeKeys = Object.keys(entry.changes);
|
|
5311
|
+
if (changeKeys.length === 0) return /* @__PURE__ */ jsx(Fragment, {});
|
|
5312
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
5313
|
+
className: "nb-audit-trail__changes",
|
|
5314
|
+
children: changeKeys.map((field) => {
|
|
5315
|
+
const { before, after } = entry.changes[field];
|
|
5316
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
5317
|
+
className: "nb-audit-trail__change",
|
|
5318
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5319
|
+
className: "nb-audit-trail__field",
|
|
5320
|
+
children: resolveFieldLabel(field)
|
|
5321
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
5322
|
+
className: "nb-audit-trail__diff",
|
|
5323
|
+
children: [
|
|
5324
|
+
/* @__PURE__ */ jsx("span", {
|
|
5325
|
+
className: "nb-audit-trail__value nb-audit-trail__value--before",
|
|
5326
|
+
children: formatAuditValue(before, yesLabel, noLabel)
|
|
5327
|
+
}),
|
|
5328
|
+
/* @__PURE__ */ jsx("span", {
|
|
5329
|
+
className: "nb-audit-trail__arrow",
|
|
5330
|
+
"aria-hidden": "true",
|
|
5331
|
+
children: "→"
|
|
5332
|
+
}),
|
|
5333
|
+
/* @__PURE__ */ jsx("span", {
|
|
5334
|
+
className: "nb-audit-trail__value nb-audit-trail__value--after",
|
|
5335
|
+
children: formatAuditValue(after, yesLabel, noLabel)
|
|
5336
|
+
})
|
|
5337
|
+
]
|
|
5338
|
+
})]
|
|
5339
|
+
}, field);
|
|
5340
|
+
})
|
|
5341
|
+
});
|
|
5342
|
+
}
|
|
5343
|
+
function DefaultEntryRenderer({ entry, resolveFieldLabel, yesLabel, noLabel }) {
|
|
5295
5344
|
const { t } = useCoreTranslation();
|
|
5296
5345
|
const date = new Date(entry.timestamp);
|
|
5297
5346
|
const formatted = Number.isNaN(date.getTime()) ? entry.timestamp : date.toLocaleString(getCoreLocale(), { timeZone: getCoreTimezone() });
|
|
@@ -5300,177 +5349,159 @@ function DefaultEntryRenderer({ entry }) {
|
|
|
5300
5349
|
update: t("auditTrail.action.update"),
|
|
5301
5350
|
delete: t("auditTrail.action.delete")
|
|
5302
5351
|
};
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
}
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
}), changeKeys.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
5345
|
-
style: {
|
|
5346
|
-
margin: "4px 0 0 0",
|
|
5347
|
-
padding: "0 0 0 16px"
|
|
5348
|
-
},
|
|
5349
|
-
children: changeKeys.map((field) => {
|
|
5350
|
-
const { before, after } = entry.changes[field];
|
|
5351
|
-
return /* @__PURE__ */ jsxs("li", {
|
|
5352
|
-
style: {
|
|
5353
|
-
fontSize: 12,
|
|
5354
|
-
color: "#616161"
|
|
5355
|
-
},
|
|
5356
|
-
children: [
|
|
5357
|
-
/* @__PURE__ */ jsxs("strong", { children: [field, ":"] }),
|
|
5358
|
-
" ",
|
|
5359
|
-
/* @__PURE__ */ jsx("span", {
|
|
5360
|
-
style: { color: "#c62828" },
|
|
5361
|
-
children: String(before ?? "—")
|
|
5362
|
-
}),
|
|
5363
|
-
" → ",
|
|
5364
|
-
/* @__PURE__ */ jsx("span", {
|
|
5365
|
-
style: { color: "#2e7d32" },
|
|
5366
|
-
children: String(after ?? "—")
|
|
5367
|
-
})
|
|
5368
|
-
]
|
|
5369
|
-
}, field);
|
|
5352
|
+
return /* @__PURE__ */ jsx(TimelineItem, {
|
|
5353
|
+
status: "complete",
|
|
5354
|
+
tone: ACTION_TONE[entry.action],
|
|
5355
|
+
title: /* @__PURE__ */ jsxs("span", {
|
|
5356
|
+
className: "nb-audit-trail__meta",
|
|
5357
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5358
|
+
className: "nb-audit-trail__user",
|
|
5359
|
+
children: entry.user
|
|
5360
|
+
}), /* @__PURE__ */ jsx(Badge, {
|
|
5361
|
+
variant: ACTION_BADGE[entry.action],
|
|
5362
|
+
size: "sm",
|
|
5363
|
+
pill: true,
|
|
5364
|
+
children: actionLabels[entry.action]
|
|
5365
|
+
})]
|
|
5366
|
+
}),
|
|
5367
|
+
timestamp: formatted,
|
|
5368
|
+
dateTime: entry.timestamp,
|
|
5369
|
+
children: /* @__PURE__ */ jsx(DefaultEntryContent, {
|
|
5370
|
+
entry,
|
|
5371
|
+
resolveFieldLabel,
|
|
5372
|
+
yesLabel,
|
|
5373
|
+
noLabel
|
|
5374
|
+
})
|
|
5375
|
+
});
|
|
5376
|
+
}
|
|
5377
|
+
function AuditTrailSkeleton() {
|
|
5378
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5379
|
+
className: "nb-audit-trail__skeleton",
|
|
5380
|
+
"aria-busy": "true",
|
|
5381
|
+
children: [
|
|
5382
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
5383
|
+
variant: "rect",
|
|
5384
|
+
height: 72
|
|
5385
|
+
}),
|
|
5386
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
5387
|
+
variant: "rect",
|
|
5388
|
+
height: 72
|
|
5389
|
+
}),
|
|
5390
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
5391
|
+
variant: "rect",
|
|
5392
|
+
height: 72
|
|
5370
5393
|
})
|
|
5371
|
-
|
|
5394
|
+
]
|
|
5372
5395
|
});
|
|
5373
5396
|
}
|
|
5374
|
-
function AuditTrailPanel({ url, renderEntry, visible, onClose }) {
|
|
5397
|
+
function AuditTrailPanel({ url, renderEntry, visible, onClose, recordSubtitle, resolveFieldLabel, drawerSize = "sm" }) {
|
|
5375
5398
|
const { t } = useCoreTranslation();
|
|
5376
5399
|
const httpClient = useCoreHttpClient();
|
|
5377
5400
|
const [fetchState, setFetchState] = useState({ status: "idle" });
|
|
5378
|
-
|
|
5379
|
-
|
|
5401
|
+
const yesLabel = t("common.yes");
|
|
5402
|
+
const noLabel = t("common.no");
|
|
5403
|
+
const loadEntries = useCallback(() => {
|
|
5404
|
+
if (url === null) {
|
|
5380
5405
|
setFetchState({ status: "idle" });
|
|
5381
5406
|
return;
|
|
5382
5407
|
}
|
|
5383
|
-
let cancelled = false;
|
|
5384
5408
|
setFetchState({ status: "loading" });
|
|
5385
5409
|
httpClient.get(url).then((response) => {
|
|
5386
|
-
|
|
5410
|
+
setFetchState({
|
|
5387
5411
|
status: "success",
|
|
5388
5412
|
entries: response.data
|
|
5389
5413
|
});
|
|
5390
5414
|
}).catch(() => {
|
|
5391
|
-
|
|
5415
|
+
setFetchState({ status: "error" });
|
|
5392
5416
|
});
|
|
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: t("auditTrail.empty")
|
|
5464
|
-
}) : /* @__PURE__ */ jsx("ul", {
|
|
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]
|
|
5417
|
+
}, [httpClient, url]);
|
|
5418
|
+
useEffect(() => {
|
|
5419
|
+
if (!visible) {
|
|
5420
|
+
setFetchState({ status: "idle" });
|
|
5421
|
+
return;
|
|
5422
|
+
}
|
|
5423
|
+
loadEntries();
|
|
5424
|
+
}, [loadEntries, visible]);
|
|
5425
|
+
const drawerTitle = /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: t("auditTrail.title") }), recordSubtitle && /* @__PURE__ */ jsx("p", {
|
|
5426
|
+
className: "nb-audit-trail__subtitle",
|
|
5427
|
+
children: recordSubtitle
|
|
5428
|
+
})] });
|
|
5429
|
+
const body = (() => {
|
|
5430
|
+
if (url === null) return /* @__PURE__ */ jsx(EmptyState, {
|
|
5431
|
+
fill: true,
|
|
5432
|
+
icon: "cursor-click",
|
|
5433
|
+
title: t("auditTrail.selectRecord"),
|
|
5434
|
+
description: t("auditTrail.selectRecordHint"),
|
|
5435
|
+
size: "sm"
|
|
5436
|
+
});
|
|
5437
|
+
if (fetchState.status === "loading") return /* @__PURE__ */ jsx(AuditTrailSkeleton, {});
|
|
5438
|
+
if (fetchState.status === "error") return /* @__PURE__ */ jsx("div", {
|
|
5439
|
+
className: "nb-audit-trail__error",
|
|
5440
|
+
children: /* @__PURE__ */ jsx(EmptyState, {
|
|
5441
|
+
fill: true,
|
|
5442
|
+
variant: "danger",
|
|
5443
|
+
icon: "warning-circle",
|
|
5444
|
+
title: t("auditTrail.error"),
|
|
5445
|
+
size: "sm",
|
|
5446
|
+
action: /* @__PURE__ */ jsx(Button, {
|
|
5447
|
+
variant: "secondary",
|
|
5448
|
+
size: "sm",
|
|
5449
|
+
onClick: loadEntries,
|
|
5450
|
+
children: t("auditTrail.retry")
|
|
5451
|
+
})
|
|
5452
|
+
})
|
|
5453
|
+
});
|
|
5454
|
+
if (fetchState.status === "success" && fetchState.entries.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
|
|
5455
|
+
fill: true,
|
|
5456
|
+
icon: "clock-counter-clockwise",
|
|
5457
|
+
title: t("auditTrail.empty"),
|
|
5458
|
+
description: t("auditTrail.emptyHint"),
|
|
5459
|
+
size: "sm"
|
|
5460
|
+
});
|
|
5461
|
+
if (fetchState.status === "success") return /* @__PURE__ */ jsx(Timeline, {
|
|
5462
|
+
variant: "log",
|
|
5463
|
+
"aria-label": t("auditTrail.title"),
|
|
5464
|
+
children: fetchState.entries.map((entry) => renderEntry ? /* @__PURE__ */ jsx(React.Fragment, { children: renderEntry(entry) }, String(entry.id)) : /* @__PURE__ */ jsx(DefaultEntryRenderer, {
|
|
5465
|
+
entry,
|
|
5466
|
+
resolveFieldLabel,
|
|
5467
|
+
yesLabel,
|
|
5468
|
+
noLabel
|
|
5469
|
+
}, String(entry.id)))
|
|
5470
|
+
});
|
|
5471
|
+
return null;
|
|
5472
|
+
})();
|
|
5473
|
+
return /* @__PURE__ */ jsx(Drawer, {
|
|
5474
|
+
isOpen: visible,
|
|
5475
|
+
onClose,
|
|
5476
|
+
title: drawerTitle,
|
|
5477
|
+
width: resolveDrawerWidth({ drawerSize }),
|
|
5478
|
+
side: "right",
|
|
5479
|
+
scrim: "subtle",
|
|
5480
|
+
closeLabel: t("auditTrail.closeButton"),
|
|
5481
|
+
"aria-label": t("auditTrail.title"),
|
|
5482
|
+
className: "nb-audit-trail-drawer",
|
|
5483
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
5484
|
+
className: "nb-audit-trail",
|
|
5485
|
+
children: body
|
|
5486
|
+
})
|
|
5471
5487
|
});
|
|
5472
5488
|
}
|
|
5473
5489
|
//#endregion
|
|
5490
|
+
//#region packages/crud/crud/AuditTrail.ts
|
|
5491
|
+
function createAuditFieldLabelResolver(config, fields) {
|
|
5492
|
+
const fieldLabelByName = new Map(fields.map((field) => [field.name, field.label || field.name]));
|
|
5493
|
+
return (field) => {
|
|
5494
|
+
const fromConfig = config?.fieldLabels;
|
|
5495
|
+
if (fromConfig) {
|
|
5496
|
+
if (typeof fromConfig === "function") {
|
|
5497
|
+
const resolved = fromConfig(field);
|
|
5498
|
+
if (resolved) return resolved;
|
|
5499
|
+
} else if (fromConfig[field]) return fromConfig[field];
|
|
5500
|
+
}
|
|
5501
|
+
return fieldLabelByName.get(field) ?? field;
|
|
5502
|
+
};
|
|
5503
|
+
}
|
|
5504
|
+
//#endregion
|
|
5474
5505
|
//#region packages/crud/crud/dialogStore.ts
|
|
5475
5506
|
function initialDialogState() {
|
|
5476
5507
|
return {
|
|
@@ -5818,13 +5849,30 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5818
5849
|
if (hasAuditTrail) {
|
|
5819
5850
|
const first = rows[0];
|
|
5820
5851
|
if (first != null) setSelectedRowId(first["id"] ?? first["ID"] ?? null);
|
|
5852
|
+
else setSelectedRowId(null);
|
|
5821
5853
|
}
|
|
5822
5854
|
};
|
|
5855
|
+
const selectedRow = selectedRows[0] ?? null;
|
|
5823
5856
|
const auditUrl = useMemo(() => {
|
|
5824
5857
|
if (!resolvedResource.auditTrail?.enabled || selectedRowId == null) return null;
|
|
5825
5858
|
const { apiUrl } = resolvedResource.auditTrail;
|
|
5826
5859
|
return typeof apiUrl === "function" ? apiUrl(selectedRowId) : `${apiUrl}${selectedRowId}`;
|
|
5827
5860
|
}, [resolvedResource.auditTrail, selectedRowId]);
|
|
5861
|
+
const resolveAuditFieldLabel = useMemo(() => createAuditFieldLabelResolver(resolvedResource.auditTrail, routeAwareGridFields), [resolvedResource.auditTrail, routeAwareGridFields]);
|
|
5862
|
+
const auditRecordSubtitle = useMemo(() => {
|
|
5863
|
+
if (!resolvedResource.auditTrail?.recordSubtitle || selectedRow == null) return void 0;
|
|
5864
|
+
return resolvedResource.auditTrail.recordSubtitle(selectedRow);
|
|
5865
|
+
}, [resolvedResource.auditTrail, selectedRow]);
|
|
5866
|
+
const openAuditTrail = useCallback((row) => {
|
|
5867
|
+
if (row != null) {
|
|
5868
|
+
const id = row["id"] ?? row["ID"] ?? null;
|
|
5869
|
+
if (id != null) {
|
|
5870
|
+
setSelectedRowId(id);
|
|
5871
|
+
setSelectedRows([row]);
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
setAuditOpen(true);
|
|
5875
|
+
}, []);
|
|
5828
5876
|
const executeBulkAction = useCallback(async (action) => {
|
|
5829
5877
|
await action.onAction(selectionState.selectedIds);
|
|
5830
5878
|
selectionState.clearSelection();
|
|
@@ -5838,7 +5886,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5838
5886
|
else executeBulkAction(action);
|
|
5839
5887
|
}, [executeBulkAction]);
|
|
5840
5888
|
const toolbar = useMemo(() => {
|
|
5841
|
-
|
|
5889
|
+
const base = resolveResourceToolbar(resolvedResource, {
|
|
5842
5890
|
resource: resolvedResource,
|
|
5843
5891
|
selectedRow: selectedRows[0],
|
|
5844
5892
|
selectedRows,
|
|
@@ -5846,14 +5894,48 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5846
5894
|
formRef,
|
|
5847
5895
|
events,
|
|
5848
5896
|
emit
|
|
5849
|
-
});
|
|
5897
|
+
}) ?? {};
|
|
5898
|
+
if (!hasAuditTrail) return base;
|
|
5899
|
+
return {
|
|
5900
|
+
...base,
|
|
5901
|
+
utility: [...base.utility ?? [], {
|
|
5902
|
+
key: "audit-trail",
|
|
5903
|
+
text: t("crudPage.auditTrailButton"),
|
|
5904
|
+
icon: "ph-clock-counter-clockwise",
|
|
5905
|
+
hint: t("auditTrail.toolbarHint"),
|
|
5906
|
+
disabled: selectedRowId == null,
|
|
5907
|
+
onClick: () => openAuditTrail()
|
|
5908
|
+
}]
|
|
5909
|
+
};
|
|
5850
5910
|
}, [
|
|
5851
5911
|
emit,
|
|
5852
5912
|
events,
|
|
5853
5913
|
formRef,
|
|
5854
5914
|
gridRef,
|
|
5915
|
+
hasAuditTrail,
|
|
5916
|
+
openAuditTrail,
|
|
5855
5917
|
resolvedResource,
|
|
5856
|
-
|
|
5918
|
+
selectedRowId,
|
|
5919
|
+
selectedRows,
|
|
5920
|
+
t
|
|
5921
|
+
]);
|
|
5922
|
+
const rowActions = useMemo(() => {
|
|
5923
|
+
const base = resolvedResource.rowActions;
|
|
5924
|
+
if (!hasAuditTrail || resolvedResource.auditTrail?.rowAction === false) return base;
|
|
5925
|
+
const auditAction = (row) => ({
|
|
5926
|
+
key: "audit-trail",
|
|
5927
|
+
text: t("auditTrail.rowAction"),
|
|
5928
|
+
icon: "ph-clock-counter-clockwise",
|
|
5929
|
+
onClick: () => openAuditTrail(row)
|
|
5930
|
+
});
|
|
5931
|
+
if (typeof base === "function") return (row) => [...base(row), auditAction(row)];
|
|
5932
|
+
return (row) => [...base ?? [], auditAction(row)];
|
|
5933
|
+
}, [
|
|
5934
|
+
hasAuditTrail,
|
|
5935
|
+
openAuditTrail,
|
|
5936
|
+
resolvedResource.auditTrail?.rowAction,
|
|
5937
|
+
resolvedResource.rowActions,
|
|
5938
|
+
t
|
|
5857
5939
|
]);
|
|
5858
5940
|
/** Preset selector rendered as a toolbar slot via `beforeToolbar`. */
|
|
5859
5941
|
const renderPresetSelector = hasPresets ? () => /* @__PURE__ */ jsx(ColumnPresetSelector, {
|
|
@@ -5866,7 +5948,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5866
5948
|
}) : void 0;
|
|
5867
5949
|
return /* @__PURE__ */ jsxs("div", {
|
|
5868
5950
|
id: "wrapper",
|
|
5869
|
-
className: [
|
|
5951
|
+
className: [viewMode.mode === "page" && dialogIsOpen && "wrapper--with-page"].filter(Boolean).join(" ") || void 0,
|
|
5870
5952
|
children: [
|
|
5871
5953
|
(resolvedResource.bulkActions?.length ?? 0) > 0 && selectionState.hasSelection && /* @__PURE__ */ jsxs("div", {
|
|
5872
5954
|
className: "nb-bulk-toolbar",
|
|
@@ -5883,16 +5965,6 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5883
5965
|
children: action.label
|
|
5884
5966
|
}, action.key))]
|
|
5885
5967
|
}),
|
|
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
5968
|
/* @__PURE__ */ jsx(NativeDataGridView, {
|
|
5897
5969
|
ref: gridRef,
|
|
5898
5970
|
id: resolvedResource.id,
|
|
@@ -5917,7 +5989,7 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5917
5989
|
mode: resolvedResource.mode,
|
|
5918
5990
|
stateStoringEnabled: resolvedResource.stateStoring,
|
|
5919
5991
|
toolbar,
|
|
5920
|
-
rowActions
|
|
5992
|
+
rowActions,
|
|
5921
5993
|
onAdd: requestNew,
|
|
5922
5994
|
onEdit: requestEdit,
|
|
5923
5995
|
onView: requestView,
|
|
@@ -5990,7 +6062,10 @@ const CrudPageInner = ({ resource, onFormDataChange, initialRecordId = null, ini
|
|
|
5990
6062
|
url: auditUrl,
|
|
5991
6063
|
renderEntry: resolvedResource.auditTrail.renderEntry,
|
|
5992
6064
|
visible: auditOpen,
|
|
5993
|
-
onClose: () => setAuditOpen(false)
|
|
6065
|
+
onClose: () => setAuditOpen(false),
|
|
6066
|
+
recordSubtitle: auditRecordSubtitle,
|
|
6067
|
+
resolveFieldLabel: resolveAuditFieldLabel,
|
|
6068
|
+
drawerSize: resolvedResource.auditTrail?.drawerSize
|
|
5994
6069
|
}),
|
|
5995
6070
|
/* @__PURE__ */ jsx(ConfirmDialog, {
|
|
5996
6071
|
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,99 @@ 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__meta {
|
|
3309
|
+
align-items: center;
|
|
3310
|
+
display: flex;
|
|
3311
|
+
flex-wrap: wrap;
|
|
3312
|
+
gap: var(--space-2);
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
.nb-audit-trail__user {
|
|
3316
|
+
color: var(--text-primary);
|
|
3317
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
3318
|
+
font-weight: 500;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
.nb-audit-trail__changes {
|
|
3322
|
+
display: flex;
|
|
3323
|
+
flex-direction: column;
|
|
3324
|
+
gap: var(--space-2);
|
|
3325
|
+
list-style: none;
|
|
3326
|
+
margin: 0;
|
|
3327
|
+
padding: 0;
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
.nb-audit-trail__change {
|
|
3331
|
+
background: var(--surface-2);
|
|
3332
|
+
border: 1px solid var(--border-color);
|
|
3333
|
+
border-radius: var(--radius-md, 6px);
|
|
3334
|
+
display: grid;
|
|
3335
|
+
gap: var(--space-1);
|
|
3336
|
+
padding: var(--space-2) var(--space-3);
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
.nb-audit-trail__field {
|
|
3340
|
+
color: var(--text-primary);
|
|
3341
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
3342
|
+
font-weight: 600;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
.nb-audit-trail__diff {
|
|
3346
|
+
align-items: center;
|
|
3347
|
+
color: var(--text-secondary);
|
|
3348
|
+
display: flex;
|
|
3349
|
+
flex-wrap: wrap;
|
|
3350
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
3351
|
+
gap: var(--space-2);
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
.nb-audit-trail__value {
|
|
3355
|
+
font-variant-numeric: tabular-nums;
|
|
3356
|
+
max-width: 100%;
|
|
3357
|
+
overflow-wrap: anywhere;
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
.nb-audit-trail__value--before {
|
|
3361
|
+
color: var(--danger-color);
|
|
3362
|
+
text-decoration: line-through;
|
|
3363
|
+
text-decoration-color: color-mix(in srgb, var(--danger-color) 55%, transparent);
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
.nb-audit-trail__value--after {
|
|
3367
|
+
color: var(--success-color);
|
|
3368
|
+
font-weight: 500;
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
.nb-audit-trail__arrow {
|
|
3372
|
+
color: var(--text-tertiary);
|
|
3373
|
+
flex: 0 0 auto;
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
.nb-audit-trail__skeleton {
|
|
3377
|
+
display: flex;
|
|
3378
|
+
flex-direction: column;
|
|
3379
|
+
gap: var(--space-3);
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
.nb-audit-trail__error {
|
|
3383
|
+
display: flex;
|
|
3384
|
+
flex-direction: column;
|
|
3385
|
+
gap: var(--space-3);
|
|
3386
|
+
}
|
|
3295
3387
|
.nb-smart-crud-fallback {
|
|
3296
3388
|
display: flex;
|
|
3297
3389
|
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.2",
|
|
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.2",
|
|
60
|
+
"@nubitio/ui": "^0.5.2"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"react-dropzone": "^15.0.0"
|