strapi-plugin-bulk-updater 1.4.6 → 1.4.9

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.
@@ -166,6 +166,8 @@ function CsvManagerTab() {
166
166
  const [showConfirmModal, setShowConfirmModal] = React.useState(false);
167
167
  const [makerNotesInput, setMakerNotesInput] = React.useState("");
168
168
  const [submittingBatch, setSubmittingBatch] = React.useState(false);
169
+ const [publishStrategy, setPublishStrategy] = React.useState("safe");
170
+ const [unreviewedConsent, setUnreviewedConsent] = React.useState(false);
169
171
  React.useEffect(() => {
170
172
  fetchInitialData();
171
173
  }, []);
@@ -337,7 +339,9 @@ function CsvManagerTab() {
337
339
  isLastChunk: isLast,
338
340
  startRowNumber,
339
341
  mode: importMode,
340
- publish: publishOnImport
342
+ publish: publishOnImport,
343
+ publishStrategy,
344
+ unreviewedConsent
341
345
  });
342
346
  if (!res.data || !res.data.success) {
343
347
  throw new Error(res.data?.error || `Chunk ${chunkIdx + 1} failed to process`);
@@ -441,7 +445,10 @@ function CsvManagerTab() {
441
445
  makerNotes: makerNotesInput,
442
446
  forceImmediate,
443
447
  mode: importMode,
444
- publish: publishOnImport
448
+ publish: publishOnImport,
449
+ publishStrategy,
450
+ unreviewedConsent,
451
+ unreviewedCount: preflightReport?.lifecycleStats?.unreviewedCount || 0
445
452
  });
446
453
  setShowConfirmModal(false);
447
454
  if (res.data && res.data.pending) {
@@ -850,7 +857,11 @@ function CsvManagerTab() {
850
857
  variant: "default",
851
858
  size: "L",
852
859
  startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
853
- onClick: () => setShowConfirmModal(true),
860
+ onClick: () => {
861
+ setPublishStrategy("safe");
862
+ setUnreviewedConsent(false);
863
+ setShowConfirmModal(true);
864
+ },
854
865
  loading: importing || submittingBatch,
855
866
  children: [
856
867
  "Review & Import (",
@@ -1123,6 +1134,27 @@ function CsvManagerTab() {
1123
1134
  ] })
1124
1135
  ] })
1125
1136
  ] }),
1137
+ preflightReport?.lifecycleStats && preflightReport.lifecycleStats.isDraftAndPublish && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid rgba(120, 120, 150, 0.15)" }, children: [
1138
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: [
1139
+ "LIFECYCLE STATUS BREAKDOWN (",
1140
+ preflightReport.lifecycleStats.totalChecked,
1141
+ " Existing Records)"
1142
+ ] }),
1143
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", children: [
1144
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "success100", textColor: "success700", children: [
1145
+ "✓ Published / Approved: ",
1146
+ preflightReport.lifecycleStats.published
1147
+ ] }),
1148
+ preflightReport.lifecycleStats.modified > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1149
+ "📝 Modified (Unreviewed Revisions): ",
1150
+ preflightReport.lifecycleStats.modified
1151
+ ] }),
1152
+ preflightReport.lifecycleStats.draft > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
1153
+ "📄 Draft (Never Published): ",
1154
+ preflightReport.lifecycleStats.draft
1155
+ ] })
1156
+ ] })
1157
+ ] }),
1126
1158
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, hasRadius: true, marginBottom: 4, background: executionWindow?.isBusinessHours ? "warning100" : "success100", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, children: [
1127
1159
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", textColor: executionWindow?.isBusinessHours ? "warning700" : "success700", children: executionWindow?.isBusinessHours ? "⏰ Peak Business Hours Restriction Active" : "✅ Server Off-Peak Window Active" }) }),
1128
1160
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: executionWindow?.isBusinessHours ? "warning700" : "success700", children: executionWindow?.policyMessage || (executionWindow?.isBusinessHours ? "Server policy restricts bulk operations during peak business hours (09:00 - 18:00). Direct execution is paused to preserve API throughput." : "Currently outside peak business hours. Direct execution is permitted.") })
@@ -1150,6 +1182,125 @@ function CsvManagerTab() {
1150
1182
  ] })
1151
1183
  }
1152
1184
  ),
1185
+ publishOnImport && (preflightReport?.lifecycleStats?.unreviewedCount ?? 0) > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
1186
+ designSystem.Box,
1187
+ {
1188
+ padding: 4,
1189
+ background: "neutral100",
1190
+ hasRadius: true,
1191
+ marginBottom: 4,
1192
+ style: { border: "1px solid #d9d8ff" },
1193
+ children: [
1194
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, display: "block", children: "PUBLISHING STRATEGY & GOVERNANCE CONSENT" }),
1195
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 3, children: [
1196
+ /* @__PURE__ */ jsxRuntime.jsx(
1197
+ designSystem.Box,
1198
+ {
1199
+ padding: 3,
1200
+ hasRadius: true,
1201
+ style: {
1202
+ border: publishStrategy === "safe" ? "2px solid #4945ff" : "1px solid #eaeaef",
1203
+ backgroundColor: publishStrategy === "safe" ? "#f0f0ff" : "#ffffff",
1204
+ cursor: "pointer"
1205
+ },
1206
+ onClick: () => {
1207
+ setPublishStrategy("safe");
1208
+ setUnreviewedConsent(false);
1209
+ },
1210
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "flex-start", children: [
1211
+ /* @__PURE__ */ jsxRuntime.jsx(
1212
+ "input",
1213
+ {
1214
+ type: "radio",
1215
+ id: "csv-strategy-safe",
1216
+ name: "csvPublishStrategy",
1217
+ checked: publishStrategy === "safe",
1218
+ onChange: () => {
1219
+ setPublishStrategy("safe");
1220
+ setUnreviewedConsent(false);
1221
+ },
1222
+ style: { marginTop: 3 }
1223
+ }
1224
+ ),
1225
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
1226
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", display: "block", children: "🛡️ Safe Publishing (Recommended)" }),
1227
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
1228
+ "Only publish records that are already approved & live (",
1229
+ preflightReport?.lifecycleStats?.published ?? 0,
1230
+ " record",
1231
+ (preflightReport?.lifecycleStats?.published ?? 0) === 1 ? "" : "s",
1232
+ "). The ",
1233
+ preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1234
+ " unreviewed or draft record(s) will receive the CSV update in Draft mode without publishing unreviewed draft revisions to visitors."
1235
+ ] })
1236
+ ] })
1237
+ ] })
1238
+ }
1239
+ ),
1240
+ /* @__PURE__ */ jsxRuntime.jsx(
1241
+ designSystem.Box,
1242
+ {
1243
+ padding: 3,
1244
+ hasRadius: true,
1245
+ style: {
1246
+ border: publishStrategy === "all" ? "2px solid #d02b20" : "1px solid #eaeaef",
1247
+ backgroundColor: publishStrategy === "all" ? "#fff5f5" : "#ffffff",
1248
+ cursor: "pointer"
1249
+ },
1250
+ onClick: () => setPublishStrategy("all"),
1251
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "flex-start", children: [
1252
+ /* @__PURE__ */ jsxRuntime.jsx(
1253
+ "input",
1254
+ {
1255
+ type: "radio",
1256
+ id: "csv-strategy-all",
1257
+ name: "csvPublishStrategy",
1258
+ checked: publishStrategy === "all",
1259
+ onChange: () => setPublishStrategy("all"),
1260
+ style: { marginTop: 3 }
1261
+ }
1262
+ ),
1263
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
1264
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "⚠️ Publish All Imported Records (Includes Unreviewed Drafts)" }),
1265
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
1266
+ "Publish all affected records live to production immediately, pushing all pending unreviewed draft edits in the ",
1267
+ preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1268
+ " draft/modified record(s) live."
1269
+ ] })
1270
+ ] })
1271
+ ] })
1272
+ }
1273
+ ),
1274
+ publishStrategy === "all" && /* @__PURE__ */ jsxRuntime.jsx(
1275
+ designSystem.Box,
1276
+ {
1277
+ padding: 3,
1278
+ background: "danger100",
1279
+ hasRadius: true,
1280
+ style: { border: "1px solid #f5c0b8" },
1281
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "flex-start", children: [
1282
+ /* @__PURE__ */ jsxRuntime.jsx(
1283
+ designSystem.Checkbox,
1284
+ {
1285
+ checked: unreviewedConsent,
1286
+ onValueChange: (checked) => setUnreviewedConsent(Boolean(checked))
1287
+ }
1288
+ ),
1289
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { cursor: "pointer" }, onClick: () => setUnreviewedConsent(!unreviewedConsent), children: [
1290
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "Mandatory Risk Consent Required" }),
1291
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "danger700", children: [
1292
+ "I understand and explicitly consent that ",
1293
+ preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1294
+ " record(s) with unreviewed draft revisions or saved draft states will be published live to production visitors."
1295
+ ] })
1296
+ ] })
1297
+ ] })
1298
+ }
1299
+ )
1300
+ ] })
1301
+ ]
1302
+ }
1303
+ ),
1153
1304
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsx(
1154
1305
  designSystem.TextInput,
1155
1306
  {
@@ -1163,71 +1314,78 @@ function CsvManagerTab() {
1163
1314
  ]
1164
1315
  }
1165
1316
  ),
1166
- /* @__PURE__ */ jsxRuntime.jsx(
1167
- designSystem.Box,
1168
- {
1169
- padding: 4,
1170
- background: "neutral100",
1171
- style: {
1172
- borderTop: "1px solid rgba(120, 120, 150, 0.2)",
1173
- flexShrink: 0,
1174
- position: "sticky",
1175
- bottom: 0,
1176
- zIndex: 20,
1177
- boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
1178
- },
1179
- children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
1180
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
1181
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
1182
- /* @__PURE__ */ jsxRuntime.jsx(
1183
- designSystem.Button,
1184
- {
1185
- variant: executionWindow?.isBusinessHours ? "default" : "secondary",
1186
- size: "M",
1187
- onClick: () => submitBatchWithMode("maker_checker"),
1188
- loading: submittingBatch,
1189
- children: "Submit for Checker Approval"
1190
- }
1191
- ),
1192
- executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1193
- /* @__PURE__ */ jsxRuntime.jsxs(
1317
+ (() => {
1318
+ const isConsentRequired = publishOnImport && (preflightReport?.lifecycleStats?.unreviewedCount ?? 0) > 0 && publishStrategy === "all" && !unreviewedConsent;
1319
+ return /* @__PURE__ */ jsxRuntime.jsx(
1320
+ designSystem.Box,
1321
+ {
1322
+ padding: 4,
1323
+ background: "neutral100",
1324
+ style: {
1325
+ borderTop: "1px solid rgba(120, 120, 150, 0.2)",
1326
+ flexShrink: 0,
1327
+ position: "sticky",
1328
+ bottom: 0,
1329
+ zIndex: 20,
1330
+ boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
1331
+ },
1332
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
1333
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
1334
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
1335
+ /* @__PURE__ */ jsxRuntime.jsx(
1194
1336
  designSystem.Button,
1195
1337
  {
1196
- variant: "secondary",
1338
+ variant: executionWindow?.isBusinessHours ? "default" : "secondary",
1197
1339
  size: "M",
1198
- onClick: () => submitBatchWithMode("queue_offpeak"),
1340
+ onClick: () => submitBatchWithMode("maker_checker"),
1199
1341
  loading: submittingBatch,
1200
- children: [
1201
- "Queue for Off-Peak (",
1202
- executionWindow?.offPeakStartTime || "18:00",
1203
- ")"
1204
- ]
1342
+ disabled: isConsentRequired,
1343
+ children: "Submit for Checker Approval"
1205
1344
  }
1206
1345
  ),
1207
- /* @__PURE__ */ jsxRuntime.jsx(
1346
+ executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1347
+ /* @__PURE__ */ jsxRuntime.jsxs(
1348
+ designSystem.Button,
1349
+ {
1350
+ variant: "secondary",
1351
+ size: "M",
1352
+ onClick: () => submitBatchWithMode("queue_offpeak"),
1353
+ loading: submittingBatch,
1354
+ disabled: isConsentRequired,
1355
+ children: [
1356
+ "Queue for Off-Peak (",
1357
+ executionWindow?.offPeakStartTime || "18:00",
1358
+ ")"
1359
+ ]
1360
+ }
1361
+ ),
1362
+ /* @__PURE__ */ jsxRuntime.jsx(
1363
+ designSystem.Button,
1364
+ {
1365
+ variant: "danger-light",
1366
+ size: "S",
1367
+ onClick: () => submitBatchWithMode("immediate", true),
1368
+ loading: importing,
1369
+ disabled: isConsentRequired,
1370
+ children: "Force Run Now"
1371
+ }
1372
+ )
1373
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
1208
1374
  designSystem.Button,
1209
1375
  {
1210
- variant: "danger-light",
1211
- size: "S",
1212
- onClick: () => submitBatchWithMode("immediate", true),
1376
+ variant: "default",
1377
+ size: "M",
1378
+ onClick: () => submitBatchWithMode("immediate"),
1213
1379
  loading: importing,
1214
- children: "Force Run Now"
1380
+ disabled: isConsentRequired,
1381
+ children: "Confirm & Execute Now"
1215
1382
  }
1216
1383
  )
1217
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(
1218
- designSystem.Button,
1219
- {
1220
- variant: "default",
1221
- size: "M",
1222
- onClick: () => submitBatchWithMode("immediate"),
1223
- loading: importing,
1224
- children: "Confirm & Execute Now"
1225
- }
1226
- )
1384
+ ] })
1227
1385
  ] })
1228
- ] })
1229
- }
1230
- )
1386
+ }
1387
+ );
1388
+ })()
1231
1389
  ]
1232
1390
  }
1233
1391
  )
@@ -1865,7 +2023,8 @@ const BulkUpdatePreviewModal = ({
1865
2023
  previewData,
1866
2024
  attribute,
1867
2025
  targetValueDisplay,
1868
- publishOnApproval = true
2026
+ publishOnApproval = true,
2027
+ lifecycleStats
1869
2028
  }) => {
1870
2029
  const [filterTab, setFilterTab] = React.useState("all");
1871
2030
  const [searchQuery, setSearchQuery] = React.useState("");
@@ -1968,6 +2127,17 @@ const BulkUpdatePreviewModal = ({
1968
2127
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "semiBold", children: "Inspecting Target Records..." }),
1969
2128
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Computing proposed diffs and verifying against database schema." })
1970
2129
  ] }) }) : !previewData ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, style: { textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: "No preview data available." }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2130
+ publishOnApproval && lifecycleStats && lifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 4, padding: 3, background: "warning100", hasRadius: true, style: { border: "1px solid #f5c0b8" }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
2131
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "warning700", children: "⚠️ Safe Publishing Active:" }),
2132
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "warning700", children: [
2133
+ lifecycleStats.unreviewedCount,
2134
+ " target record(s) contain unreviewed draft changes (",
2135
+ lifecycleStats.draft,
2136
+ " Draft, ",
2137
+ lifecycleStats.modified,
2138
+ " Modified). By default, these will be updated as drafts without publishing unreviewed revisions to production visitors."
2139
+ ] })
2140
+ ] }) }),
1971
2141
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, marginBottom: 4, wrap: "wrap", children: [
1972
2142
  /* @__PURE__ */ jsxRuntime.jsxs(
1973
2143
  designSystem.Box,
@@ -2220,7 +2390,7 @@ function getAllNestedValues(obj, path, availableAttributes) {
2220
2390
  const parsed = parseDynamicZonePath(path);
2221
2391
  const parts = parsed.cleanPath.split(".");
2222
2392
  const [head, ...tail] = parts;
2223
- const rootTarget = obj[head];
2393
+ const rootTarget = getPropertyValue(obj, head);
2224
2394
  if (Array.isArray(rootTarget)) {
2225
2395
  const results = [];
2226
2396
  rootTarget.forEach((item, idx) => {
@@ -2255,6 +2425,27 @@ function getAllNestedValues(obj, path, availableAttributes) {
2255
2425
  }
2256
2426
  return [];
2257
2427
  }
2428
+ function getPropertyValue(target, key) {
2429
+ if (!target || typeof target !== "object" || !key) return void 0;
2430
+ if (target[key] !== void 0 && target[key] !== null) return target[key];
2431
+ const camelKey = key.replace(/_([a-z0-9])/g, (_, letter) => letter.toUpperCase());
2432
+ if (target[camelKey] !== void 0 && target[camelKey] !== null) return target[camelKey];
2433
+ const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase();
2434
+ if (target[snakeKey] !== void 0 && target[snakeKey] !== null) return target[snakeKey];
2435
+ const lowerKey = key.toLowerCase();
2436
+ const lowerNoUnderscore = lowerKey.replace(/_/g, "");
2437
+ const matchedKey = Object.keys(target).find((k) => {
2438
+ const kLower = k.toLowerCase();
2439
+ return kLower === lowerKey || kLower.replace(/_/g, "") === lowerNoUnderscore;
2440
+ });
2441
+ if (matchedKey && target[matchedKey] !== void 0 && target[matchedKey] !== null) {
2442
+ return target[matchedKey];
2443
+ }
2444
+ if (key in target) return target[key];
2445
+ if (camelKey in target) return target[camelKey];
2446
+ if (snakeKey in target) return target[snakeKey];
2447
+ return void 0;
2448
+ }
2258
2449
  function getNestedValue(obj, path) {
2259
2450
  if (!obj || !path) return void 0;
2260
2451
  const parsed = parseDynamicZonePath(path);
@@ -2271,12 +2462,12 @@ function getNestedValue(obj, path) {
2271
2462
  const item = current[idx];
2272
2463
  if (parsed.targetIndex !== void 0 && idx !== parsed.targetIndex) continue;
2273
2464
  if (parsed.targetComponentUid && item?.__component !== parsed.targetComponentUid) continue;
2274
- const found = extract(item?.[head], tail);
2465
+ const found = extract(getPropertyValue(item, head), tail);
2275
2466
  if (found !== void 0 && found !== null) return found;
2276
2467
  }
2277
2468
  return void 0;
2278
2469
  }
2279
- return extract(current[head], tail);
2470
+ return extract(getPropertyValue(current, head), tail);
2280
2471
  }
2281
2472
  return extract(obj, parts);
2282
2473
  }
@@ -2301,24 +2492,59 @@ function getAvailableColumnsForCollection(col, selectedAttribute, searchAttribut
2301
2492
  return;
2302
2493
  }
2303
2494
  let formattedLabel = attr;
2304
- const bracketMatch = attr.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
2305
- if (bracketMatch) {
2306
- const root = bracketMatch[1].charAt(0).toUpperCase() + bracketMatch[1].slice(1);
2307
- const comp = formatComponentName(bracketMatch[2]);
2308
- const sub = bracketMatch[3].split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2309
- formattedLabel = `${root} [${comp}] > ${sub}`;
2310
- } else if (attr.includes(".")) {
2311
- formattedLabel = attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2495
+ if (attr === "updated_at" || attr === "updatedAt") {
2496
+ formattedLabel = "Updated At";
2497
+ } else if (attr === "created_at" || attr === "createdAt") {
2498
+ formattedLabel = "Created At";
2499
+ } else if (attr === "published_at" || attr === "publishedAt") {
2500
+ formattedLabel = "Published At";
2312
2501
  } else {
2313
- formattedLabel = attr.charAt(0).toUpperCase() + attr.slice(1);
2502
+ const bracketMatch = attr.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
2503
+ if (bracketMatch) {
2504
+ const root = bracketMatch[1].charAt(0).toUpperCase() + bracketMatch[1].slice(1);
2505
+ const comp = formatComponentName(bracketMatch[2]);
2506
+ const sub = bracketMatch[3].split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2507
+ formattedLabel = `${root} [${comp}] > ${sub}`;
2508
+ } else if (attr.includes(".")) {
2509
+ formattedLabel = attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2510
+ } else {
2511
+ formattedLabel = attr.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
2512
+ }
2314
2513
  }
2315
2514
  cols.push({
2316
2515
  key: `attr_${attr}`,
2317
2516
  label: formattedLabel,
2318
2517
  sortable: !col.isSingleType,
2319
- defaultVisible: ["department", "email", "location"].includes(attr)
2518
+ defaultVisible: ["department", "email", "location", "updated_at", "updatedAt"].includes(attr)
2320
2519
  });
2321
2520
  });
2521
+ const hasUpdatedAt = candidateAttrs.some((a) => a === "updatedAt" || a === "updated_at");
2522
+ const hasCreatedAt = candidateAttrs.some((a) => a === "createdAt" || a === "created_at");
2523
+ const hasPublishedAt = candidateAttrs.some((a) => a === "publishedAt" || a === "published_at");
2524
+ if (!hasUpdatedAt) {
2525
+ cols.push({
2526
+ key: "attr_updatedAt",
2527
+ label: "Updated At",
2528
+ sortable: !col.isSingleType,
2529
+ defaultVisible: false
2530
+ });
2531
+ }
2532
+ if (!hasCreatedAt) {
2533
+ cols.push({
2534
+ key: "attr_createdAt",
2535
+ label: "Created At",
2536
+ sortable: !col.isSingleType,
2537
+ defaultVisible: false
2538
+ });
2539
+ }
2540
+ if (!hasPublishedAt && col.draftAndPublish) {
2541
+ cols.push({
2542
+ key: "attr_publishedAt",
2543
+ label: "Published At",
2544
+ sortable: !col.isSingleType,
2545
+ defaultVisible: false
2546
+ });
2547
+ }
2322
2548
  if (searchAttribute && searchAttribute !== selectedAttribute) {
2323
2549
  let formattedSearchLabel = searchAttribute;
2324
2550
  const bracketMatch = searchAttribute.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
@@ -2364,11 +2590,136 @@ function getAvailableColumnsForCollection(col, selectedAttribute, searchAttribut
2364
2590
  });
2365
2591
  return cols;
2366
2592
  }
2593
+ function getActiveSortLabel(currentSort, col) {
2594
+ const f = currentSort.field;
2595
+ const o = currentSort.order;
2596
+ const dir = o === "asc" ? "▲" : "▼";
2597
+ if (f === "updatedAt" || f === "updated_at") {
2598
+ return `Updated: ${o === "desc" ? "Newest" : "Oldest"} ${dir}`;
2599
+ }
2600
+ if (f === "createdAt" || f === "created_at") {
2601
+ return `Created: ${o === "desc" ? "Newest" : "Oldest"} ${dir}`;
2602
+ }
2603
+ if (f === "publishedAt" || f === "published_at") {
2604
+ return `Published: ${o === "desc" ? "Newest" : "Oldest"} ${dir}`;
2605
+ }
2606
+ if (f === "id") {
2607
+ return `ID: ${o === "desc" ? "Highest" : "Lowest"} ${dir}`;
2608
+ }
2609
+ let attrLabel = f;
2610
+ if (f.includes(".")) {
2611
+ attrLabel = f.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2612
+ } else {
2613
+ attrLabel = f.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
2614
+ }
2615
+ return `${attrLabel} (${o === "asc" ? "A → Z" : "Z → A"} ${dir})`;
2616
+ }
2617
+ function getSortOptionsForCollection(col, selectedAttribute, searchAttribute) {
2618
+ const options = [];
2619
+ const hasCustomUpdatedAt = col.attributes?.includes("updated_at");
2620
+ const updatedField = hasCustomUpdatedAt ? "updated_at" : "updatedAt";
2621
+ options.push({
2622
+ field: updatedField,
2623
+ order: "desc",
2624
+ label: "Updated At (Newest First ↓)",
2625
+ columnLabel: "Updated At",
2626
+ group: "timestamps"
2627
+ });
2628
+ options.push({
2629
+ field: updatedField,
2630
+ order: "asc",
2631
+ label: "Updated At (Oldest First ↑)",
2632
+ columnLabel: "Updated At",
2633
+ group: "timestamps"
2634
+ });
2635
+ const hasCustomCreatedAt = col.attributes?.includes("created_at");
2636
+ const createdField = hasCustomCreatedAt ? "created_at" : "createdAt";
2637
+ options.push({
2638
+ field: createdField,
2639
+ order: "desc",
2640
+ label: "Created At (Newest First ↓)",
2641
+ columnLabel: "Created At",
2642
+ group: "timestamps"
2643
+ });
2644
+ options.push({
2645
+ field: createdField,
2646
+ order: "asc",
2647
+ label: "Created At (Oldest First ↑)",
2648
+ columnLabel: "Created At",
2649
+ group: "timestamps"
2650
+ });
2651
+ if (col.draftAndPublish) {
2652
+ const hasCustomPublishedAt = col.attributes?.includes("published_at");
2653
+ const publishedField = hasCustomPublishedAt ? "published_at" : "publishedAt";
2654
+ options.push({
2655
+ field: publishedField,
2656
+ order: "desc",
2657
+ label: "Published At (Newest First ↓)",
2658
+ columnLabel: "Published At",
2659
+ group: "timestamps"
2660
+ });
2661
+ options.push({
2662
+ field: publishedField,
2663
+ order: "asc",
2664
+ label: "Published At (Oldest First ↑)",
2665
+ columnLabel: "Published At",
2666
+ group: "timestamps"
2667
+ });
2668
+ }
2669
+ options.push({
2670
+ field: "id",
2671
+ order: "desc",
2672
+ label: "Entry ID (Highest First ↓)",
2673
+ columnLabel: "ID",
2674
+ group: "system"
2675
+ });
2676
+ options.push({
2677
+ field: "id",
2678
+ order: "asc",
2679
+ label: "Entry ID (Lowest First ↑)",
2680
+ columnLabel: "ID",
2681
+ group: "system"
2682
+ });
2683
+ const systemKeywords = [
2684
+ "createdat",
2685
+ "created_at",
2686
+ "updatedat",
2687
+ "updated_at",
2688
+ "publishedat",
2689
+ "published_at",
2690
+ "id",
2691
+ "documentid"
2692
+ ];
2693
+ const attrs = (col.attributes || []).filter((a) => !systemKeywords.includes(a.toLowerCase()));
2694
+ attrs.forEach((attr) => {
2695
+ let formattedLabel = attr;
2696
+ if (attr.includes(".")) {
2697
+ formattedLabel = attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2698
+ } else {
2699
+ formattedLabel = attr.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
2700
+ }
2701
+ options.push({
2702
+ field: attr,
2703
+ order: "asc",
2704
+ label: `${formattedLabel} (A → Z / Lowest ↑)`,
2705
+ columnLabel: formattedLabel,
2706
+ group: "attributes"
2707
+ });
2708
+ options.push({
2709
+ field: attr,
2710
+ order: "desc",
2711
+ label: `${formattedLabel} (Z → A / Highest ↓)`,
2712
+ columnLabel: formattedLabel,
2713
+ group: "attributes"
2714
+ });
2715
+ });
2716
+ return options;
2717
+ }
2367
2718
  function renderHeaderCell(colDef, col, currentSort, selectedAttribute, searchAttribute, onSort) {
2368
2719
  if (colDef.key === "select") {
2369
2720
  return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { style: { width: 48 }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Select" }) }, "select");
2370
2721
  }
2371
- const isCurrentSorted = colDef.key === "identifier" && (currentSort.field === "name" || currentSort.field === "title") || colDef.key === "search_attr" && currentSort.field === searchAttribute || colDef.key === "target_attr" && currentSort.field === selectedAttribute || colDef.key.startsWith("attr_") && currentSort.field === colDef.key.replace("attr_", "");
2722
+ const isCurrentSorted = colDef.key === "identifier" && (currentSort.field === "name" || currentSort.field === "title" || currentSort.field === "id") || colDef.key === "search_attr" && currentSort.field === searchAttribute || colDef.key === "target_attr" && currentSort.field === selectedAttribute || colDef.key.startsWith("attr_") && (currentSort.field === colDef.key.replace("attr_", "") || currentSort.field === "updatedAt" && colDef.key === "attr_updated_at" || currentSort.field === "updated_at" && colDef.key === "attr_updatedAt" || currentSort.field === "createdAt" && colDef.key === "attr_created_at" || currentSort.field === "created_at" && colDef.key === "attr_createdAt" || currentSort.field === "publishedAt" && colDef.key === "attr_published_at" || currentSort.field === "published_at" && colDef.key === "attr_publishedAt");
2372
2723
  const sortArrow = isCurrentSorted ? currentSort.order === "asc" ? "▲" : "▼" : "⇅";
2373
2724
  const handleClick = () => {
2374
2725
  if (!colDef.sortable) return;
@@ -2400,6 +2751,15 @@ function formatDisplayValue(val) {
2400
2751
  if (val === void 0 || val === null || val === "") {
2401
2752
  return "(empty / not set)";
2402
2753
  }
2754
+ if (val instanceof Date) {
2755
+ return isNaN(val.getTime()) ? "(invalid date)" : val.toLocaleString();
2756
+ }
2757
+ if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(val)) {
2758
+ const d = new Date(val);
2759
+ if (!isNaN(d.getTime())) {
2760
+ return d.toLocaleString();
2761
+ }
2762
+ }
2403
2763
  if (Array.isArray(val)) {
2404
2764
  if (val.length === 0) return "(empty / unlinked)";
2405
2765
  return val.map(
@@ -2700,9 +3060,11 @@ const BulkUpdater = () => {
2700
3060
  const [pendingBatchesLoading, setPendingBatchesLoading] = React.useState(false);
2701
3061
  const [approvingBatchId, setApprovingBatchId] = React.useState(null);
2702
3062
  const [rejectingBatchId, setRejectingBatchId] = React.useState(null);
2703
- const [executingNowBatchId, setExecutingNowBatchId] = React.useState(null);
2704
3063
  const [makerNotesInput, setMakerNotesInput] = React.useState("");
2705
3064
  const [publishOnApproval, setPublishOnApproval] = React.useState(true);
3065
+ const [publishStrategy, setPublishStrategy] = React.useState("safe");
3066
+ const [unreviewedConsent, setUnreviewedConsent] = React.useState(false);
3067
+ const [publishConsent, setPublishConsent] = React.useState(false);
2706
3068
  const [showProcessingDashboard, setShowProcessingDashboard] = React.useState(false);
2707
3069
  const [dashboardBatchId, setDashboardBatchId] = React.useState("");
2708
3070
  const [dashboardJob, setDashboardJob] = React.useState(null);
@@ -2779,10 +3141,32 @@ const BulkUpdater = () => {
2779
3141
  } catch {
2780
3142
  }
2781
3143
  };
3144
+ const [searchRelationOptions, setSearchRelationOptions] = React.useState([]);
3145
+ const [searchRelationOptionsLoading, setSearchRelationOptionsLoading] = React.useState(false);
3146
+ const fetchSearchRelationOptions = async (targetUid) => {
3147
+ try {
3148
+ setSearchRelationOptionsLoading(true);
3149
+ const res = await get(`/bulk-updater/relation-options?targetUid=${encodeURIComponent(targetUid)}`);
3150
+ if (res.data && res.data.success) {
3151
+ setSearchRelationOptions(res.data.options || []);
3152
+ }
3153
+ } catch {
3154
+ setSearchRelationOptions([]);
3155
+ } finally {
3156
+ setSearchRelationOptionsLoading(false);
3157
+ }
3158
+ };
2782
3159
  const [searchAttribute, setSearchAttribute] = React.useState("");
2783
3160
  const [searchAttributeInput, setSearchAttributeInput] = React.useState("");
2784
3161
  const [attributesMeta, setAttributesMeta] = React.useState({});
2785
3162
  const effectiveSearchAttribute = searchAttribute || selectedAttribute;
3163
+ React.useEffect(() => {
3164
+ if (effectiveSearchAttribute && relationsMeta[effectiveSearchAttribute]) {
3165
+ fetchSearchRelationOptions(relationsMeta[effectiveSearchAttribute].targetUid);
3166
+ } else {
3167
+ setSearchRelationOptions([]);
3168
+ }
3169
+ }, [effectiveSearchAttribute, relationsMeta]);
2786
3170
  const [filterOperator, setFilterOperator] = React.useState("none");
2787
3171
  const [filterQuery, setFilterQuery] = React.useState("");
2788
3172
  const [entrySearch, setEntrySearch] = React.useState("");
@@ -2801,6 +3185,8 @@ const BulkUpdater = () => {
2801
3185
  const [collectionColumnVisibility, setCollectionColumnVisibility] = React.useState({});
2802
3186
  const [openColumnPickerUid, setOpenColumnPickerUid] = React.useState(null);
2803
3187
  const [columnSearchTerm, setColumnSearchTerm] = React.useState("");
3188
+ const [openSortPickerUid, setOpenSortPickerUid] = React.useState(null);
3189
+ const [sortSearchTerm, setSortSearchTerm] = React.useState("");
2804
3190
  const toggleColumn = (colUid, colKey, availableCols) => {
2805
3191
  setCollectionColumnVisibility((prev) => {
2806
3192
  const current = prev[colUid] || availableCols.filter((c) => c.defaultVisible).map((c) => c.key);
@@ -3333,21 +3719,45 @@ const BulkUpdater = () => {
3333
3719
  }
3334
3720
  const checkMatch = (val) => {
3335
3721
  const displayStr = formatDisplayValue(val);
3336
- const isValEmpty = val === void 0 || val === null || displayStr.trim() === "" || displayStr.trim().toLowerCase() === "(not set)" || displayStr.trim().toLowerCase() === "(empty / not set)" || displayStr.trim().toLowerCase() === "(empty / unlinked)";
3337
- switch (filterOperator) {
3338
- case "empty":
3339
- return isValEmpty;
3340
- case "not_empty":
3341
- return !isValEmpty;
3342
- case "equals":
3343
- return !isValEmpty && displayStr.trim().toLowerCase() === filterQuery.trim().toLowerCase();
3344
- case "contains":
3345
- return !isValEmpty && displayStr.toLowerCase().includes(filterQuery.trim().toLowerCase());
3346
- case "starts_with":
3347
- return !isValEmpty && displayStr.trim().toLowerCase().startsWith(filterQuery.trim().toLowerCase());
3348
- default:
3349
- return true;
3722
+ const isValEmpty = val === void 0 || val === null || Array.isArray(val) && val.length === 0 || displayStr.trim() === "" || displayStr.trim().toLowerCase() === "(not set)" || displayStr.trim().toLowerCase() === "(empty / not set)" || displayStr.trim().toLowerCase() === "(empty / unlinked)";
3723
+ if (filterOperator === "empty") return isValEmpty;
3724
+ if (filterOperator === "not_empty") return !isValEmpty;
3725
+ if (isValEmpty) return false;
3726
+ const q = filterQuery.trim().toLowerCase();
3727
+ if (!q) return true;
3728
+ const testString = (str) => {
3729
+ if (str === void 0 || str === null) return false;
3730
+ const s = String(str).trim().toLowerCase();
3731
+ switch (filterOperator) {
3732
+ case "equals":
3733
+ return s === q;
3734
+ case "contains":
3735
+ return s.includes(q);
3736
+ case "starts_with":
3737
+ return s.startsWith(q);
3738
+ default:
3739
+ return true;
3740
+ }
3741
+ };
3742
+ const testEntityObject = (obj) => {
3743
+ if (!obj || typeof obj !== "object") return false;
3744
+ if (testString(obj.name)) return true;
3745
+ if (testString(obj.title)) return true;
3746
+ if (testString(obj.email)) return true;
3747
+ if (testString(obj.username)) return true;
3748
+ if (testString(obj.documentId)) return true;
3749
+ if (testString(String(obj.id))) return true;
3750
+ if (testString(obj.slug)) return true;
3751
+ if (testString(formatDisplayValue(obj))) return true;
3752
+ return false;
3753
+ };
3754
+ if (Array.isArray(val)) {
3755
+ return val.some((item) => typeof item === "object" ? testEntityObject(item) : testString(item));
3350
3756
  }
3757
+ if (typeof val === "object" && val !== null) {
3758
+ if (testEntityObject(val)) return true;
3759
+ }
3760
+ return testString(displayStr);
3351
3761
  };
3352
3762
  if (filterOperator === "empty") {
3353
3763
  return matches.every((m) => checkMatch(m.value));
@@ -3459,6 +3869,40 @@ const BulkUpdater = () => {
3459
3869
  });
3460
3870
  return breakdown;
3461
3871
  };
3872
+ const selectedLifecycleStats = React.useMemo(() => {
3873
+ let published = 0;
3874
+ let modified = 0;
3875
+ let draft = 0;
3876
+ let pendingReview = 0;
3877
+ Object.keys(selectedEntries).forEach((key) => {
3878
+ if (!selectedEntries[key]) return;
3879
+ const parts = key.split(KEY_SEP);
3880
+ if (parts.length !== 2) return;
3881
+ const [uid, docId] = parts;
3882
+ const col = collections.find((c) => c.uid === uid) || null;
3883
+ const entry = collectionPages[uid]?.entries?.find((e) => e.documentId === docId || String(e.id) === docId) || col?.entries?.find((e) => e.documentId === docId || String(e.id) === docId);
3884
+ if (col && col.draftAndPublish === false) {
3885
+ published++;
3886
+ } else if (entry) {
3887
+ const { status } = getEntryLifecycleStatus(entry, col, pendingBatches);
3888
+ if (status === "published") published++;
3889
+ else if (status === "modified") modified++;
3890
+ else if (status === "draft") draft++;
3891
+ else if (status === "pending_review") pendingReview++;
3892
+ } else {
3893
+ draft++;
3894
+ }
3895
+ });
3896
+ const unreviewedCount = draft + modified + pendingReview;
3897
+ return {
3898
+ published,
3899
+ modified,
3900
+ draft,
3901
+ pendingReview,
3902
+ unreviewedCount,
3903
+ total: published + modified + draft + pendingReview
3904
+ };
3905
+ }, [selectedEntries, collections, collectionPages, pendingBatches]);
3462
3906
  const handleOpenConfirmModal = () => {
3463
3907
  if (!selectedAttribute) {
3464
3908
  toggleNotification({
@@ -3475,6 +3919,8 @@ const BulkUpdater = () => {
3475
3919
  });
3476
3920
  return;
3477
3921
  }
3922
+ setPublishStrategy("safe");
3923
+ setUnreviewedConsent(false);
3478
3924
  setShowConfirmModal(true);
3479
3925
  };
3480
3926
  const handleOpenPreviewModal = async () => {
@@ -3569,6 +4015,9 @@ const BulkUpdater = () => {
3569
4015
  failedCount: 0,
3570
4016
  isLive: false,
3571
4017
  publishOnApproval,
4018
+ publishStrategy,
4019
+ unreviewedConsent,
4020
+ unreviewedCount: selectedLifecycleStats.unreviewedCount,
3572
4021
  results: []
3573
4022
  });
3574
4023
  const payload = {
@@ -3580,7 +4029,10 @@ const BulkUpdater = () => {
3580
4029
  executionMode,
3581
4030
  makerNotes: makerNotesInput,
3582
4031
  forceImmediate,
3583
- publishOnApproval
4032
+ publishOnApproval,
4033
+ publishStrategy,
4034
+ unreviewedConsent,
4035
+ unreviewedCount: selectedLifecycleStats.unreviewedCount
3584
4036
  };
3585
4037
  const res = await post("/bulk-updater/batch-update", payload);
3586
4038
  setIsSubmittingBatch(false);
@@ -3748,6 +4200,7 @@ const BulkUpdater = () => {
3748
4200
  });
3749
4201
  return;
3750
4202
  }
4203
+ setPublishConsent(false);
3751
4204
  setShowPublishConfirmModal(true);
3752
4205
  };
3753
4206
  const executeBatchPublish = async (executionMode, forceImmediate = false) => {
@@ -3782,13 +4235,17 @@ const BulkUpdater = () => {
3782
4235
  failedCount: 0,
3783
4236
  isLive: true,
3784
4237
  publishOnApproval: true,
4238
+ unreviewedConsent: publishConsent,
4239
+ unreviewedCount: selectedLifecycleStats.unreviewedCount,
3785
4240
  results: []
3786
4241
  });
3787
4242
  const payload = {
3788
4243
  selectedEntries: targets,
3789
4244
  executionMode,
3790
4245
  makerNotes: publishMakerNotesInput,
3791
- forceImmediate
4246
+ forceImmediate,
4247
+ unreviewedConsent: publishConsent,
4248
+ unreviewedCount: selectedLifecycleStats.unreviewedCount
3792
4249
  };
3793
4250
  const res = await post("/bulk-updater/batch-publish", payload);
3794
4251
  setIsSubmittingBatch(false);
@@ -4369,7 +4826,27 @@ const BulkUpdater = () => {
4369
4826
  ]
4370
4827
  }
4371
4828
  ) }),
4372
- (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsx(
4829
+ (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 200 }, children: effectiveSearchAttribute && relationsMeta[effectiveSearchAttribute] ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
4830
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: `Target ${relationsMeta[effectiveSearchAttribute].targetDisplayName} Relation` }),
4831
+ /* @__PURE__ */ jsxRuntime.jsx(
4832
+ designSystem.Combobox,
4833
+ {
4834
+ autocomplete: { type: "list", filter: "contains" },
4835
+ placeholder: searchRelationOptionsLoading ? "Loading entities..." : `Search & pick ${relationsMeta[effectiveSearchAttribute].targetDisplayName} or type text...`,
4836
+ value: filterQuery,
4837
+ onClear: () => setFilterQuery(""),
4838
+ onChange: (val) => {
4839
+ if (typeof val === "string") {
4840
+ setFilterQuery(val);
4841
+ }
4842
+ },
4843
+ onInputChange: (e) => {
4844
+ setFilterQuery(e.target.value);
4845
+ },
4846
+ children: searchRelationOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: opt.label, textValue: opt.label, children: opt.label }, opt.documentId))
4847
+ }
4848
+ )
4849
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
4373
4850
  designSystem.TextInput,
4374
4851
  {
4375
4852
  label: effectiveSearchAttribute ? `Value for "${effectiveSearchAttribute}"` : "Value to match",
@@ -4519,41 +4996,127 @@ const BulkUpdater = () => {
4519
4996
  ) : null
4520
4997
  ] }),
4521
4998
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "center", wrap: "wrap", children: [
4522
- !col.isSingleType && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
4523
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: "Sort:" }),
4524
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { width: 190 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
4525
- designSystem.SingleSelect,
4999
+ !col.isSingleType && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { position: "relative", display: "inline-block" }, children: [
5000
+ /* @__PURE__ */ jsxRuntime.jsx(
5001
+ designSystem.Button,
4526
5002
  {
5003
+ variant: openSortPickerUid === col.uid ? "secondary" : "tertiary",
4527
5004
  size: "S",
4528
- value: `${currentSort.field}:${currentSort.order}`,
4529
- onChange: (val) => {
4530
- const [f, o] = String(val).split(":");
4531
- handleCollectionSortChange(col.uid, f, o);
5005
+ onClick: () => {
5006
+ setOpenSortPickerUid(openSortPickerUid === col.uid ? null : col.uid);
5007
+ setSortSearchTerm("");
5008
+ if (openColumnPickerUid === col.uid) setOpenColumnPickerUid(null);
5009
+ },
5010
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, alignItems: "center", children: [
5011
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", fontWeight: "bold", children: "Sort:" }),
5012
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "semiBold", children: getActiveSortLabel(currentSort) })
5013
+ ] })
5014
+ }
5015
+ ),
5016
+ openSortPickerUid === col.uid && /* @__PURE__ */ jsxRuntime.jsxs(
5017
+ designSystem.Box,
5018
+ {
5019
+ style: {
5020
+ position: "absolute",
5021
+ top: "calc(100% + 6px)",
5022
+ left: 0,
5023
+ zIndex: 220,
5024
+ width: 330,
5025
+ maxHeight: 440,
5026
+ background: "#ffffff",
5027
+ borderRadius: 8,
5028
+ boxShadow: "0 10px 25px rgba(33, 33, 52, 0.18)",
5029
+ border: "1px solid #dcdce4",
5030
+ padding: 12,
5031
+ display: "flex",
5032
+ flexDirection: "column",
5033
+ gap: 8
4532
5034
  },
4533
5035
  children: [
4534
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "createdAt:desc", children: "Created: Newest" }),
4535
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "createdAt:asc", children: "Created: Oldest" }),
4536
- hasAttr && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4537
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: `${selectedAttribute}:asc`, children: [
4538
- selectedAttribute,
4539
- " (A → Z)"
4540
- ] }),
4541
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: `${selectedAttribute}:desc`, children: [
4542
- selectedAttribute,
4543
- " (Z → A)"
4544
- ] })
4545
- ] }),
4546
- col.attributes.includes("name") && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4547
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "name:asc", children: "Name (A → Z)" }),
4548
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "name:desc", children: "Name (Z → A)" })
5036
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
5037
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: "Sort by Column" }),
5038
+ /* @__PURE__ */ jsxRuntime.jsx(
5039
+ designSystem.Button,
5040
+ {
5041
+ variant: "ghost",
5042
+ size: "S",
5043
+ onClick: () => setOpenSortPickerUid(null),
5044
+ style: { padding: "2px 6px", height: "auto", minWidth: "unset" },
5045
+ children: "✕"
5046
+ }
5047
+ )
4549
5048
  ] }),
4550
- col.attributes.includes("title") && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4551
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "title:asc", children: "Title (A → Z)" }),
4552
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "title:desc", children: "Title (Z → A)" })
4553
- ] })
5049
+ /* @__PURE__ */ jsxRuntime.jsx(
5050
+ designSystem.TextInput,
5051
+ {
5052
+ size: "S",
5053
+ placeholder: "Search columns to sort...",
5054
+ value: sortSearchTerm,
5055
+ onChange: (e) => setSortSearchTerm(e.target.value)
5056
+ }
5057
+ ),
5058
+ /* @__PURE__ */ jsxRuntime.jsx(
5059
+ designSystem.Box,
5060
+ {
5061
+ style: {
5062
+ maxHeight: 320,
5063
+ overflowY: "auto",
5064
+ display: "flex",
5065
+ flexDirection: "column",
5066
+ gap: 4
5067
+ },
5068
+ children: getSortOptionsForCollection(col).filter((opt) => {
5069
+ if (!sortSearchTerm.trim()) return true;
5070
+ const term = sortSearchTerm.toLowerCase().trim();
5071
+ return opt.label.toLowerCase().includes(term) || opt.field.toLowerCase().includes(term);
5072
+ }).map((opt) => {
5073
+ const isSelected = (currentSort.field === opt.field || currentSort.field === "updatedAt" && opt.field === "updated_at" || currentSort.field === "updated_at" && opt.field === "updatedAt" || currentSort.field === "createdAt" && opt.field === "created_at" || currentSort.field === "created_at" && opt.field === "createdAt") && currentSort.order === opt.order;
5074
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5075
+ designSystem.Box,
5076
+ {
5077
+ onClick: () => {
5078
+ handleCollectionSortChange(col.uid, opt.field, opt.order);
5079
+ setOpenSortPickerUid(null);
5080
+ setSortSearchTerm("");
5081
+ },
5082
+ style: {
5083
+ padding: "6px 10px",
5084
+ borderRadius: 6,
5085
+ cursor: "pointer",
5086
+ background: isSelected ? "rgba(73, 69, 255, 0.12)" : "transparent",
5087
+ border: isSelected ? "1px solid #4945ff" : "1px solid transparent",
5088
+ display: "flex",
5089
+ alignItems: "center",
5090
+ justifyContent: "space-between",
5091
+ transition: "background 0.15s ease"
5092
+ },
5093
+ onMouseEnter: (e) => {
5094
+ if (!isSelected) e.currentTarget.style.background = "#f6f6f9";
5095
+ },
5096
+ onMouseLeave: (e) => {
5097
+ if (!isSelected) e.currentTarget.style.background = "transparent";
5098
+ },
5099
+ children: [
5100
+ /* @__PURE__ */ jsxRuntime.jsx(
5101
+ designSystem.Typography,
5102
+ {
5103
+ variant: "omega",
5104
+ fontWeight: isSelected ? "bold" : "normal",
5105
+ textColor: isSelected ? "primary700" : "neutral800",
5106
+ children: opt.label
5107
+ }
5108
+ ),
5109
+ isSelected && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { size: "S", backgroundColor: "primary100", textColor: "primary700", children: "Active" })
5110
+ ]
5111
+ },
5112
+ `${opt.field}:${opt.order}`
5113
+ );
5114
+ })
5115
+ }
5116
+ )
4554
5117
  ]
4555
5118
  }
4556
- ) })
5119
+ )
4557
5120
  ] }),
4558
5121
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { position: "relative", display: "inline-block" }, children: [
4559
5122
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -4565,6 +5128,7 @@ const BulkUpdater = () => {
4565
5128
  onClick: () => {
4566
5129
  setOpenColumnPickerUid(openColumnPickerUid === col.uid ? null : col.uid);
4567
5130
  setColumnSearchTerm("");
5131
+ if (openSortPickerUid === col.uid) setOpenSortPickerUid(null);
4568
5132
  },
4569
5133
  children: [
4570
5134
  "Columns (",
@@ -5190,27 +5754,42 @@ const BulkUpdater = () => {
5190
5754
  )
5191
5755
  ] })
5192
5756
  ] }),
5193
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
5194
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
5195
- /* @__PURE__ */ jsxRuntime.jsx(
5196
- designSystem.Checkbox,
5197
- {
5198
- checked: publishOnApproval,
5199
- onCheckedChange: (checked) => setPublishOnApproval(Boolean(checked)),
5200
- children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: "Auto-Publish after Update" })
5201
- }
5202
- ),
5203
- /* @__PURE__ */ jsxRuntime.jsx(
5204
- designSystem.Badge,
5205
- {
5206
- backgroundColor: publishOnApproval ? "success100" : "warning100",
5207
- textColor: publishOnApproval ? "success700" : "warning700",
5208
- children: publishOnApproval ? "✓ LIVE PUBLISH" : "DRAFT ONLY"
5209
- }
5210
- )
5757
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
5758
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
5759
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
5760
+ /* @__PURE__ */ jsxRuntime.jsx(
5761
+ designSystem.Checkbox,
5762
+ {
5763
+ checked: publishOnApproval,
5764
+ onCheckedChange: (checked) => setPublishOnApproval(Boolean(checked)),
5765
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: "Auto-Publish after Update" })
5766
+ }
5767
+ ),
5768
+ /* @__PURE__ */ jsxRuntime.jsx(
5769
+ designSystem.Badge,
5770
+ {
5771
+ backgroundColor: publishOnApproval ? "success100" : "warning100",
5772
+ textColor: publishOnApproval ? "success700" : "warning700",
5773
+ children: publishOnApproval ? "✓ LIVE PUBLISH" : "DRAFT ONLY"
5774
+ }
5775
+ )
5776
+ ] }),
5777
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: publishOnApproval ? "When enabled, entries will be published live immediately upon batch execution for draft-enabled content types." : "When disabled, entries will be saved as draft versions without publishing to live visitors." })
5211
5778
  ] }),
5212
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: publishOnApproval ? "When enabled, entries will be published live immediately upon batch execution for draft-enabled content types." : "When disabled, entries will be saved as draft versions without publishing to live visitors." })
5213
- ] }) }),
5779
+ publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 3, padding: 3, background: "warning100", hasRadius: true, style: { border: "1px solid #f5c0b8" }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "flex-start", children: [
5780
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "warning700", children: "⚠️ Unreviewed Content Alert:" }),
5781
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "warning700", children: [
5782
+ selectedLifecycleStats.unreviewedCount,
5783
+ " of ",
5784
+ totalSelectedCount,
5785
+ " selected record(s) contain unreviewed draft changes (",
5786
+ selectedLifecycleStats.draft,
5787
+ " Draft, ",
5788
+ selectedLifecycleStats.modified,
5789
+ " Modified). Publishing them live will push all unreviewed draft content across all fields to live visitors. You can select safe publishing (recommended) or provide explicit consent at review time."
5790
+ ] })
5791
+ ] }) })
5792
+ ] }),
5214
5793
  selectedCol && selectedAttribute && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 4, padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
5215
5794
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: "Target Selection:" }),
5216
5795
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
@@ -5396,6 +5975,12 @@ const BulkUpdater = () => {
5396
5975
  children: batch.summary.publishOnApproval === false ? "DRAFT ONLY" : "AUTO-PUBLISH"
5397
5976
  }
5398
5977
  ),
5978
+ batch.summary.publishStrategy === "safe" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "SAFE PUBLISH" }),
5979
+ batch.summary.publishStrategy === "all" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "warning100", textColor: "warning700", children: "PUBLISH ALL (CONSENTED)" }),
5980
+ Boolean(batch.summary.unreviewedCount) && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral700", children: [
5981
+ "Unreviewed: ",
5982
+ batch.summary.unreviewedCount
5983
+ ] }),
5399
5984
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
5400
5985
  "• ",
5401
5986
  batch.summary.totalTargets,
@@ -5543,7 +6128,9 @@ const BulkUpdater = () => {
5543
6128
  rec.skippedCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "warning100", textColor: "warning700", style: { marginLeft: 6 }, children: [
5544
6129
  rec.skippedCount,
5545
6130
  " skipped"
5546
- ] })
6131
+ ] }),
6132
+ rec.publishStrategy === "safe" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "secondary100", textColor: "secondary700", style: { marginLeft: 6 }, children: "Safe Publish" }),
6133
+ rec.publishStrategy === "all" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "warning100", textColor: "warning700", style: { marginLeft: 6 }, children: "Publish All (Consented)" })
5547
6134
  ] }),
5548
6135
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { minWidth: 120 }, children: rec.isRolledBack ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
5549
6136
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral200", textColor: "neutral700", children: "ROLLED BACK" }),
@@ -5762,6 +6349,31 @@ const BulkUpdater = () => {
5762
6349
  ] })
5763
6350
  ] })
5764
6351
  ] }),
6352
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid rgba(120, 120, 150, 0.15)" }, children: [
6353
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: [
6354
+ "LIFECYCLE STATUS BREAKDOWN (",
6355
+ totalSelectedCount,
6356
+ " Selected)"
6357
+ ] }),
6358
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", children: [
6359
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "success100", textColor: "success700", children: [
6360
+ "✓ Published / Approved: ",
6361
+ selectedLifecycleStats.published
6362
+ ] }),
6363
+ selectedLifecycleStats.modified > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
6364
+ "📝 Modified (Unreviewed Revisions): ",
6365
+ selectedLifecycleStats.modified
6366
+ ] }),
6367
+ selectedLifecycleStats.draft > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
6368
+ "📄 Draft (Never Published): ",
6369
+ selectedLifecycleStats.draft
6370
+ ] }),
6371
+ selectedLifecycleStats.pendingReview > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
6372
+ "⏳ Pending Review: ",
6373
+ selectedLifecycleStats.pendingReview
6374
+ ] })
6375
+ ] })
6376
+ ] }),
5765
6377
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, children: [
5766
6378
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: "COLLECTION BREAKDOWN" }),
5767
6379
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 1, children: Object.entries(getSelectedBreakdown()).map(([name, count]) => /* @__PURE__ */ jsxRuntime.jsxs(
@@ -5812,6 +6424,128 @@ const BulkUpdater = () => {
5812
6424
  ] })
5813
6425
  }
5814
6426
  ),
6427
+ publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
6428
+ designSystem.Box,
6429
+ {
6430
+ padding: 4,
6431
+ background: "neutral100",
6432
+ hasRadius: true,
6433
+ marginBottom: 4,
6434
+ style: { border: "1px solid #d9d8ff" },
6435
+ children: [
6436
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, display: "block", children: "PUBLISHING STRATEGY & GOVERNANCE CONSENT" }),
6437
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 3, children: [
6438
+ /* @__PURE__ */ jsxRuntime.jsx(
6439
+ designSystem.Box,
6440
+ {
6441
+ padding: 3,
6442
+ hasRadius: true,
6443
+ style: {
6444
+ border: publishStrategy === "safe" ? "2px solid #4945ff" : "1px solid #eaeaef",
6445
+ backgroundColor: publishStrategy === "safe" ? "#f0f0ff" : "#ffffff",
6446
+ cursor: "pointer"
6447
+ },
6448
+ onClick: () => {
6449
+ setPublishStrategy("safe");
6450
+ setUnreviewedConsent(false);
6451
+ },
6452
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "flex-start", children: [
6453
+ /* @__PURE__ */ jsxRuntime.jsx(
6454
+ "input",
6455
+ {
6456
+ type: "radio",
6457
+ id: "strategy-safe",
6458
+ name: "publishStrategy",
6459
+ checked: publishStrategy === "safe",
6460
+ onChange: () => {
6461
+ setPublishStrategy("safe");
6462
+ setUnreviewedConsent(false);
6463
+ },
6464
+ style: { marginTop: 3 }
6465
+ }
6466
+ ),
6467
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
6468
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", display: "block", children: "🛡️ Safe Publishing (Recommended)" }),
6469
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
6470
+ "Only publish records that are already approved & live (",
6471
+ selectedLifecycleStats.published,
6472
+ " record",
6473
+ selectedLifecycleStats.published === 1 ? "" : "s",
6474
+ "). The ",
6475
+ selectedLifecycleStats.unreviewedCount,
6476
+ " unreviewed or draft record(s) will receive the bulk update in Draft mode without publishing unreviewed draft revisions to visitors."
6477
+ ] })
6478
+ ] })
6479
+ ] })
6480
+ }
6481
+ ),
6482
+ /* @__PURE__ */ jsxRuntime.jsx(
6483
+ designSystem.Box,
6484
+ {
6485
+ padding: 3,
6486
+ hasRadius: true,
6487
+ style: {
6488
+ border: publishStrategy === "all" ? "2px solid #d02b20" : "1px solid #eaeaef",
6489
+ backgroundColor: publishStrategy === "all" ? "#fff5f5" : "#ffffff",
6490
+ cursor: "pointer"
6491
+ },
6492
+ onClick: () => setPublishStrategy("all"),
6493
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "flex-start", children: [
6494
+ /* @__PURE__ */ jsxRuntime.jsx(
6495
+ "input",
6496
+ {
6497
+ type: "radio",
6498
+ id: "strategy-all",
6499
+ name: "publishStrategy",
6500
+ checked: publishStrategy === "all",
6501
+ onChange: () => setPublishStrategy("all"),
6502
+ style: { marginTop: 3 }
6503
+ }
6504
+ ),
6505
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
6506
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "⚠️ Publish All Selected Records (Includes Unreviewed Drafts)" }),
6507
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
6508
+ "Publish all ",
6509
+ totalSelectedCount,
6510
+ " records live to production immediately, pushing all pending unreviewed draft edits in the ",
6511
+ selectedLifecycleStats.unreviewedCount,
6512
+ " draft/modified record(s) live."
6513
+ ] })
6514
+ ] })
6515
+ ] })
6516
+ }
6517
+ ),
6518
+ publishStrategy === "all" && /* @__PURE__ */ jsxRuntime.jsx(
6519
+ designSystem.Box,
6520
+ {
6521
+ padding: 3,
6522
+ background: "danger100",
6523
+ hasRadius: true,
6524
+ style: { border: "1px solid #f5c0b8" },
6525
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "flex-start", children: [
6526
+ /* @__PURE__ */ jsxRuntime.jsx(
6527
+ designSystem.Checkbox,
6528
+ {
6529
+ name: "unreviewedConsent",
6530
+ checked: unreviewedConsent,
6531
+ onCheckedChange: (checked) => setUnreviewedConsent(Boolean(checked))
6532
+ }
6533
+ ),
6534
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { cursor: "pointer" }, onClick: () => setUnreviewedConsent(!unreviewedConsent), children: [
6535
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "Mandatory Risk Consent Required" }),
6536
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "danger700", children: [
6537
+ "I understand and explicitly consent that ",
6538
+ selectedLifecycleStats.unreviewedCount,
6539
+ " record(s) with unreviewed draft revisions or saved draft states will be published live to production visitors."
6540
+ ] })
6541
+ ] })
6542
+ ] })
6543
+ }
6544
+ )
6545
+ ] })
6546
+ ]
6547
+ }
6548
+ ),
5815
6549
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsx(
5816
6550
  designSystem.TextInput,
5817
6551
  {
@@ -5838,54 +6572,61 @@ const BulkUpdater = () => {
5838
6572
  zIndex: 20,
5839
6573
  boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
5840
6574
  },
5841
- children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
5842
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
5843
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
5844
- /* @__PURE__ */ jsxRuntime.jsx(
5845
- designSystem.Button,
5846
- {
5847
- variant: executionWindow?.isBusinessHours ? "default" : "secondary",
5848
- size: "M",
5849
- onClick: () => executeBatchWithMode("maker_checker"),
5850
- loading: updating,
5851
- children: "Submit for Checker Approval"
5852
- }
5853
- ),
5854
- executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5855
- /* @__PURE__ */ jsxRuntime.jsxs(
6575
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, style: { width: "100%" }, children: [
6576
+ publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: "⚠️ Explicit consent is required before publishing records with unreviewed draft revisions." }),
6577
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
6578
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
6579
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
6580
+ /* @__PURE__ */ jsxRuntime.jsx(
5856
6581
  designSystem.Button,
5857
6582
  {
5858
- variant: "secondary",
6583
+ variant: executionWindow?.isBusinessHours ? "default" : "secondary",
5859
6584
  size: "M",
5860
- onClick: () => executeBatchWithMode("queue_offpeak"),
6585
+ onClick: () => executeBatchWithMode("maker_checker"),
5861
6586
  loading: updating,
5862
- children: [
5863
- "Queue for Off-Peak (",
5864
- executionWindow?.offPeakStartTime || "18:00",
5865
- ")"
5866
- ]
6587
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6588
+ children: "Submit for Checker Approval"
5867
6589
  }
5868
6590
  ),
5869
- /* @__PURE__ */ jsxRuntime.jsx(
6591
+ executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6592
+ /* @__PURE__ */ jsxRuntime.jsxs(
6593
+ designSystem.Button,
6594
+ {
6595
+ variant: "secondary",
6596
+ size: "M",
6597
+ onClick: () => executeBatchWithMode("queue_offpeak"),
6598
+ loading: updating,
6599
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6600
+ children: [
6601
+ "Queue for Off-Peak (",
6602
+ executionWindow?.offPeakStartTime || "18:00",
6603
+ ")"
6604
+ ]
6605
+ }
6606
+ ),
6607
+ /* @__PURE__ */ jsxRuntime.jsx(
6608
+ designSystem.Button,
6609
+ {
6610
+ variant: "danger-light",
6611
+ size: "S",
6612
+ onClick: () => executeBatchWithMode("immediate", true),
6613
+ loading: updating,
6614
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6615
+ children: "Force Run Now"
6616
+ }
6617
+ )
6618
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
5870
6619
  designSystem.Button,
5871
6620
  {
5872
- variant: "danger-light",
5873
- size: "S",
5874
- onClick: () => executeBatchWithMode("immediate", true),
6621
+ variant: "default",
6622
+ size: "M",
6623
+ onClick: () => executeBatchWithMode("immediate"),
5875
6624
  loading: updating,
5876
- children: "Force Run Now"
6625
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6626
+ children: "Confirm & Execute Now"
5877
6627
  }
5878
6628
  )
5879
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(
5880
- designSystem.Button,
5881
- {
5882
- variant: "default",
5883
- size: "M",
5884
- onClick: () => executeBatchWithMode("immediate"),
5885
- loading: updating,
5886
- children: "Confirm & Execute Now"
5887
- }
5888
- )
6629
+ ] })
5889
6630
  ] })
5890
6631
  ] })
5891
6632
  }
@@ -5960,6 +6701,27 @@ const BulkUpdater = () => {
5960
6701
  gap: 16
5961
6702
  },
5962
6703
  children: [
6704
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
6705
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: [
6706
+ "LIFECYCLE STATUS BREAKDOWN (",
6707
+ totalSelectedCount,
6708
+ " Selected)"
6709
+ ] }),
6710
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", children: [
6711
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "success100", textColor: "success700", children: [
6712
+ "✓ Published: ",
6713
+ selectedLifecycleStats.published
6714
+ ] }),
6715
+ selectedLifecycleStats.modified > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
6716
+ "📝 Modified (Unreviewed Revisions): ",
6717
+ selectedLifecycleStats.modified
6718
+ ] }),
6719
+ selectedLifecycleStats.draft > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
6720
+ "📄 Draft (Never Published): ",
6721
+ selectedLifecycleStats.draft
6722
+ ] })
6723
+ ] })
6724
+ ] }),
5963
6725
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
5964
6726
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: "Collection Breakdown:" }),
5965
6727
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 2, children: collections.filter(
@@ -5998,6 +6760,37 @@ const BulkUpdater = () => {
5998
6760
  );
5999
6761
  }) })
6000
6762
  ] }),
6763
+ selectedLifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(
6764
+ designSystem.Box,
6765
+ {
6766
+ padding: 4,
6767
+ background: "danger100",
6768
+ hasRadius: true,
6769
+ style: { border: "1px solid #f5c0b8" },
6770
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "flex-start", children: [
6771
+ /* @__PURE__ */ jsxRuntime.jsx(
6772
+ designSystem.Checkbox,
6773
+ {
6774
+ name: "publishConsent",
6775
+ checked: publishConsent,
6776
+ onCheckedChange: (checked) => setPublishConsent(Boolean(checked))
6777
+ }
6778
+ ),
6779
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { cursor: "pointer" }, onClick: () => setPublishConsent(!publishConsent), children: [
6780
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "Mandatory Risk Consent Required" }),
6781
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "danger700", children: [
6782
+ "I understand and explicitly consent that ",
6783
+ selectedLifecycleStats.unreviewedCount,
6784
+ " record(s) contain unreviewed draft changes (",
6785
+ selectedLifecycleStats.draft,
6786
+ " Draft, ",
6787
+ selectedLifecycleStats.modified,
6788
+ " Modified) and will be published live to production visitors."
6789
+ ] })
6790
+ ] })
6791
+ ] })
6792
+ }
6793
+ ),
6001
6794
  executionWindow?.isBusinessHours && /* @__PURE__ */ jsxRuntime.jsxs(
6002
6795
  designSystem.Box,
6003
6796
  {
@@ -6042,54 +6835,61 @@ const BulkUpdater = () => {
6042
6835
  flexShrink: 0,
6043
6836
  boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
6044
6837
  },
6045
- children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
6046
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "M", onClick: () => setShowPublishConfirmModal(false), children: "Cancel" }),
6047
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
6048
- /* @__PURE__ */ jsxRuntime.jsx(
6049
- designSystem.Button,
6050
- {
6051
- variant: executionWindow?.isBusinessHours ? "default" : "secondary",
6052
- size: "M",
6053
- onClick: () => executeBatchPublish("maker_checker"),
6054
- loading: publishing,
6055
- children: "Submit for Checker Approval"
6056
- }
6057
- ),
6058
- executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6059
- /* @__PURE__ */ jsxRuntime.jsxs(
6838
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, style: { width: "100%" }, children: [
6839
+ selectedLifecycleStats.unreviewedCount > 0 && !publishConsent && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: "⚠️ Please check the mandatory consent box above to confirm publishing unreviewed draft records." }),
6840
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
6841
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "M", onClick: () => setShowPublishConfirmModal(false), children: "Cancel" }),
6842
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
6843
+ /* @__PURE__ */ jsxRuntime.jsx(
6060
6844
  designSystem.Button,
6061
6845
  {
6062
- variant: "secondary",
6846
+ variant: executionWindow?.isBusinessHours ? "default" : "secondary",
6063
6847
  size: "M",
6064
- onClick: () => executeBatchPublish("queue_offpeak"),
6848
+ onClick: () => executeBatchPublish("maker_checker"),
6065
6849
  loading: publishing,
6066
- children: [
6067
- "Queue for Off-Peak (",
6068
- executionWindow?.offPeakStartTime || "18:00",
6069
- ")"
6070
- ]
6850
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6851
+ children: "Submit for Checker Approval"
6071
6852
  }
6072
6853
  ),
6073
- /* @__PURE__ */ jsxRuntime.jsx(
6854
+ executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6855
+ /* @__PURE__ */ jsxRuntime.jsxs(
6856
+ designSystem.Button,
6857
+ {
6858
+ variant: "secondary",
6859
+ size: "M",
6860
+ onClick: () => executeBatchPublish("queue_offpeak"),
6861
+ loading: publishing,
6862
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6863
+ children: [
6864
+ "Queue for Off-Peak (",
6865
+ executionWindow?.offPeakStartTime || "18:00",
6866
+ ")"
6867
+ ]
6868
+ }
6869
+ ),
6870
+ /* @__PURE__ */ jsxRuntime.jsx(
6871
+ designSystem.Button,
6872
+ {
6873
+ variant: "danger-light",
6874
+ size: "S",
6875
+ onClick: () => executeBatchPublish("immediate", true),
6876
+ loading: publishing,
6877
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6878
+ children: "Force Publish Now"
6879
+ }
6880
+ )
6881
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
6074
6882
  designSystem.Button,
6075
6883
  {
6076
- variant: "danger-light",
6077
- size: "S",
6078
- onClick: () => executeBatchPublish("immediate", true),
6884
+ variant: "default",
6885
+ size: "M",
6886
+ onClick: () => executeBatchPublish("immediate"),
6079
6887
  loading: publishing,
6080
- children: "Force Publish Now"
6888
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6889
+ children: "Confirm & Publish Now"
6081
6890
  }
6082
6891
  )
6083
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(
6084
- designSystem.Button,
6085
- {
6086
- variant: "default",
6087
- size: "M",
6088
- onClick: () => executeBatchPublish("immediate"),
6089
- loading: publishing,
6090
- children: "Confirm & Publish Now"
6091
- }
6092
- )
6892
+ ] })
6093
6893
  ] })
6094
6894
  ] })
6095
6895
  }
@@ -6112,6 +6912,7 @@ const BulkUpdater = () => {
6112
6912
  previewData,
6113
6913
  attribute: selectedAttribute,
6114
6914
  publishOnApproval,
6915
+ lifecycleStats: selectedLifecycleStats,
6115
6916
  targetValueDisplay: relationsMeta[selectedAttribute] ? selectedRelationDocId === "__clear__" ? "Clear / Unlink Relation" : relationOptions.find((o) => o.documentId === selectedRelationDocId)?.label || selectedRelationDocId || "(none)" : useRegex ? `Replace /${regexPattern}/g with "${attributeValue}"` : attributeValue
6116
6917
  }
6117
6918
  ),
@@ -6132,9 +6933,12 @@ const BulkUpdater = () => {
6132
6933
  exports.default = BulkUpdater;
6133
6934
  exports.formatComponentName = formatComponentName;
6134
6935
  exports.formatDisplayValue = formatDisplayValue;
6936
+ exports.getActiveSortLabel = getActiveSortLabel;
6135
6937
  exports.getAllNestedValues = getAllNestedValues;
6136
6938
  exports.getAvailableColumnsForCollection = getAvailableColumnsForCollection;
6137
6939
  exports.getEntryLifecycleStatus = getEntryLifecycleStatus;
6138
6940
  exports.getNestedValue = getNestedValue;
6941
+ exports.getPropertyValue = getPropertyValue;
6942
+ exports.getSortOptionsForCollection = getSortOptionsForCollection;
6139
6943
  exports.parseDynamicZonePath = parseDynamicZonePath;
6140
- //# sourceMappingURL=BulkUpdater-DtBH2c0-.js.map
6944
+ //# sourceMappingURL=BulkUpdater-C0uPoso2.js.map