@proveanything/smartlinks-utils-ui 0.12.8 → 0.12.10

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.
@@ -525,6 +525,10 @@ interface RecordsAdminI18n {
525
525
  lifecycleStatusActive: string;
526
526
  lifecycleStatusArchived: string;
527
527
  lifecycleStatusDraft: string;
528
+ /** Product rail subtitle when the only records on a product are archived. */
529
+ subtitleArchivedOnly: string;
530
+ /** Product rail subtitle when the only records on a product are drafts. */
531
+ subtitleDraftOnly: string;
528
532
  /** Tooltip on the editor's status control. */
529
533
  lifecycleStatusHint: string;
530
534
  actionArchive: string;
@@ -2381,9 +2385,25 @@ interface UseScopeCountsArgs {
2381
2385
  maxRecords?: number;
2382
2386
  /** Page size used for the underlying SDK list calls. Default 100. */
2383
2387
  pageSize?: number;
2388
+ /**
2389
+ * Lifecycle status values treated as "active" when bucketing records
2390
+ * per product. Records with `null`/empty status are ALWAYS active for
2391
+ * back-compat. Defaults to `['active']`.
2392
+ */
2393
+ activeStatuses?: readonly string[];
2384
2394
  }
2385
2395
  interface ScopeCounts extends Partial<Record<ScopeKind, number>> {
2386
2396
  }
2397
+ /**
2398
+ * Per-product lifecycle bucket counts. `other` covers any non-active
2399
+ * status that isn't draft or archived (e.g. host-defined "scheduled").
2400
+ */
2401
+ interface ProductLifecycleBuckets {
2402
+ active: number;
2403
+ draft: number;
2404
+ archived: number;
2405
+ other: number;
2406
+ }
2387
2407
  interface UseScopeCountsResult {
2388
2408
  counts: ScopeCounts;
2389
2409
  total: number;
@@ -2401,6 +2421,13 @@ interface UseScopeCountsResult {
2401
2421
  * Lower bound when `truncated` is true.
2402
2422
  */
2403
2423
  productIds: ReadonlySet<string>;
2424
+ /**
2425
+ * Per-product lifecycle bucket counts, derived from the same scanned
2426
+ * records (no extra network cost). Used by the rail to distinguish a
2427
+ * product that has only-archived or only-draft records from one with
2428
+ * a live record — so the green tick doesn't lie.
2429
+ */
2430
+ productLifecycle: ReadonlyMap<string, ProductLifecycleBuckets>;
2404
2431
  }
2405
2432
  declare const useScopeCounts: (args: UseScopeCountsArgs) => UseScopeCountsResult;
2406
2433
  /** React Query key factory for cache invalidation from save/delete sites. */
@@ -266,6 +266,8 @@ var DEFAULT_I18N = {
266
266
  lifecycleStatusActive: "Active",
267
267
  lifecycleStatusArchived: "Archived",
268
268
  lifecycleStatusDraft: "Draft",
269
+ subtitleArchivedOnly: "Archived only",
270
+ subtitleDraftOnly: "Draft only",
269
271
  lifecycleStatusHint: "Archived records aren't returned to public consumers.",
270
272
  actionArchive: "Archive",
271
273
  actionRestore: "Restore",
@@ -582,7 +584,17 @@ var classify = (rec) => {
582
584
  return "collection";
583
585
  };
584
586
  var useScopeCounts = (args) => {
585
- const { ctx, enabled = true, maxRecords = 500, pageSize = 100 } = args;
587
+ const {
588
+ ctx,
589
+ enabled = true,
590
+ maxRecords = 500,
591
+ pageSize = 100,
592
+ activeStatuses
593
+ } = args;
594
+ const activeSet = useMemo(
595
+ () => new Set(activeStatuses ?? ["active"]),
596
+ [activeStatuses]
597
+ );
586
598
  const queryKey = useMemo(
587
599
  () => [...QK_BASE, ctx.collectionId, ctx.appId, ctx.recordType ?? null],
588
600
  [ctx.collectionId, ctx.appId, ctx.recordType]
@@ -621,10 +633,24 @@ var useScopeCounts = (args) => {
621
633
  all: 0
622
634
  };
623
635
  const productIds = /* @__PURE__ */ new Set();
636
+ const productLifecycle = /* @__PURE__ */ new Map();
624
637
  for (const rec of records) {
625
638
  counts[classify(rec)] += 1;
626
639
  const pid = rec.productId ?? void 0;
627
- if (pid) productIds.add(pid);
640
+ if (pid) {
641
+ productIds.add(pid);
642
+ const raw = rec.status ?? void 0;
643
+ const isActive2 = raw == null || raw === "" || activeSet.has(raw);
644
+ let bucket = productLifecycle.get(pid);
645
+ if (!bucket) {
646
+ bucket = { active: 0, draft: 0, archived: 0, other: 0 };
647
+ productLifecycle.set(pid, bucket);
648
+ }
649
+ if (isActive2) bucket.active += 1;
650
+ else if (raw === "draft") bucket.draft += 1;
651
+ else if (raw === "archived") bucket.archived += 1;
652
+ else bucket.other += 1;
653
+ }
628
654
  }
629
655
  counts.all = records.length;
630
656
  return {
@@ -633,9 +659,10 @@ var useScopeCounts = (args) => {
633
659
  isLoading: query.isLoading,
634
660
  error: query.error ?? null,
635
661
  truncated,
636
- productIds
662
+ productIds,
663
+ productLifecycle
637
664
  };
638
- }, [query.data, query.isLoading, query.error]);
665
+ }, [query.data, query.isLoading, query.error, activeSet]);
639
666
  return result;
640
667
  };
641
668
  var scopeCountsQueryKey = (collectionId, appId, recordType) => [...QK_BASE, collectionId, appId, recordType ?? null];
@@ -8951,16 +8978,39 @@ var coerceDraftItemId2 = (generateItemId) => {
8951
8978
  const candidate = generateItemId ? generateItemId() : mintDraftItemId2();
8952
8979
  return isDraftId3(candidate) ? candidate : `draft:${candidate}`;
8953
8980
  };
8954
- var productItemToSummary = (p, configured) => {
8981
+ var productItemToSummary = (p, buckets, i18n) => {
8955
8982
  const ref = buildRef({ productId: p.id });
8983
+ const total = buckets ? buckets.active + buckets.draft + buckets.archived + buckets.other : 0;
8984
+ const hasAnyActive = (buckets?.active ?? 0) > 0;
8985
+ let status;
8986
+ let toneHint;
8987
+ let subtitle = p.sku ?? void 0;
8988
+ if (hasAnyActive) {
8989
+ status = "configured";
8990
+ } else if (total === 0) {
8991
+ status = "empty";
8992
+ } else {
8993
+ status = "partial";
8994
+ if ((buckets.archived ?? 0) > 0 && (buckets.draft ?? 0) === 0) {
8995
+ toneHint = "muted";
8996
+ subtitle = i18n.subtitleArchivedOnly;
8997
+ } else if ((buckets.draft ?? 0) > 0 && (buckets.archived ?? 0) === 0) {
8998
+ toneHint = "warning";
8999
+ subtitle = i18n.subtitleDraftOnly;
9000
+ } else {
9001
+ toneHint = "warning";
9002
+ subtitle = (buckets.draft ?? 0) > 0 ? i18n.subtitleDraftOnly : i18n.subtitleArchivedOnly;
9003
+ }
9004
+ }
8956
9005
  return {
8957
9006
  id: null,
8958
9007
  ref,
8959
9008
  scope: parseRef(ref),
8960
9009
  data: null,
8961
- status: configured?.has(p.id) ? "configured" : "empty",
9010
+ status,
8962
9011
  label: p.name,
8963
- subtitle: p.sku ?? void 0
9012
+ subtitle,
9013
+ toneHint
8964
9014
  };
8965
9015
  };
8966
9016
  function RecordsAdminShell(props) {
@@ -9181,7 +9231,7 @@ function RecordsAdminShellInner(props) {
9181
9231
  };
9182
9232
  }, [queryClient, recordChangeRef]);
9183
9233
  const probe = useScopeProbe({ SL, collectionId });
9184
- const scopeCounts = useScopeCounts({ ctx });
9234
+ const scopeCounts = useScopeCounts({ ctx, activeStatuses: resolvedActiveStatuses });
9185
9235
  const topLevelScopes = useMemo(() => {
9186
9236
  const requested = requestedScopes ?? [];
9187
9237
  const allowed = /* @__PURE__ */ new Set([...TOP_LEVEL_SCOPES, ...OPT_IN_TOP_LEVEL_SCOPES]);
@@ -9981,7 +10031,7 @@ function RecordsAdminShellInner(props) {
9981
10031
  ]
9982
10032
  }
9983
10033
  ) : null;
9984
- const selectedSummary = selectedRecordId && selectedRecordId !== DRAFT_ID3 ? recordList.items.find((r) => r.id === selectedRecordId) ?? globalScopedList.items.find((r) => r.id === selectedRecordId) ?? ruleScopedList.items.find((r) => r.id === selectedRecordId) ?? collectionItems.items.find((r) => r.id === selectedRecordId) : isCollection && selectedItemId && !isDraftId3(selectedItemId) ? collectionItems.items.find((r) => r.id === selectedItemId) : void 0;
10034
+ const selectedSummary = isCollection && selectedItemId && !isDraftId3(selectedItemId) ? collectionItems.items.find((r) => r.id === selectedItemId || r.itemId === selectedItemId) : selectedRecordId && selectedRecordId !== DRAFT_ID3 ? recordList.items.find((r) => r.id === selectedRecordId) ?? globalScopedList.items.find((r) => r.id === selectedRecordId) ?? ruleScopedList.items.find((r) => r.id === selectedRecordId) ?? collectionItems.items.find((r) => r.id === selectedRecordId) : void 0;
9985
10035
  const editorLifecycleControl = selectedSummary?.id ? /* @__PURE__ */ jsx(
9986
10036
  LifecycleStatusControl,
9987
10037
  {
@@ -10214,7 +10264,12 @@ function RecordsAdminShellInner(props) {
10214
10264
  }];
10215
10265
  }
10216
10266
  const configured = scopeCounts.productIds;
10217
- const all = productBrowse.items.map((p) => productItemToSummary(p, configured));
10267
+ const lifecycle = scopeCounts.productLifecycle;
10268
+ const lifecycleI18n = {
10269
+ subtitleArchivedOnly: i18n.subtitleArchivedOnly,
10270
+ subtitleDraftOnly: i18n.subtitleDraftOnly
10271
+ };
10272
+ const all = productBrowse.items.map((p) => productItemToSummary(p, lifecycle.get(p.id), lifecycleI18n));
10218
10273
  const isConfigured = (s) => {
10219
10274
  const pid = s.scope.productId;
10220
10275
  return !!pid && configured.has(pid);
@@ -10231,7 +10286,10 @@ function RecordsAdminShellInner(props) {
10231
10286
  productBrowse.items,
10232
10287
  pinnedProduct.item,
10233
10288
  scopeCounts.productIds,
10234
- filter
10289
+ scopeCounts.productLifecycle,
10290
+ filter,
10291
+ i18n.subtitleArchivedOnly,
10292
+ i18n.subtitleDraftOnly
10235
10293
  ]);
10236
10294
  const isRuleTab = activeScope === "rule";
10237
10295
  const isGlobalTab = activeScope === "collection";