@proveanything/smartlinks-utils-ui 0.9.1 → 0.9.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.
|
@@ -424,6 +424,14 @@ interface RecordsAdminI18n {
|
|
|
424
424
|
rulesTabTooltip: string;
|
|
425
425
|
/** Empty-state body shown inside the Rules tab when no rules exist yet. */
|
|
426
426
|
rulesEmptyBody: string;
|
|
427
|
+
/**
|
|
428
|
+
* Lifecycle-hook failure toasts. `{message}` is replaced with the
|
|
429
|
+
* thrown error's message (or a generic fallback when none is provided).
|
|
430
|
+
*/
|
|
431
|
+
hookBeforeSaveFailed: string;
|
|
432
|
+
hookAfterSaveFailed: string;
|
|
433
|
+
hookBeforeDeleteFailed: string;
|
|
434
|
+
hookAfterDeleteFailed: string;
|
|
427
435
|
}
|
|
428
436
|
declare const DEFAULT_I18N: RecordsAdminI18n;
|
|
429
437
|
|
|
@@ -555,6 +563,54 @@ declare function mergeIcons(override?: Partial<RecordsAdminIcons>): RecordsAdmin
|
|
|
555
563
|
* explicit `headerIcon` > `icons.header.byRecordType[type]` > `icons.header.default`. */
|
|
556
564
|
declare function pickHeaderIcon(icons: RecordsAdminIcons, recordType?: string): LucideIcon;
|
|
557
565
|
|
|
566
|
+
/** Common context fields passed to every lifecycle hook. */
|
|
567
|
+
interface RecordHookCtx {
|
|
568
|
+
collectionId: string;
|
|
569
|
+
appId: string;
|
|
570
|
+
recordType?: string;
|
|
571
|
+
/** Editing scope kind (`'global' | 'rule' | 'product' | …`). */
|
|
572
|
+
scope: ScopeKind;
|
|
573
|
+
/** Resolved scope ref string when available (e.g. `product:abc`). Most
|
|
574
|
+
* hosts ignore this; provided for parity with non-shell callers. */
|
|
575
|
+
targetRef?: string;
|
|
576
|
+
/** Always `true` from inside the shell; included for symmetry with
|
|
577
|
+
* future non-shell callers. */
|
|
578
|
+
admin: true;
|
|
579
|
+
}
|
|
580
|
+
interface SaveHookCtx<T = unknown> extends RecordHookCtx {
|
|
581
|
+
isCreate: boolean;
|
|
582
|
+
/** Record as it WILL be (in `beforeSave`) or HAS been (in `afterSave`)
|
|
583
|
+
* written. `id` is the resolved UUID when known, otherwise undefined. */
|
|
584
|
+
record: RecordSummary<T>;
|
|
585
|
+
/** Snapshot of the record's previous server state. Undefined on create. */
|
|
586
|
+
before?: RecordSummary<T>;
|
|
587
|
+
}
|
|
588
|
+
interface DeleteHookCtx<T = unknown> extends RecordHookCtx {
|
|
589
|
+
/** Snapshot of the record taken immediately before deletion. */
|
|
590
|
+
record: RecordSummary<T>;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Opt-in lifecycle hooks for `RecordsAdminShell`. All four are awaited.
|
|
594
|
+
*
|
|
595
|
+
* - `beforeSave` — return `false` (or throw) to cancel the write. Return
|
|
596
|
+
* a `Partial<T>` to merge into the record's `data` payload before it
|
|
597
|
+
* hits the server (e.g. inject computed `status`, normalised dates).
|
|
598
|
+
* - `afterSave` — runs after a successful write. Throwing surfaces a
|
|
599
|
+
* toast but does NOT roll back the server state.
|
|
600
|
+
* - `beforeDelete` — return `false` (or throw) to cancel the delete.
|
|
601
|
+
* - `afterDelete` — runs after a successful delete. Throwing surfaces a
|
|
602
|
+
* toast; the record is already gone.
|
|
603
|
+
*
|
|
604
|
+
* `data` is the only host-shaped portion of the payload — anchors and
|
|
605
|
+
* `facetRule` are owned by the shell, so `beforeSave` returns a partial
|
|
606
|
+
* of the typed `data` shape, not of the full SDK record input.
|
|
607
|
+
*/
|
|
608
|
+
interface RecordsAdminShellHooks<T = unknown> {
|
|
609
|
+
beforeSave?: (ctx: SaveHookCtx<T>) => Promise<Partial<T> | false | void> | Partial<T> | false | void;
|
|
610
|
+
afterSave?: (ctx: SaveHookCtx<T>) => Promise<void> | void;
|
|
611
|
+
beforeDelete?: (ctx: DeleteHookCtx<T>) => Promise<false | void> | false | void;
|
|
612
|
+
afterDelete?: (ctx: DeleteHookCtx<T>) => Promise<void> | void;
|
|
613
|
+
}
|
|
558
614
|
/**
|
|
559
615
|
* Footer / banner action keys whose label and icon can be customised by
|
|
560
616
|
* the host. `save`, `discard`, `delete` are wired today. `saveAll` /
|
|
@@ -714,6 +770,15 @@ interface RecordsAdminShellProps<TData = unknown> {
|
|
|
714
770
|
unsaved?: UnsavedConfig<TData>;
|
|
715
771
|
clipboard?: ClipboardConfig<TData>;
|
|
716
772
|
actions?: ActionsConfig;
|
|
773
|
+
/**
|
|
774
|
+
* Opt-in awaited callbacks fired around record save/delete. See
|
|
775
|
+
* {@link RecordsAdminShellHooks} for the contract.
|
|
776
|
+
*
|
|
777
|
+
* Today these fire on the single-record paths (editor footer Save/Delete
|
|
778
|
+
* and the right-pane item-list trash). Bulk paths (CSV import, paste,
|
|
779
|
+
* Save-all) do NOT fire hooks yet — tracked as a follow-up.
|
|
780
|
+
*/
|
|
781
|
+
hooks?: RecordsAdminShellHooks<TData>;
|
|
717
782
|
/**
|
|
718
783
|
* Mirror the shell's runtime state into URL params. Off by default. The
|
|
719
784
|
* shell only owns `item`, `scope`, `view` — host platform params are
|
|
@@ -2134,6 +2199,12 @@ interface Props$5<T> {
|
|
|
2134
2199
|
error: Error | null;
|
|
2135
2200
|
ctx: ItemViewContext;
|
|
2136
2201
|
itemNoun: string;
|
|
2202
|
+
/**
|
|
2203
|
+
* Optional one-line rule summary shown beneath the toolbar — used in
|
|
2204
|
+
* collection-mode when an item list is scoped to a specific rule, so the
|
|
2205
|
+
* admin sees which rule's items they're viewing without leaving the pane.
|
|
2206
|
+
*/
|
|
2207
|
+
ruleSummary?: string | null;
|
|
2137
2208
|
view: ItemView;
|
|
2138
2209
|
views: ItemView[];
|
|
2139
2210
|
onViewChange: (view: ItemView) => void;
|
|
@@ -2145,7 +2216,7 @@ interface Props$5<T> {
|
|
|
2145
2216
|
cardSize?: 'sm' | 'md' | 'lg';
|
|
2146
2217
|
i18n: RecordsAdminI18n;
|
|
2147
2218
|
}
|
|
2148
|
-
declare function ItemListView<T>({ items, isLoading, error, ctx, itemNoun, view, views, onViewChange, renderItemList, renderItemCard, renderItemEmpty, itemColumns, cardSize, i18n, }: Props$5<T>): react_jsx_runtime.JSX.Element;
|
|
2219
|
+
declare function ItemListView<T>({ items, isLoading, error, ctx, itemNoun, ruleSummary, view, views, onViewChange, renderItemList, renderItemCard, renderItemEmpty, itemColumns, cardSize, i18n, }: Props$5<T>): react_jsx_runtime.JSX.Element;
|
|
2149
2220
|
|
|
2150
2221
|
interface Props$4 {
|
|
2151
2222
|
options: ItemView[];
|
|
@@ -146,7 +146,11 @@ var DEFAULT_I18N = {
|
|
|
146
146
|
subtitleConfigured: "Configured",
|
|
147
147
|
subtitleInherited: "Inherited",
|
|
148
148
|
rulesTabTooltip: "Use rules to scope records to a targeted audience or a subset of products.",
|
|
149
|
-
rulesEmptyBody: "Create a rule to target a subset of products or a specific audience \u2014 for example, \u201Conly premium tier customers in Germany\u201D."
|
|
149
|
+
rulesEmptyBody: "Create a rule to target a subset of products or a specific audience \u2014 for example, \u201Conly premium tier customers in Germany\u201D.",
|
|
150
|
+
hookBeforeSaveFailed: "Couldn't save: {message}",
|
|
151
|
+
hookAfterSaveFailed: "Saved, but a follow-up step failed: {message}",
|
|
152
|
+
hookBeforeDeleteFailed: "Couldn't delete: {message}",
|
|
153
|
+
hookAfterDeleteFailed: "Deleted, but a follow-up step failed: {message}"
|
|
150
154
|
};
|
|
151
155
|
|
|
152
156
|
// src/components/RecordsAdmin/types/presentation.ts
|
|
@@ -1819,7 +1823,8 @@ function useShellNavigation(args) {
|
|
|
1819
1823
|
deepLinkState,
|
|
1820
1824
|
onTelemetry,
|
|
1821
1825
|
onBeforeDelete,
|
|
1822
|
-
generateItemId
|
|
1826
|
+
generateItemId,
|
|
1827
|
+
hooks
|
|
1823
1828
|
} = args;
|
|
1824
1829
|
const buildItemUrlValue = useCallback((id) => {
|
|
1825
1830
|
if (!baseScopeRef && id.startsWith("item:")) return id;
|
|
@@ -1904,6 +1909,34 @@ function useShellNavigation(args) {
|
|
|
1904
1909
|
const ok = await onBeforeDelete(editingScope ?? parseRef(""));
|
|
1905
1910
|
if (!ok) return;
|
|
1906
1911
|
}
|
|
1912
|
+
const row = collectionItems.items.find(
|
|
1913
|
+
(it) => it.itemId === itemId || it.id === itemId
|
|
1914
|
+
);
|
|
1915
|
+
const hookCtxBase = {
|
|
1916
|
+
collectionId: ctx.collectionId,
|
|
1917
|
+
appId: ctx.appId,
|
|
1918
|
+
recordType: ctx.recordType,
|
|
1919
|
+
scope: editingScope?.kind ?? "collection",
|
|
1920
|
+
targetRef: editingScope?.raw || void 0,
|
|
1921
|
+
admin: true
|
|
1922
|
+
};
|
|
1923
|
+
const recordSummary = row ?? {
|
|
1924
|
+
id: itemId,
|
|
1925
|
+
ref: editingScope?.raw ?? "",
|
|
1926
|
+
scope: editingScope ?? parseRef(""),
|
|
1927
|
+
data: null,
|
|
1928
|
+
status: "configured",
|
|
1929
|
+
label: itemId
|
|
1930
|
+
};
|
|
1931
|
+
if (hooks?.beforeDelete) {
|
|
1932
|
+
try {
|
|
1933
|
+
const result = await hooks.beforeDelete({ ...hookCtxBase, record: recordSummary });
|
|
1934
|
+
if (result === false) return;
|
|
1935
|
+
} catch (err) {
|
|
1936
|
+
console.warn("[RecordsAdmin] item beforeDelete hook cancelled delete", err);
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1907
1940
|
try {
|
|
1908
1941
|
const { removeRecord: removeRecord2 } = await import('../../records-AYYQSP7E.js');
|
|
1909
1942
|
await removeRecord2(ctx, itemId);
|
|
@@ -1911,6 +1944,13 @@ function useShellNavigation(args) {
|
|
|
1911
1944
|
if (selectedItemId === itemId) setSelectedItemId(null);
|
|
1912
1945
|
if (selectedItemId === itemId) deepLinkState.emit({ recordId: null }, "record.close");
|
|
1913
1946
|
collectionItems.refetch();
|
|
1947
|
+
if (hooks?.afterDelete) {
|
|
1948
|
+
try {
|
|
1949
|
+
await hooks.afterDelete({ ...hookCtxBase, record: recordSummary });
|
|
1950
|
+
} catch (err) {
|
|
1951
|
+
console.warn("[RecordsAdmin] item afterDelete hook failed", err);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1914
1954
|
} catch (err) {
|
|
1915
1955
|
console.error("[RecordsAdminShell] item delete failed", err);
|
|
1916
1956
|
}
|
|
@@ -1925,7 +1965,8 @@ function useShellNavigation(args) {
|
|
|
1925
1965
|
collectionItems,
|
|
1926
1966
|
deepLinkState,
|
|
1927
1967
|
editingScope,
|
|
1928
|
-
setSelectedItemId
|
|
1968
|
+
setSelectedItemId,
|
|
1969
|
+
hooks
|
|
1929
1970
|
]);
|
|
1930
1971
|
const itemViewCtx = useMemo(() => ({
|
|
1931
1972
|
onOpen: onItemOpen,
|
|
@@ -2231,6 +2272,25 @@ var createEditorStore = () => {
|
|
|
2231
2272
|
const listeners = /* @__PURE__ */ new Set();
|
|
2232
2273
|
let cachedList = [];
|
|
2233
2274
|
let nextOrder = 0;
|
|
2275
|
+
let hooksBundle = null;
|
|
2276
|
+
let notifier = null;
|
|
2277
|
+
const buildRecordSummary = (entry, value) => ({
|
|
2278
|
+
id: entry.recordId ?? null,
|
|
2279
|
+
ref: entry.spec.scope.raw,
|
|
2280
|
+
scope: entry.spec.scope,
|
|
2281
|
+
label: entry.label,
|
|
2282
|
+
status: "configured",
|
|
2283
|
+
data: value,
|
|
2284
|
+
facetRule: entry.facetRule
|
|
2285
|
+
});
|
|
2286
|
+
const buildHookCtxBase = (entry) => ({
|
|
2287
|
+
collectionId: entry.saveSpec.ctx.collectionId,
|
|
2288
|
+
appId: entry.saveSpec.ctx.appId,
|
|
2289
|
+
recordType: entry.saveSpec.ctx.recordType,
|
|
2290
|
+
scope: entry.spec.scope.kind,
|
|
2291
|
+
targetRef: entry.spec.scope.raw || void 0,
|
|
2292
|
+
admin: true
|
|
2293
|
+
});
|
|
2234
2294
|
const recompute = () => {
|
|
2235
2295
|
cachedList = Array.from(map.values()).sort((a, b) => a.order - b.order);
|
|
2236
2296
|
};
|
|
@@ -2384,10 +2444,35 @@ var createEditorStore = () => {
|
|
|
2384
2444
|
const entry = map.get(editorId);
|
|
2385
2445
|
if (!entry) return;
|
|
2386
2446
|
if (entry.status !== "dirty" && entry.status !== "error") return;
|
|
2387
|
-
|
|
2447
|
+
let persistedValue = entry.value;
|
|
2388
2448
|
const persistedFacetRule = entry.facetRule;
|
|
2389
2449
|
const { ctx, anchors } = entry.saveSpec;
|
|
2390
2450
|
const spec = entry.spec;
|
|
2451
|
+
const isCreate = !(entry.recordId && entry.source === "self") || !!spec.createMode;
|
|
2452
|
+
if (hooksBundle?.beforeSave) {
|
|
2453
|
+
try {
|
|
2454
|
+
const hookCtx = {
|
|
2455
|
+
...buildHookCtxBase(entry),
|
|
2456
|
+
isCreate,
|
|
2457
|
+
record: buildRecordSummary(entry, persistedValue),
|
|
2458
|
+
before: !isCreate ? buildRecordSummary(entry, entry.baseline) : void 0
|
|
2459
|
+
};
|
|
2460
|
+
const result = await hooksBundle.beforeSave(hookCtx);
|
|
2461
|
+
if (result === false) {
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
if (result && typeof result === "object") {
|
|
2465
|
+
persistedValue = persistedValue && typeof persistedValue === "object" ? { ...persistedValue, ...result } : result;
|
|
2466
|
+
}
|
|
2467
|
+
} catch (err) {
|
|
2468
|
+
notifier?.({ kind: "hook-before-save", error: err, recordLabel: entry.label });
|
|
2469
|
+
update(editorId, (e) => {
|
|
2470
|
+
e.status = "error";
|
|
2471
|
+
e.error = err;
|
|
2472
|
+
});
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2391
2476
|
update(editorId, (e) => {
|
|
2392
2477
|
e.status = "saving";
|
|
2393
2478
|
e.error = void 0;
|
|
@@ -2424,6 +2509,9 @@ var createEditorStore = () => {
|
|
|
2424
2509
|
nextRecordId = upserted.record?.id ?? nextRecordId;
|
|
2425
2510
|
}
|
|
2426
2511
|
update(editorId, (e) => {
|
|
2512
|
+
if (persistedValue !== entry.value) {
|
|
2513
|
+
e.value = cloneDeep(persistedValue);
|
|
2514
|
+
}
|
|
2427
2515
|
e.baseline = cloneDeep(e.value);
|
|
2428
2516
|
e.baselineFacetRule = e.facetRule;
|
|
2429
2517
|
e.recordId = nextRecordId;
|
|
@@ -2431,6 +2519,21 @@ var createEditorStore = () => {
|
|
|
2431
2519
|
e.status = "saved";
|
|
2432
2520
|
e.error = void 0;
|
|
2433
2521
|
});
|
|
2522
|
+
if (hooksBundle?.afterSave) {
|
|
2523
|
+
const refreshed = map.get(editorId);
|
|
2524
|
+
const hookCtx = {
|
|
2525
|
+
...buildHookCtxBase(refreshed ?? entry),
|
|
2526
|
+
isCreate,
|
|
2527
|
+
record: buildRecordSummary(refreshed ?? entry, persistedValue),
|
|
2528
|
+
before: !isCreate ? buildRecordSummary(entry, entry.baseline) : void 0
|
|
2529
|
+
};
|
|
2530
|
+
try {
|
|
2531
|
+
await hooksBundle.afterSave(hookCtx);
|
|
2532
|
+
} catch (err) {
|
|
2533
|
+
notifier?.({ kind: "hook-after-save", error: err, recordLabel: entry.label });
|
|
2534
|
+
console.warn("[RecordsAdmin] afterSave hook failed", err);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2434
2537
|
} catch (err) {
|
|
2435
2538
|
update(editorId, (e) => {
|
|
2436
2539
|
e.status = "error";
|
|
@@ -2452,9 +2555,35 @@ var createEditorStore = () => {
|
|
|
2452
2555
|
if (!entry) return;
|
|
2453
2556
|
if (entry.source !== "self") return;
|
|
2454
2557
|
if (!entry.recordId) return;
|
|
2558
|
+
if (hooksBundle?.beforeDelete) {
|
|
2559
|
+
try {
|
|
2560
|
+
const hookCtx = {
|
|
2561
|
+
...buildHookCtxBase(entry),
|
|
2562
|
+
record: buildRecordSummary(entry, entry.value)
|
|
2563
|
+
};
|
|
2564
|
+
const result = await hooksBundle.beforeDelete(hookCtx);
|
|
2565
|
+
if (result === false) return;
|
|
2566
|
+
} catch (err) {
|
|
2567
|
+
notifier?.({ kind: "hook-before-delete", error: err, recordLabel: entry.label });
|
|
2568
|
+
console.warn("[RecordsAdmin] beforeDelete hook cancelled delete", err);
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
const deletedSnapshot = hooksBundle?.afterDelete ? {
|
|
2573
|
+
...buildHookCtxBase(entry),
|
|
2574
|
+
record: buildRecordSummary(entry, entry.value)
|
|
2575
|
+
} : null;
|
|
2455
2576
|
await removeRecord(entry.saveSpec.ctx, entry.recordId);
|
|
2456
2577
|
map.delete(editorId);
|
|
2457
2578
|
emit();
|
|
2579
|
+
if (hooksBundle?.afterDelete && deletedSnapshot) {
|
|
2580
|
+
try {
|
|
2581
|
+
await hooksBundle.afterDelete(deletedSnapshot);
|
|
2582
|
+
} catch (err) {
|
|
2583
|
+
notifier?.({ kind: "hook-after-delete", error: err, recordLabel: entry.label });
|
|
2584
|
+
console.warn("[RecordsAdmin] afterDelete hook failed", err);
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2458
2587
|
},
|
|
2459
2588
|
enforcePoolLimit(max, exceptEditorId) {
|
|
2460
2589
|
const alive = Array.from(map.values());
|
|
@@ -2475,6 +2604,12 @@ var createEditorStore = () => {
|
|
|
2475
2604
|
return () => {
|
|
2476
2605
|
listeners.delete(listener);
|
|
2477
2606
|
};
|
|
2607
|
+
},
|
|
2608
|
+
setHooks(hooks) {
|
|
2609
|
+
hooksBundle = hooks ?? null;
|
|
2610
|
+
},
|
|
2611
|
+
setNotifier(notify2) {
|
|
2612
|
+
notifier = notify2 ?? null;
|
|
2478
2613
|
}
|
|
2479
2614
|
};
|
|
2480
2615
|
};
|
|
@@ -2496,10 +2631,14 @@ var EditorSessionProvider = ({
|
|
|
2496
2631
|
ctx,
|
|
2497
2632
|
children,
|
|
2498
2633
|
maxOpenEditors = 8,
|
|
2499
|
-
defaultValueFactory
|
|
2634
|
+
defaultValueFactory,
|
|
2635
|
+
hooks,
|
|
2636
|
+
onHookNotice
|
|
2500
2637
|
}) => {
|
|
2501
2638
|
const storeRef = useRef(null);
|
|
2502
2639
|
if (!storeRef.current) storeRef.current = createEditorStore();
|
|
2640
|
+
storeRef.current.setHooks(hooks ?? null);
|
|
2641
|
+
storeRef.current.setNotifier(onHookNotice ?? null);
|
|
2503
2642
|
const currentRef = useRef(void 0);
|
|
2504
2643
|
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
2505
2644
|
const subscribeCurrent = useCallback((cb) => {
|
|
@@ -4100,8 +4239,15 @@ function RecordEditor({
|
|
|
4100
4239
|
const DeleteIcon = actionIcons?.delete;
|
|
4101
4240
|
const showInherited = ctx.source === "inherited";
|
|
4102
4241
|
const showEmpty = ctx.source === "empty";
|
|
4242
|
+
const hasBreadcrumb = (() => {
|
|
4243
|
+
const s = ctx.scope;
|
|
4244
|
+
return Boolean(s?.facetId || s?.productId || s?.variantId || s?.batchId);
|
|
4245
|
+
})();
|
|
4246
|
+
const hasLeftContent = Boolean(headerLabel) || hasBreadcrumb;
|
|
4247
|
+
const hasRightContent = showInherited || showEmpty || Boolean(headerMeta) || Boolean(bulkActions) || Boolean(targetingControl);
|
|
4248
|
+
const showHeader = hasLeftContent || hasRightContent;
|
|
4103
4249
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
|
|
4104
|
-
/* @__PURE__ */ jsxs(
|
|
4250
|
+
showHeader && /* @__PURE__ */ jsxs(
|
|
4105
4251
|
"header",
|
|
4106
4252
|
{
|
|
4107
4253
|
className: "sticky top-0 z-40 px-5 py-3 border-b flex items-start justify-between gap-3",
|
|
@@ -5309,6 +5455,7 @@ function ItemListView({
|
|
|
5309
5455
|
error,
|
|
5310
5456
|
ctx,
|
|
5311
5457
|
itemNoun,
|
|
5458
|
+
ruleSummary,
|
|
5312
5459
|
view,
|
|
5313
5460
|
views,
|
|
5314
5461
|
onViewChange,
|
|
@@ -5406,6 +5553,27 @@ function ItemListView({
|
|
|
5406
5553
|
}
|
|
5407
5554
|
return /* @__PURE__ */ jsxs("div", { className: "ra-item-list", children: [
|
|
5408
5555
|
toolbar,
|
|
5556
|
+
ruleSummary ? /* @__PURE__ */ jsxs(
|
|
5557
|
+
"div",
|
|
5558
|
+
{
|
|
5559
|
+
className: "ra-item-rule-summary",
|
|
5560
|
+
style: {
|
|
5561
|
+
padding: "6px 12px",
|
|
5562
|
+
fontSize: "11px",
|
|
5563
|
+
color: "hsl(var(--ra-muted-text))",
|
|
5564
|
+
borderBottom: "1px solid hsl(var(--ra-border))",
|
|
5565
|
+
background: "hsl(var(--ra-muted) / 0.4)",
|
|
5566
|
+
display: "flex",
|
|
5567
|
+
alignItems: "center",
|
|
5568
|
+
gap: "6px"
|
|
5569
|
+
},
|
|
5570
|
+
children: [
|
|
5571
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 500, color: "hsl(var(--ra-text))" }, children: "Rule" }),
|
|
5572
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
5573
|
+
/* @__PURE__ */ jsx("span", { children: ruleSummary })
|
|
5574
|
+
]
|
|
5575
|
+
}
|
|
5576
|
+
) : null,
|
|
5409
5577
|
/* @__PURE__ */ jsx("div", { className: "ra-item-list-body", children: body })
|
|
5410
5578
|
] });
|
|
5411
5579
|
}
|
|
@@ -6730,6 +6898,19 @@ function RecordsAdminShell(props) {
|
|
|
6730
6898
|
{
|
|
6731
6899
|
ctx,
|
|
6732
6900
|
defaultValueFactory: props.defaultData,
|
|
6901
|
+
hooks: props.hooks,
|
|
6902
|
+
onHookNotice: (notice) => {
|
|
6903
|
+
console.warn(`[RecordsAdmin] ${notice.kind} hook failed`, notice.error);
|
|
6904
|
+
try {
|
|
6905
|
+
props.onTelemetry?.({
|
|
6906
|
+
type: "record.hook.error",
|
|
6907
|
+
recordType: props.recordType,
|
|
6908
|
+
kind: notice.kind,
|
|
6909
|
+
message: notice.error instanceof Error ? notice.error.message : String(notice.error)
|
|
6910
|
+
});
|
|
6911
|
+
} catch {
|
|
6912
|
+
}
|
|
6913
|
+
},
|
|
6733
6914
|
children: /* @__PURE__ */ jsx(RecordsAdminShellInner, { ...props })
|
|
6734
6915
|
}
|
|
6735
6916
|
);
|
|
@@ -7290,7 +7471,8 @@ function RecordsAdminShellInner(props) {
|
|
|
7290
7471
|
deepLinkState,
|
|
7291
7472
|
onTelemetry,
|
|
7292
7473
|
onBeforeDelete,
|
|
7293
|
-
generateItemId
|
|
7474
|
+
generateItemId,
|
|
7475
|
+
hooks: props.hooks
|
|
7294
7476
|
});
|
|
7295
7477
|
const renderEditorWithPreview = () => {
|
|
7296
7478
|
if (!editingTargetScope) return null;
|
|
@@ -7497,12 +7679,13 @@ function RecordsAdminShellInner(props) {
|
|
|
7497
7679
|
if (groupBy) return groupBy;
|
|
7498
7680
|
if (isAllTab) return void 0;
|
|
7499
7681
|
if (!isRuleTab) return void 0;
|
|
7682
|
+
if (isCollection) return void 0;
|
|
7500
7683
|
return (record) => {
|
|
7501
7684
|
const hash = ruleHash(record.facetRule);
|
|
7502
7685
|
if (!hash) return null;
|
|
7503
7686
|
return { key: `rule:${hash}`, label: summariseRule(record.facetRule) };
|
|
7504
7687
|
};
|
|
7505
|
-
}, [groupBy, isRuleTab, isAllTab]);
|
|
7688
|
+
}, [groupBy, isRuleTab, isAllTab, isCollection]);
|
|
7506
7689
|
const [bulkRuleEditTarget, setBulkRuleEditTarget] = useState(null);
|
|
7507
7690
|
const ruleCatalogue = useMemo(() => {
|
|
7508
7691
|
const buckets = /* @__PURE__ */ new Map();
|
|
@@ -7601,6 +7784,32 @@ function RecordsAdminShellInner(props) {
|
|
|
7601
7784
|
() => isRuleTab ? applyRuleFilters(recordList.items, ruleFilters) : recordList.items,
|
|
7602
7785
|
[isRuleTab, recordList.items, ruleFilters]
|
|
7603
7786
|
);
|
|
7787
|
+
const collectionRuleRailItems = useMemo(() => {
|
|
7788
|
+
if (!isRuleTab || !isCollection) return filteredRuleItems;
|
|
7789
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
7790
|
+
for (const item of filteredRuleItems) {
|
|
7791
|
+
const hash = ruleHash(item.facetRule);
|
|
7792
|
+
if (!hash) continue;
|
|
7793
|
+
const existing = buckets.get(hash);
|
|
7794
|
+
if (existing) {
|
|
7795
|
+
existing.count += 1;
|
|
7796
|
+
} else {
|
|
7797
|
+
buckets.set(hash, { rep: item, count: 1 });
|
|
7798
|
+
}
|
|
7799
|
+
}
|
|
7800
|
+
return Array.from(buckets.values()).map(({ rep, count }) => ({
|
|
7801
|
+
...rep,
|
|
7802
|
+
label: summariseRule(rep.facetRule),
|
|
7803
|
+
subtitle: `${count} ${itemNoun}${count === 1 ? "" : "s"}`
|
|
7804
|
+
}));
|
|
7805
|
+
}, [isRuleTab, isCollection, filteredRuleItems, itemNoun]);
|
|
7806
|
+
const activeRuleSummary = useMemo(() => {
|
|
7807
|
+
if (!isRuleTab || !isCollection) return null;
|
|
7808
|
+
if (!selectedRecordId || isDraftId3(selectedRecordId)) return null;
|
|
7809
|
+
const hit = recordList.items.find((it) => it.id === selectedRecordId);
|
|
7810
|
+
if (!hit?.facetRule) return null;
|
|
7811
|
+
return summariseRule(hit.facetRule);
|
|
7812
|
+
}, [isRuleTab, isCollection, selectedRecordId, recordList.items]);
|
|
7604
7813
|
const applyFacetBrowseFilter = useCallback(
|
|
7605
7814
|
(items2) => {
|
|
7606
7815
|
if (!facetBrowseFilter) return items2;
|
|
@@ -7648,7 +7857,9 @@ function RecordsAdminShellInner(props) {
|
|
|
7648
7857
|
}),
|
|
7649
7858
|
[i18n.itemsAllLabel, collectionItems.items.length, itemNoun]
|
|
7650
7859
|
);
|
|
7651
|
-
const leftItems = isProductTab ? productListItems : isRuleTab ? applyFacetBrowseFilter(
|
|
7860
|
+
const leftItems = isProductTab ? productListItems : isRuleTab ? applyFacetBrowseFilter(
|
|
7861
|
+
isCollection ? collectionRuleRailItems : filteredRuleItems
|
|
7862
|
+
) : (isGlobalTab || isAllTab) && isCollection ? [collectionGlobalAllRow] : isRecordsTab ? applyFacetBrowseFilter(recordList.items) : [];
|
|
7652
7863
|
const leftLoading = isProductTab ? !productPinned && productBrowse.isLoading : isRecordsTab ? recordList.isLoading || probe.isLoading : false;
|
|
7653
7864
|
const leftError = isProductTab ? productBrowse.error : isRecordsTab ? recordList.error : null;
|
|
7654
7865
|
const leftSelectedId = isProductTab ? void 0 : selectedRecordId && selectedRecordId !== DRAFT_ID3 ? selectedRecordId : void 0;
|
|
@@ -8089,6 +8300,7 @@ function RecordsAdminShellInner(props) {
|
|
|
8089
8300
|
renderItemEmpty,
|
|
8090
8301
|
itemColumns,
|
|
8091
8302
|
cardSize: itemCardSize,
|
|
8303
|
+
ruleSummary: activeRuleSummary,
|
|
8092
8304
|
i18n
|
|
8093
8305
|
}
|
|
8094
8306
|
),
|