@proveanything/smartlinks-utils-ui 0.12.6 → 0.12.7

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.
@@ -1,11 +1,11 @@
1
+ import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KA4MKRHL.js';
2
+ export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KA4MKRHL.js';
1
3
  import { useIntroState, AdminPageHeader } from '../../chunk-3RRHM4LP.js';
2
- import { assertComponentStylesLoaded } from '../../chunk-OLYC54YT.js';
3
- import '../../chunk-5UQQYXCX.js';
4
4
  import { FacetRuleEditor } from '../../chunk-JMCV6FOW.js';
5
5
  import { useFacets } from '../../chunk-4LHF5JB7.js';
6
+ import { assertComponentStylesLoaded } from '../../chunk-OLYC54YT.js';
7
+ import '../../chunk-5UQQYXCX.js';
6
8
  import { cn } from '../../chunk-L7FQ52F5.js';
7
- import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KA4MKRHL.js';
8
- export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KA4MKRHL.js';
9
9
  import { createContext, useMemo, useState, useEffect, useCallback, useRef, isValidElement, useLayoutEffect, useContext, useSyncExternalStore, createElement } from 'react';
10
10
  import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, Sparkles, Settings2 } from 'lucide-react';
11
11
  import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
@@ -276,7 +276,15 @@ var DEFAULT_I18N = {
276
276
  conflictArchiveDuplicates: "Archive duplicates",
277
277
  conflictDeleteDuplicates: "Delete duplicates",
278
278
  conflictDeleteConfirm: "Permanently delete {n} duplicate record(s)? This cannot be undone.",
279
- conflictResolveLabel: "Resolve"
279
+ conflictResolveLabel: "Resolve",
280
+ ruleSortLabel: "Sort",
281
+ ruleSortRecent: "Recently updated",
282
+ ruleSortName: "Name",
283
+ ruleSortActiveCount: "Active count",
284
+ ruleSortHasArchived: "Has archived",
285
+ lifecycleBadgeActive: "{n} active",
286
+ lifecycleBadgeArchived: "{n} archived",
287
+ lifecycleBadgeDraft: "{n} draft"
280
288
  };
281
289
 
282
290
  // src/components/RecordsAdmin/types/presentation.ts
@@ -851,6 +859,24 @@ var useRecordList = (args) => {
851
859
  }
852
860
  return map;
853
861
  }, [historyItems]);
862
+ const ruleLifecycleCounts = useMemo(() => {
863
+ const map = /* @__PURE__ */ new Map();
864
+ for (const r of items) {
865
+ const h = ruleHash(r.facetRule ?? null);
866
+ if (!h) continue;
867
+ let bucket = map.get(h);
868
+ if (!bucket) {
869
+ bucket = { active: 0, archived: 0, draft: 0, other: 0 };
870
+ map.set(h, bucket);
871
+ }
872
+ const ls = r.lifecycleStatus;
873
+ if (ls == null || ls === "" || activeStatuses.includes(ls)) bucket.active += 1;
874
+ else if (ls === "archived") bucket.archived += 1;
875
+ else if (ls === "draft") bucket.draft += 1;
876
+ else bucket.other += 1;
877
+ }
878
+ return map;
879
+ }, [items, activeStatuses]);
854
880
  const refetch = useCallback(() => queryClient.refetchQueries({
855
881
  queryKey: [...QK_BASE2, ctx.collectionId, ctx.appId, ctx.recordType]
856
882
  }), [queryClient, ctx.collectionId, ctx.appId, ctx.recordType]);
@@ -861,6 +887,7 @@ var useRecordList = (args) => {
861
887
  activeItems,
862
888
  historyItems,
863
889
  historyBySlot,
890
+ ruleLifecycleCounts,
864
891
  total,
865
892
  counts,
866
893
  isLoading: query.isLoading,
@@ -6881,6 +6908,8 @@ function ItemListView({
6881
6908
  hasNextPage,
6882
6909
  isFetchingNextPage,
6883
6910
  onLoadMore,
6911
+ groupByLifecycle = false,
6912
+ lifecycle,
6884
6913
  i18n
6885
6914
  }) {
6886
6915
  const newLabel = i18n.newItem.includes("{noun}") ? i18n.newItem.replace("{noun}", itemNoun) : i18n.newItem;
@@ -6955,6 +6984,69 @@ function ItemListView({
6955
6984
  }), [ctx]);
6956
6985
  const confirmName = pendingRecord?.label ?? itemNoun;
6957
6986
  const confirmBody = i18n.deleteConfirmBody.includes("{name}") ? i18n.deleteConfirmBody.replace("{name}", confirmName) : i18n.deleteConfirmBody;
6987
+ const activeValues = useMemo(
6988
+ () => getActiveStatusValues(lifecycle),
6989
+ [lifecycle]
6990
+ );
6991
+ const buckets = useMemo(() => {
6992
+ if (!groupByLifecycle) return null;
6993
+ const map = /* @__PURE__ */ new Map();
6994
+ for (const item of visibleItems) {
6995
+ const def = resolveLifecycleStatus(item, lifecycle);
6996
+ const existing = map.get(def.value);
6997
+ if (existing) existing.items.push(item);
6998
+ else map.set(def.value, { label: def.label, items: [item] });
6999
+ }
7000
+ return Array.from(map.entries()).map(([key, v]) => ({
7001
+ key,
7002
+ label: v.label,
7003
+ items: v.items,
7004
+ isActive: activeValues.includes(key)
7005
+ })).sort((a, b) => compareLifecycleBuckets(a.key, b.key)).filter((b) => b.items.length > 0);
7006
+ }, [groupByLifecycle, visibleItems, lifecycle, activeValues]);
7007
+ const [collapsedBuckets, setCollapsedBuckets] = useState(() => /* @__PURE__ */ new Set());
7008
+ const toggleBucket = (key) => {
7009
+ setCollapsedBuckets((prev) => {
7010
+ const next = new Set(prev);
7011
+ if (next.has(key)) next.delete(key);
7012
+ else next.add(key);
7013
+ return next;
7014
+ });
7015
+ };
7016
+ const renderBody = (rows) => {
7017
+ if (renderItemList) return renderItemList(rows, guardedCtx);
7018
+ if (view === "table") {
7019
+ return /* @__PURE__ */ jsx(
7020
+ DefaultItemTable,
7021
+ {
7022
+ items: rows,
7023
+ columns: itemColumns,
7024
+ selectedId: guardedCtx.selectedId,
7025
+ onOpen: guardedCtx.onOpen,
7026
+ onDelete: guardedCtx.onDelete,
7027
+ sort,
7028
+ onToggleSort,
7029
+ rowActions,
7030
+ rowClipboard,
7031
+ i18n
7032
+ }
7033
+ );
7034
+ }
7035
+ return /* @__PURE__ */ jsx(
7036
+ DefaultItemCards,
7037
+ {
7038
+ items: rows,
7039
+ variant: view,
7040
+ selectedId: guardedCtx.selectedId,
7041
+ ctx: guardedCtx,
7042
+ renderCard: renderItemCard,
7043
+ cardSize,
7044
+ rowActions,
7045
+ rowClipboard,
7046
+ i18n
7047
+ }
7048
+ );
7049
+ };
6958
7050
  const toolbar = /* @__PURE__ */ jsxs("div", { className: "ra-item-toolbar", children: [
6959
7051
  /* @__PURE__ */ jsxs("div", { className: "ra-item-toolbar-title", children: [
6960
7052
  /* @__PURE__ */ jsx("h2", { className: "ra-display", style: { fontSize: "0.95rem", margin: 0 }, children: i18n.itemListTitle }),
@@ -7072,39 +7164,62 @@ function ItemListView({
7072
7164
  body: `No ${itemNoun}s match "${search}".`
7073
7165
  }
7074
7166
  );
7075
- } else if (renderItemList) {
7076
- body = renderItemList(visibleItems, guardedCtx);
7077
- } else if (view === "table") {
7078
- body = /* @__PURE__ */ jsx(
7079
- DefaultItemTable,
7080
- {
7081
- items: visibleItems,
7082
- columns: itemColumns,
7083
- selectedId: guardedCtx.selectedId,
7084
- onOpen: guardedCtx.onOpen,
7085
- onDelete: guardedCtx.onDelete,
7086
- sort,
7087
- onToggleSort,
7088
- rowActions,
7089
- rowClipboard,
7090
- i18n
7091
- }
7092
- );
7167
+ } else if (buckets && buckets.length > 0) {
7168
+ body = /* @__PURE__ */ jsx("div", { className: "ra-item-buckets", children: buckets.map((bucket) => {
7169
+ const open = bucket.isActive || !collapsedBuckets.has(bucket.key);
7170
+ return /* @__PURE__ */ jsxs(
7171
+ "section",
7172
+ {
7173
+ className: "ra-item-bucket",
7174
+ "data-bucket": bucket.key,
7175
+ children: [
7176
+ /* @__PURE__ */ jsxs(
7177
+ "button",
7178
+ {
7179
+ type: "button",
7180
+ className: "ra-item-bucket-header",
7181
+ onClick: () => !bucket.isActive && toggleBucket(bucket.key),
7182
+ "aria-expanded": open,
7183
+ disabled: bucket.isActive,
7184
+ style: {
7185
+ display: "flex",
7186
+ alignItems: "center",
7187
+ gap: "6px",
7188
+ width: "100%",
7189
+ padding: "6px 12px",
7190
+ background: "transparent",
7191
+ border: 0,
7192
+ borderTop: "1px solid hsl(var(--ra-border))",
7193
+ font: "inherit",
7194
+ fontSize: "11px",
7195
+ fontWeight: 600,
7196
+ textTransform: "uppercase",
7197
+ letterSpacing: "0.04em",
7198
+ color: "hsl(var(--ra-muted-text))",
7199
+ cursor: bucket.isActive ? "default" : "pointer"
7200
+ },
7201
+ children: [
7202
+ !bucket.isActive ? open ? /* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3", "aria-hidden": "true" }) : null,
7203
+ /* @__PURE__ */ jsx("span", { children: bucket.label }),
7204
+ /* @__PURE__ */ jsx("span", { style: { marginLeft: "auto", fontWeight: 500 }, children: bucket.items.length })
7205
+ ]
7206
+ }
7207
+ ),
7208
+ open && /* @__PURE__ */ jsx(
7209
+ "div",
7210
+ {
7211
+ className: "ra-item-bucket-body",
7212
+ style: bucket.isActive ? void 0 : { opacity: 0.75 },
7213
+ children: renderBody(bucket.items)
7214
+ }
7215
+ )
7216
+ ]
7217
+ },
7218
+ bucket.key
7219
+ );
7220
+ }) });
7093
7221
  } else {
7094
- body = /* @__PURE__ */ jsx(
7095
- DefaultItemCards,
7096
- {
7097
- items: visibleItems,
7098
- variant: view,
7099
- selectedId: guardedCtx.selectedId,
7100
- ctx: guardedCtx,
7101
- renderCard: renderItemCard,
7102
- cardSize,
7103
- rowActions,
7104
- rowClipboard,
7105
- i18n
7106
- }
7107
- );
7222
+ body = renderBody(visibleItems);
7108
7223
  }
7109
7224
  return /* @__PURE__ */ jsxs("div", { className: "ra-item-list", children: [
7110
7225
  toolbar,
@@ -7247,10 +7362,42 @@ function SiblingRail({
7247
7362
  isFetchingNextPage,
7248
7363
  onLoadMore,
7249
7364
  rowClipboard,
7250
- rowActions
7365
+ rowActions,
7366
+ groupByLifecycle = false,
7367
+ lifecycle
7251
7368
  }) {
7252
7369
  const ruleLabelLookup = useRuleLabelLookup();
7253
7370
  const newLabel = i18n.newItem.includes("{noun}") ? i18n.newItem.replace("{noun}", itemNoun ?? "item") : i18n.newItem;
7371
+ const activeValues = useMemo(
7372
+ () => getActiveStatusValues(lifecycle),
7373
+ [lifecycle]
7374
+ );
7375
+ const buckets = useMemo(() => {
7376
+ if (!groupByLifecycle) return null;
7377
+ const map = /* @__PURE__ */ new Map();
7378
+ for (const item of items) {
7379
+ const def = resolveLifecycleStatus(item, lifecycle);
7380
+ const key = def.value;
7381
+ const existing = map.get(key);
7382
+ if (existing) existing.items.push(item);
7383
+ else map.set(key, { label: def.label, items: [item] });
7384
+ }
7385
+ return Array.from(map.entries()).map(([key, v]) => ({
7386
+ key,
7387
+ label: v.label,
7388
+ items: v.items,
7389
+ isActive: activeValues.includes(key)
7390
+ })).sort((a, b) => compareLifecycleBuckets(a.key, b.key)).filter((b) => b.items.length > 0);
7391
+ }, [groupByLifecycle, items, lifecycle, activeValues]);
7392
+ const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
7393
+ const toggleBucket = (key) => {
7394
+ setCollapsed((prev) => {
7395
+ const next = new Set(prev);
7396
+ if (next.has(key)) next.delete(key);
7397
+ else next.add(key);
7398
+ return next;
7399
+ });
7400
+ };
7254
7401
  return /* @__PURE__ */ jsxs("div", { className: "ra-sibling-rail", children: [
7255
7402
  (onBack || contextKind) && /* @__PURE__ */ jsxs("div", { className: "ra-sibling-context", children: [
7256
7403
  onBack && /* @__PURE__ */ jsx(
@@ -7276,79 +7423,125 @@ function SiblingRail({
7276
7423
  isLoading && /* @__PURE__ */ jsx(LoadingState, {}),
7277
7424
  !isLoading && error && /* @__PURE__ */ jsx(ErrorState, { error }),
7278
7425
  !isLoading && !error && items.length === 0 && /* @__PURE__ */ jsx(EmptyState, { title: i18n.noItemsTitle, body: i18n.noItemsBody }),
7279
- !isLoading && !error && items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "ra-sibling-list", children: items.map((item, idx) => {
7280
- const id = item.itemId ?? "";
7281
- const key = item.id ?? (id || anchorKey(item.scope) || `pos:${idx}`);
7282
- const akey = anchorKey(item.scope);
7283
- const selected = selectedItemId === id;
7284
- const isDirty = !!(id && dirtyKeys?.has(id) || akey && dirtyKeys?.has(akey));
7285
- const hasError = !!(id && errorKeys?.has(id) || akey && errorKeys?.has(akey));
7286
- const ruleClauses = item.facetRule ? summarizeFacetRule(item.facetRule, ruleLabelLookup) : [];
7287
- const isTargeted = ruleClauses.length > 0;
7288
- const ruleSummary = isTargeted ? ruleClauses.map((c) => c.label).join(" \xB7 ") : null;
7289
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("div", { className: "ra-row-shell", "data-selected": selected, children: [
7290
- /* @__PURE__ */ jsxs(
7291
- "button",
7292
- {
7293
- type: "button",
7294
- onClick: () => onSelect(id),
7295
- className: "ra-row",
7296
- "data-selected": selected,
7297
- children: [
7298
- /* @__PURE__ */ jsxs("div", { className: "ra-row-body", children: [
7299
- /* @__PURE__ */ jsx("div", { className: "ra-row-title", children: item.label }),
7300
- item.subtitle && /* @__PURE__ */ jsx("div", { className: "ra-row-sub", children: item.subtitle })
7301
- ] }),
7302
- isTargeted && /* @__PURE__ */ jsx(
7303
- "span",
7304
- {
7305
- className: "ra-row-rule-pip",
7306
- title: ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
7307
- "aria-label": ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
7308
- children: /* @__PURE__ */ jsx(Target, { className: "w-3 h-3", "aria-hidden": "true" })
7309
- }
7310
- ),
7311
- hasError ? /* @__PURE__ */ jsx(
7312
- "span",
7313
- {
7314
- className: "ra-error-pip",
7315
- title: "Save failed",
7316
- "aria-label": "Save failed"
7317
- }
7318
- ) : isDirty ? /* @__PURE__ */ jsx(
7319
- "span",
7320
- {
7321
- className: "ra-dirty-pip",
7322
- title: "Unsaved changes",
7323
- "aria-label": "Unsaved changes"
7324
- }
7325
- ) : null
7326
- ]
7327
- }
7328
- ),
7329
- (() => {
7330
- const cb = rowClipboard ? rowClipboard(item) : null;
7331
- const extra = rowActions ? rowActions(item) ?? void 0 : void 0;
7332
- if (!cb?.onCopy && !cb?.onDuplicate && !cb?.onCopyAndNewRule && !(extra && extra.length)) {
7333
- return null;
7334
- }
7335
- return /* @__PURE__ */ jsx(
7336
- RowContextMenu,
7426
+ !isLoading && !error && items.length > 0 && (() => {
7427
+ const renderRow = (item, idx, dimmed) => {
7428
+ const id = item.itemId ?? "";
7429
+ const key = item.id ?? (id || anchorKey(item.scope) || `pos:${idx}`);
7430
+ const akey = anchorKey(item.scope);
7431
+ const selected = selectedItemId === id;
7432
+ const isDirty = !!(id && dirtyKeys?.has(id) || akey && dirtyKeys?.has(akey));
7433
+ const hasError = !!(id && errorKeys?.has(id) || akey && errorKeys?.has(akey));
7434
+ const ruleClauses = item.facetRule ? summarizeFacetRule(item.facetRule, ruleLabelLookup) : [];
7435
+ const isTargeted = ruleClauses.length > 0;
7436
+ const ruleSummary = isTargeted ? ruleClauses.map((c) => c.label).join(" \xB7 ") : null;
7437
+ return /* @__PURE__ */ jsx("li", { "data-dimmed": dimmed || void 0, children: /* @__PURE__ */ jsxs("div", { className: "ra-row-shell", "data-selected": selected, children: [
7438
+ /* @__PURE__ */ jsxs(
7439
+ "button",
7337
7440
  {
7338
- onCopy: cb?.onCopy,
7339
- onDuplicate: cb?.onDuplicate,
7340
- onCopyAndNewRule: cb?.onCopyAndNewRule,
7341
- actions: extra,
7342
- i18n: {
7343
- copy: i18n.copy,
7344
- duplicateAction: i18n.duplicateAction,
7345
- copyAndNewRuleAction: i18n.copyAndNewRuleAction
7441
+ type: "button",
7442
+ onClick: () => onSelect(id),
7443
+ className: "ra-row",
7444
+ "data-selected": selected,
7445
+ style: dimmed ? { opacity: 0.7 } : void 0,
7446
+ children: [
7447
+ /* @__PURE__ */ jsxs("div", { className: "ra-row-body", children: [
7448
+ /* @__PURE__ */ jsx("div", { className: "ra-row-title", children: item.label }),
7449
+ item.subtitle && /* @__PURE__ */ jsx("div", { className: "ra-row-sub", children: item.subtitle })
7450
+ ] }),
7451
+ isTargeted && /* @__PURE__ */ jsx(
7452
+ "span",
7453
+ {
7454
+ className: "ra-row-rule-pip",
7455
+ title: ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
7456
+ "aria-label": ruleSummary ? `Targeted: ${ruleSummary}` : "This item has targeting rules",
7457
+ children: /* @__PURE__ */ jsx(Target, { className: "w-3 h-3", "aria-hidden": "true" })
7458
+ }
7459
+ ),
7460
+ hasError ? /* @__PURE__ */ jsx(
7461
+ "span",
7462
+ {
7463
+ className: "ra-error-pip",
7464
+ title: "Save failed",
7465
+ "aria-label": "Save failed"
7466
+ }
7467
+ ) : isDirty ? /* @__PURE__ */ jsx(
7468
+ "span",
7469
+ {
7470
+ className: "ra-dirty-pip",
7471
+ title: "Unsaved changes",
7472
+ "aria-label": "Unsaved changes"
7473
+ }
7474
+ ) : null
7475
+ ]
7476
+ }
7477
+ ),
7478
+ (() => {
7479
+ const cb = rowClipboard ? rowClipboard(item) : null;
7480
+ const extra = rowActions ? rowActions(item) ?? void 0 : void 0;
7481
+ if (!cb?.onCopy && !cb?.onDuplicate && !cb?.onCopyAndNewRule && !(extra && extra.length)) {
7482
+ return null;
7483
+ }
7484
+ return /* @__PURE__ */ jsx(
7485
+ RowContextMenu,
7486
+ {
7487
+ onCopy: cb?.onCopy,
7488
+ onDuplicate: cb?.onDuplicate,
7489
+ onCopyAndNewRule: cb?.onCopyAndNewRule,
7490
+ actions: extra,
7491
+ i18n: {
7492
+ copy: i18n.copy,
7493
+ duplicateAction: i18n.duplicateAction,
7494
+ copyAndNewRuleAction: i18n.copyAndNewRuleAction
7495
+ }
7346
7496
  }
7497
+ );
7498
+ })()
7499
+ ] }) }, key);
7500
+ };
7501
+ if (!buckets) {
7502
+ return /* @__PURE__ */ jsx("ul", { className: "ra-sibling-list", children: items.map((item, idx) => renderRow(item, idx, false)) });
7503
+ }
7504
+ return /* @__PURE__ */ jsx("div", { className: "ra-sibling-buckets", children: buckets.map((bucket) => {
7505
+ const open = bucket.isActive || !collapsed.has(bucket.key);
7506
+ return /* @__PURE__ */ jsxs("section", { className: "ra-sibling-bucket", "data-bucket": bucket.key, children: [
7507
+ /* @__PURE__ */ jsxs(
7508
+ "button",
7509
+ {
7510
+ type: "button",
7511
+ className: "ra-sibling-bucket-header",
7512
+ onClick: () => !bucket.isActive && toggleBucket(bucket.key),
7513
+ "aria-expanded": open,
7514
+ disabled: bucket.isActive,
7515
+ style: {
7516
+ display: "flex",
7517
+ alignItems: "center",
7518
+ gap: "6px",
7519
+ width: "100%",
7520
+ padding: "6px 10px",
7521
+ background: "transparent",
7522
+ border: 0,
7523
+ borderTop: "1px solid hsl(var(--ra-border))",
7524
+ font: "inherit",
7525
+ fontSize: "11px",
7526
+ fontWeight: 600,
7527
+ textTransform: "uppercase",
7528
+ letterSpacing: "0.04em",
7529
+ color: "hsl(var(--ra-muted-text))",
7530
+ cursor: bucket.isActive ? "default" : "pointer"
7531
+ },
7532
+ children: [
7533
+ !bucket.isActive ? open ? /* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3", "aria-hidden": "true" }) : null,
7534
+ /* @__PURE__ */ jsx("span", { children: bucket.label }),
7535
+ /* @__PURE__ */ jsx("span", { style: { marginLeft: "auto", fontWeight: 500 }, children: bucket.items.length })
7536
+ ]
7347
7537
  }
7348
- );
7349
- })()
7350
- ] }) }, key);
7351
- }) })
7538
+ ),
7539
+ open && /* @__PURE__ */ jsx("ul", { className: "ra-sibling-list", children: bucket.items.map(
7540
+ (item, idx) => renderRow(item, idx, !bucket.isActive)
7541
+ ) })
7542
+ ] }, bucket.key);
7543
+ }) });
7544
+ })()
7352
7545
  ] }),
7353
7546
  onLoadMore && /* @__PURE__ */ jsx(
7354
7547
  LoadMoreFooter,
@@ -8892,6 +9085,7 @@ function RecordsAdminShellInner(props) {
8892
9085
  onTelemetry?.({ type: "presentation.change", recordType, from: presentation, to: next });
8893
9086
  setPresentation(next);
8894
9087
  }, [onTelemetry, recordType, presentation, setPresentation]);
9088
+ const [ruleSort, setRuleSort] = useState("recent");
8895
9089
  const [itemView, setItemView] = useItemViewPref({
8896
9090
  appId,
8897
9091
  recordType,
@@ -10070,6 +10264,7 @@ function RecordsAdminShellInner(props) {
10070
10264
  if (cardinality === "collection") {
10071
10265
  setRuleWizardStep(2);
10072
10266
  if (ruleWizardSeedMode) {
10267
+ skipNextItemResetRef.current = true;
10073
10268
  const id = coerceDraftItemId2(generateItemId);
10074
10269
  setSelectedItemId(id);
10075
10270
  }
@@ -10080,7 +10275,7 @@ function RecordsAdminShellInner(props) {
10080
10275
  setSelectedRecordId(DRAFT_ID3);
10081
10276
  }
10082
10277
  }
10083
- }, [cardinality, ruleWizardSeedMode, mintRuleWizardDraftKey, generateItemId]);
10278
+ }, [cardinality, ruleWizardSeedMode, mintRuleWizardDraftKey, generateItemId, skipNextItemResetRef]);
10084
10279
  const startRuleWizardDraft = useCallback((seed) => {
10085
10280
  setRuleWizardSeedMode(seed);
10086
10281
  setRuleWizardDraftKey(mintRuleWizardDraftKey());
@@ -10134,17 +10329,51 @@ function RecordsAdminShellInner(props) {
10134
10329
  buckets.set(hash, { rep: item, count: 1 });
10135
10330
  }
10136
10331
  }
10137
- return Array.from(buckets.values()).map(({ rep, count }) => ({
10138
- ...rep,
10139
- // Title carries the count + identity; the rule chips below render
10140
- // the friendly facet/value labels (via the lookup) so we don't
10141
- // repeat the same information in two places. Without this, a row
10142
- // had three echoes of the same rule (raw-key title, friendly chip,
10143
- // and the right-pane "Rule · …" header).
10144
- label: `${count} ${itemNoun}${count === 1 ? "" : "s"}`,
10145
- subtitle: void 0
10146
- }));
10147
- }, [isRuleTab, isCollection, filteredRuleItems, itemNoun]);
10332
+ const counts = recordList.ruleLifecycleCounts;
10333
+ return Array.from(buckets.entries()).map(([hash, { rep, count }]) => {
10334
+ const lc = counts.get(hash) ?? { active: 0, archived: 0, draft: 0, other: 0 };
10335
+ const lifecycleBadges = [];
10336
+ if (lc.active > 0) lifecycleBadges.push({
10337
+ label: i18n.lifecycleBadgeActive.replace("{n}", String(lc.active)),
10338
+ tone: "success"
10339
+ });
10340
+ if (lc.draft > 0) lifecycleBadges.push({
10341
+ label: i18n.lifecycleBadgeDraft.replace("{n}", String(lc.draft)),
10342
+ tone: "warning"
10343
+ });
10344
+ if (lc.archived > 0) lifecycleBadges.push({
10345
+ label: i18n.lifecycleBadgeArchived.replace("{n}", String(lc.archived)),
10346
+ tone: "neutral"
10347
+ });
10348
+ return {
10349
+ ...rep,
10350
+ // Title carries the count + identity; the rule chips below render
10351
+ // the friendly facet/value labels (via the lookup) so we don't
10352
+ // repeat the same information in two places.
10353
+ label: `${count} ${itemNoun}${count === 1 ? "" : "s"}`,
10354
+ subtitle: void 0,
10355
+ badges: [...rep.badges ?? [], ...lifecycleBadges],
10356
+ // Stash counts on the row for the sort comparator below.
10357
+ __lifecycleCounts: lc
10358
+ };
10359
+ });
10360
+ }, [isRuleTab, isCollection, filteredRuleItems, itemNoun, recordList.ruleLifecycleCounts, i18n]);
10361
+ const sortedCollectionRuleRailItems = useMemo(() => {
10362
+ if (!isRuleTab || !isCollection || ruleSort === "recent") return collectionRuleRailItems;
10363
+ const arr = [...collectionRuleRailItems];
10364
+ const counts = (r) => r.__lifecycleCounts ?? { active: 0, archived: 0 };
10365
+ if (ruleSort === "name") arr.sort((a, b) => a.label.localeCompare(b.label));
10366
+ else if (ruleSort === "activeCount") arr.sort((a, b) => counts(b).active - counts(a).active);
10367
+ else if (ruleSort === "hasArchived") {
10368
+ arr.sort((a, b) => {
10369
+ const ah = counts(a).archived > 0 ? 1 : 0;
10370
+ const bh = counts(b).archived > 0 ? 1 : 0;
10371
+ if (ah !== bh) return bh - ah;
10372
+ return counts(b).archived - counts(a).archived;
10373
+ });
10374
+ }
10375
+ return arr;
10376
+ }, [collectionRuleRailItems, isRuleTab, isCollection, ruleSort]);
10148
10377
  const activeRuleSummary = useMemo(() => {
10149
10378
  if (!isRuleTab || !isCollection) return null;
10150
10379
  if (!selectedRecordId || isDraftId3(selectedRecordId)) return null;
@@ -10283,9 +10512,9 @@ function RecordsAdminShellInner(props) {
10283
10512
  });
10284
10513
  }, [isLifecycleRail, selectedLifecycleKey, lcGroupBy, collectionItems.items]);
10285
10514
  const leftItems = isProductTab ? productListItems : isRuleTab ? applyFacetBrowseFilter(
10286
- isCollection ? collectionRuleRailItems : filteredRuleItems
10515
+ isCollection ? sortedCollectionRuleRailItems : filteredRuleItems
10287
10516
  ) : isLifecycleRail ? lifecycleRows : (isGlobalTab || isAllTab) && isCollection ? [collectionGlobalAllRow] : isRecordsTab ? applyFacetBrowseFilter(recordList.items) : [];
10288
- const railShowsHistoryDisclosure = isRecordsTab && !isLifecycleRail && !((isGlobalTab || isAllTab) && isCollection);
10517
+ const railShowsHistoryDisclosure = isRecordsTab && !isLifecycleRail && !isCollection;
10289
10518
  const filteredLeftItems = useMemo(() => {
10290
10519
  if (!railShowsHistoryDisclosure) return leftItems;
10291
10520
  return leftItems.filter((r) => {
@@ -10553,6 +10782,8 @@ function RecordsAdminShellInner(props) {
10553
10782
  },
10554
10783
  rowClipboard,
10555
10784
  rowActions: wrappedRecordActions,
10785
+ groupByLifecycle: true,
10786
+ lifecycle: lifecycleConfig,
10556
10787
  contextKind: isLifecycleRailEarly && lifecycleBucketLabel ? lifecycleBucketLabel : activeScope === "rule" ? "Rule" : activeScope === "product" ? "Product" : activeScope === "collection" ? "Global" : activeScope === "all" ? "All records" : activeScope === "variant" ? "Variant" : activeScope === "batch" ? "Batch" : activeScope === "facet" ? "Facet" : void 0,
10557
10788
  contextSummary: isLifecycleRailEarly && lifecycleBucketLabel ? `${scopedCollectionItemsList.length} ${itemNounLabel}${scopedCollectionItemsList.length === 1 ? "" : "s"}` : activeScope === "rule" ? activeRuleSummary : activeScope === "product" ? editorHeaderLabel ?? null : null,
10558
10789
  i18n
@@ -10683,7 +10914,33 @@ function RecordsAdminShellInner(props) {
10683
10914
  onChange: setFacetBrowseFilter,
10684
10915
  isLoading: facetBrowse.isLoading
10685
10916
  }
10686
- )
10917
+ ),
10918
+ isRuleTab && isCollection && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
10919
+ /* @__PURE__ */ jsx(
10920
+ "label",
10921
+ {
10922
+ htmlFor: "ra-rule-sort",
10923
+ style: { color: "hsl(var(--ra-text-muted))" },
10924
+ children: i18n.ruleSortLabel
10925
+ }
10926
+ ),
10927
+ /* @__PURE__ */ jsxs(
10928
+ "select",
10929
+ {
10930
+ id: "ra-rule-sort",
10931
+ value: ruleSort,
10932
+ onChange: (e) => setRuleSort(e.target.value),
10933
+ className: "text-xs px-2 py-1 rounded-md border bg-transparent focus:outline-none focus:ring-1",
10934
+ style: { borderColor: "hsl(var(--ra-border))", color: "hsl(var(--ra-text))" },
10935
+ children: [
10936
+ /* @__PURE__ */ jsx("option", { value: "recent", children: i18n.ruleSortRecent }),
10937
+ /* @__PURE__ */ jsx("option", { value: "name", children: i18n.ruleSortName }),
10938
+ /* @__PURE__ */ jsx("option", { value: "activeCount", children: i18n.ruleSortActiveCount }),
10939
+ /* @__PURE__ */ jsx("option", { value: "hasArchived", children: i18n.ruleSortHasArchived })
10940
+ ]
10941
+ }
10942
+ )
10943
+ ] })
10687
10944
  ] })
10688
10945
  ] }),
10689
10946
  /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
@@ -10920,6 +11177,8 @@ function RecordsAdminShellInner(props) {
10920
11177
  onLoadMore: () => {
10921
11178
  void collectionItems.fetchNextPage();
10922
11179
  },
11180
+ groupByLifecycle: true,
11181
+ lifecycle: lifecycleConfig,
10923
11182
  i18n
10924
11183
  }
10925
11184
  ),