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.
@@ -162,6 +162,8 @@ function CsvManagerTab() {
162
162
  const [showConfirmModal, setShowConfirmModal] = useState(false);
163
163
  const [makerNotesInput, setMakerNotesInput] = useState("");
164
164
  const [submittingBatch, setSubmittingBatch] = useState(false);
165
+ const [publishStrategy, setPublishStrategy] = useState("safe");
166
+ const [unreviewedConsent, setUnreviewedConsent] = useState(false);
165
167
  useEffect(() => {
166
168
  fetchInitialData();
167
169
  }, []);
@@ -333,7 +335,9 @@ function CsvManagerTab() {
333
335
  isLastChunk: isLast,
334
336
  startRowNumber,
335
337
  mode: importMode,
336
- publish: publishOnImport
338
+ publish: publishOnImport,
339
+ publishStrategy,
340
+ unreviewedConsent
337
341
  });
338
342
  if (!res.data || !res.data.success) {
339
343
  throw new Error(res.data?.error || `Chunk ${chunkIdx + 1} failed to process`);
@@ -437,7 +441,10 @@ function CsvManagerTab() {
437
441
  makerNotes: makerNotesInput,
438
442
  forceImmediate,
439
443
  mode: importMode,
440
- publish: publishOnImport
444
+ publish: publishOnImport,
445
+ publishStrategy,
446
+ unreviewedConsent,
447
+ unreviewedCount: preflightReport?.lifecycleStats?.unreviewedCount || 0
441
448
  });
442
449
  setShowConfirmModal(false);
443
450
  if (res.data && res.data.pending) {
@@ -846,7 +853,11 @@ function CsvManagerTab() {
846
853
  variant: "default",
847
854
  size: "L",
848
855
  startIcon: /* @__PURE__ */ jsx(Check, {}),
849
- onClick: () => setShowConfirmModal(true),
856
+ onClick: () => {
857
+ setPublishStrategy("safe");
858
+ setUnreviewedConsent(false);
859
+ setShowConfirmModal(true);
860
+ },
850
861
  loading: importing || submittingBatch,
851
862
  children: [
852
863
  "Review & Import (",
@@ -1119,6 +1130,27 @@ function CsvManagerTab() {
1119
1130
  ] })
1120
1131
  ] })
1121
1132
  ] }),
1133
+ preflightReport?.lifecycleStats && preflightReport.lifecycleStats.isDraftAndPublish && /* @__PURE__ */ jsxs(Box, { marginBottom: 4, padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid rgba(120, 120, 150, 0.15)" }, children: [
1134
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: [
1135
+ "LIFECYCLE STATUS BREAKDOWN (",
1136
+ preflightReport.lifecycleStats.totalChecked,
1137
+ " Existing Records)"
1138
+ ] }),
1139
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
1140
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
1141
+ "✓ Published / Approved: ",
1142
+ preflightReport.lifecycleStats.published
1143
+ ] }),
1144
+ preflightReport.lifecycleStats.modified > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1145
+ "📝 Modified (Unreviewed Revisions): ",
1146
+ preflightReport.lifecycleStats.modified
1147
+ ] }),
1148
+ preflightReport.lifecycleStats.draft > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
1149
+ "📄 Draft (Never Published): ",
1150
+ preflightReport.lifecycleStats.draft
1151
+ ] })
1152
+ ] })
1153
+ ] }),
1122
1154
  /* @__PURE__ */ jsx(Box, { padding: 4, hasRadius: true, marginBottom: 4, background: executionWindow?.isBusinessHours ? "warning100" : "success100", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 1, children: [
1123
1155
  /* @__PURE__ */ jsx(Flex, { gap: 2, alignItems: "center", children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: executionWindow?.isBusinessHours ? "warning700" : "success700", children: executionWindow?.isBusinessHours ? "⏰ Peak Business Hours Restriction Active" : "✅ Server Off-Peak Window Active" }) }),
1124
1156
  /* @__PURE__ */ jsx(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.") })
@@ -1146,6 +1178,125 @@ function CsvManagerTab() {
1146
1178
  ] })
1147
1179
  }
1148
1180
  ),
1181
+ publishOnImport && (preflightReport?.lifecycleStats?.unreviewedCount ?? 0) > 0 && /* @__PURE__ */ jsxs(
1182
+ Box,
1183
+ {
1184
+ padding: 4,
1185
+ background: "neutral100",
1186
+ hasRadius: true,
1187
+ marginBottom: 4,
1188
+ style: { border: "1px solid #d9d8ff" },
1189
+ children: [
1190
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, display: "block", children: "PUBLISHING STRATEGY & GOVERNANCE CONSENT" }),
1191
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
1192
+ /* @__PURE__ */ jsx(
1193
+ Box,
1194
+ {
1195
+ padding: 3,
1196
+ hasRadius: true,
1197
+ style: {
1198
+ border: publishStrategy === "safe" ? "2px solid #4945ff" : "1px solid #eaeaef",
1199
+ backgroundColor: publishStrategy === "safe" ? "#f0f0ff" : "#ffffff",
1200
+ cursor: "pointer"
1201
+ },
1202
+ onClick: () => {
1203
+ setPublishStrategy("safe");
1204
+ setUnreviewedConsent(false);
1205
+ },
1206
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
1207
+ /* @__PURE__ */ jsx(
1208
+ "input",
1209
+ {
1210
+ type: "radio",
1211
+ id: "csv-strategy-safe",
1212
+ name: "csvPublishStrategy",
1213
+ checked: publishStrategy === "safe",
1214
+ onChange: () => {
1215
+ setPublishStrategy("safe");
1216
+ setUnreviewedConsent(false);
1217
+ },
1218
+ style: { marginTop: 3 }
1219
+ }
1220
+ ),
1221
+ /* @__PURE__ */ jsxs(Box, { children: [
1222
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", display: "block", children: "🛡️ Safe Publishing (Recommended)" }),
1223
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
1224
+ "Only publish records that are already approved & live (",
1225
+ preflightReport?.lifecycleStats?.published ?? 0,
1226
+ " record",
1227
+ (preflightReport?.lifecycleStats?.published ?? 0) === 1 ? "" : "s",
1228
+ "). The ",
1229
+ preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1230
+ " unreviewed or draft record(s) will receive the CSV update in Draft mode without publishing unreviewed draft revisions to visitors."
1231
+ ] })
1232
+ ] })
1233
+ ] })
1234
+ }
1235
+ ),
1236
+ /* @__PURE__ */ jsx(
1237
+ Box,
1238
+ {
1239
+ padding: 3,
1240
+ hasRadius: true,
1241
+ style: {
1242
+ border: publishStrategy === "all" ? "2px solid #d02b20" : "1px solid #eaeaef",
1243
+ backgroundColor: publishStrategy === "all" ? "#fff5f5" : "#ffffff",
1244
+ cursor: "pointer"
1245
+ },
1246
+ onClick: () => setPublishStrategy("all"),
1247
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
1248
+ /* @__PURE__ */ jsx(
1249
+ "input",
1250
+ {
1251
+ type: "radio",
1252
+ id: "csv-strategy-all",
1253
+ name: "csvPublishStrategy",
1254
+ checked: publishStrategy === "all",
1255
+ onChange: () => setPublishStrategy("all"),
1256
+ style: { marginTop: 3 }
1257
+ }
1258
+ ),
1259
+ /* @__PURE__ */ jsxs(Box, { children: [
1260
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "⚠️ Publish All Imported Records (Includes Unreviewed Drafts)" }),
1261
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
1262
+ "Publish all affected records live to production immediately, pushing all pending unreviewed draft edits in the ",
1263
+ preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1264
+ " draft/modified record(s) live."
1265
+ ] })
1266
+ ] })
1267
+ ] })
1268
+ }
1269
+ ),
1270
+ publishStrategy === "all" && /* @__PURE__ */ jsx(
1271
+ Box,
1272
+ {
1273
+ padding: 3,
1274
+ background: "danger100",
1275
+ hasRadius: true,
1276
+ style: { border: "1px solid #f5c0b8" },
1277
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
1278
+ /* @__PURE__ */ jsx(
1279
+ Checkbox,
1280
+ {
1281
+ checked: unreviewedConsent,
1282
+ onValueChange: (checked) => setUnreviewedConsent(Boolean(checked))
1283
+ }
1284
+ ),
1285
+ /* @__PURE__ */ jsxs(Box, { style: { cursor: "pointer" }, onClick: () => setUnreviewedConsent(!unreviewedConsent), children: [
1286
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "Mandatory Risk Consent Required" }),
1287
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", children: [
1288
+ "I understand and explicitly consent that ",
1289
+ preflightReport?.lifecycleStats?.unreviewedCount ?? 0,
1290
+ " record(s) with unreviewed draft revisions or saved draft states will be published live to production visitors."
1291
+ ] })
1292
+ ] })
1293
+ ] })
1294
+ }
1295
+ )
1296
+ ] })
1297
+ ]
1298
+ }
1299
+ ),
1149
1300
  /* @__PURE__ */ jsx(Box, { marginBottom: 2, children: /* @__PURE__ */ jsx(
1150
1301
  TextInput,
1151
1302
  {
@@ -1159,71 +1310,78 @@ function CsvManagerTab() {
1159
1310
  ]
1160
1311
  }
1161
1312
  ),
1162
- /* @__PURE__ */ jsx(
1163
- Box,
1164
- {
1165
- padding: 4,
1166
- background: "neutral100",
1167
- style: {
1168
- borderTop: "1px solid rgba(120, 120, 150, 0.2)",
1169
- flexShrink: 0,
1170
- position: "sticky",
1171
- bottom: 0,
1172
- zIndex: 20,
1173
- boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
1174
- },
1175
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
1176
- /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
1177
- /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
1178
- /* @__PURE__ */ jsx(
1179
- Button,
1180
- {
1181
- variant: executionWindow?.isBusinessHours ? "default" : "secondary",
1182
- size: "M",
1183
- onClick: () => submitBatchWithMode("maker_checker"),
1184
- loading: submittingBatch,
1185
- children: "Submit for Checker Approval"
1186
- }
1187
- ),
1188
- executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
1189
- /* @__PURE__ */ jsxs(
1313
+ (() => {
1314
+ const isConsentRequired = publishOnImport && (preflightReport?.lifecycleStats?.unreviewedCount ?? 0) > 0 && publishStrategy === "all" && !unreviewedConsent;
1315
+ return /* @__PURE__ */ jsx(
1316
+ Box,
1317
+ {
1318
+ padding: 4,
1319
+ background: "neutral100",
1320
+ style: {
1321
+ borderTop: "1px solid rgba(120, 120, 150, 0.2)",
1322
+ flexShrink: 0,
1323
+ position: "sticky",
1324
+ bottom: 0,
1325
+ zIndex: 20,
1326
+ boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
1327
+ },
1328
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
1329
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
1330
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
1331
+ /* @__PURE__ */ jsx(
1190
1332
  Button,
1191
1333
  {
1192
- variant: "secondary",
1334
+ variant: executionWindow?.isBusinessHours ? "default" : "secondary",
1193
1335
  size: "M",
1194
- onClick: () => submitBatchWithMode("queue_offpeak"),
1336
+ onClick: () => submitBatchWithMode("maker_checker"),
1195
1337
  loading: submittingBatch,
1196
- children: [
1197
- "Queue for Off-Peak (",
1198
- executionWindow?.offPeakStartTime || "18:00",
1199
- ")"
1200
- ]
1338
+ disabled: isConsentRequired,
1339
+ children: "Submit for Checker Approval"
1201
1340
  }
1202
1341
  ),
1203
- /* @__PURE__ */ jsx(
1342
+ executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
1343
+ /* @__PURE__ */ jsxs(
1344
+ Button,
1345
+ {
1346
+ variant: "secondary",
1347
+ size: "M",
1348
+ onClick: () => submitBatchWithMode("queue_offpeak"),
1349
+ loading: submittingBatch,
1350
+ disabled: isConsentRequired,
1351
+ children: [
1352
+ "Queue for Off-Peak (",
1353
+ executionWindow?.offPeakStartTime || "18:00",
1354
+ ")"
1355
+ ]
1356
+ }
1357
+ ),
1358
+ /* @__PURE__ */ jsx(
1359
+ Button,
1360
+ {
1361
+ variant: "danger-light",
1362
+ size: "S",
1363
+ onClick: () => submitBatchWithMode("immediate", true),
1364
+ loading: importing,
1365
+ disabled: isConsentRequired,
1366
+ children: "Force Run Now"
1367
+ }
1368
+ )
1369
+ ] }) : /* @__PURE__ */ jsx(
1204
1370
  Button,
1205
1371
  {
1206
- variant: "danger-light",
1207
- size: "S",
1208
- onClick: () => submitBatchWithMode("immediate", true),
1372
+ variant: "default",
1373
+ size: "M",
1374
+ onClick: () => submitBatchWithMode("immediate"),
1209
1375
  loading: importing,
1210
- children: "Force Run Now"
1376
+ disabled: isConsentRequired,
1377
+ children: "Confirm & Execute Now"
1211
1378
  }
1212
1379
  )
1213
- ] }) : /* @__PURE__ */ jsx(
1214
- Button,
1215
- {
1216
- variant: "default",
1217
- size: "M",
1218
- onClick: () => submitBatchWithMode("immediate"),
1219
- loading: importing,
1220
- children: "Confirm & Execute Now"
1221
- }
1222
- )
1380
+ ] })
1223
1381
  ] })
1224
- ] })
1225
- }
1226
- )
1382
+ }
1383
+ );
1384
+ })()
1227
1385
  ]
1228
1386
  }
1229
1387
  )
@@ -1861,7 +2019,8 @@ const BulkUpdatePreviewModal = ({
1861
2019
  previewData,
1862
2020
  attribute,
1863
2021
  targetValueDisplay,
1864
- publishOnApproval = true
2022
+ publishOnApproval = true,
2023
+ lifecycleStats
1865
2024
  }) => {
1866
2025
  const [filterTab, setFilterTab] = useState("all");
1867
2026
  const [searchQuery, setSearchQuery] = useState("");
@@ -1964,6 +2123,17 @@ const BulkUpdatePreviewModal = ({
1964
2123
  /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "semiBold", children: "Inspecting Target Records..." }),
1965
2124
  /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Computing proposed diffs and verifying against database schema." })
1966
2125
  ] }) }) : !previewData ? /* @__PURE__ */ jsx(Box, { padding: 6, style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "No preview data available." }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2126
+ publishOnApproval && lifecycleStats && lifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsx(Box, { marginBottom: 4, padding: 3, background: "warning100", hasRadius: true, style: { border: "1px solid #f5c0b8" }, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
2127
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "warning700", children: "⚠️ Safe Publishing Active:" }),
2128
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "warning700", children: [
2129
+ lifecycleStats.unreviewedCount,
2130
+ " target record(s) contain unreviewed draft changes (",
2131
+ lifecycleStats.draft,
2132
+ " Draft, ",
2133
+ lifecycleStats.modified,
2134
+ " Modified). By default, these will be updated as drafts without publishing unreviewed revisions to production visitors."
2135
+ ] })
2136
+ ] }) }),
1967
2137
  /* @__PURE__ */ jsxs(Flex, { gap: 3, marginBottom: 4, wrap: "wrap", children: [
1968
2138
  /* @__PURE__ */ jsxs(
1969
2139
  Box,
@@ -2216,7 +2386,7 @@ function getAllNestedValues(obj, path, availableAttributes) {
2216
2386
  const parsed = parseDynamicZonePath(path);
2217
2387
  const parts = parsed.cleanPath.split(".");
2218
2388
  const [head, ...tail] = parts;
2219
- const rootTarget = obj[head];
2389
+ const rootTarget = getPropertyValue(obj, head);
2220
2390
  if (Array.isArray(rootTarget)) {
2221
2391
  const results = [];
2222
2392
  rootTarget.forEach((item, idx) => {
@@ -2251,6 +2421,27 @@ function getAllNestedValues(obj, path, availableAttributes) {
2251
2421
  }
2252
2422
  return [];
2253
2423
  }
2424
+ function getPropertyValue(target, key) {
2425
+ if (!target || typeof target !== "object" || !key) return void 0;
2426
+ if (target[key] !== void 0 && target[key] !== null) return target[key];
2427
+ const camelKey = key.replace(/_([a-z0-9])/g, (_, letter) => letter.toUpperCase());
2428
+ if (target[camelKey] !== void 0 && target[camelKey] !== null) return target[camelKey];
2429
+ const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase();
2430
+ if (target[snakeKey] !== void 0 && target[snakeKey] !== null) return target[snakeKey];
2431
+ const lowerKey = key.toLowerCase();
2432
+ const lowerNoUnderscore = lowerKey.replace(/_/g, "");
2433
+ const matchedKey = Object.keys(target).find((k) => {
2434
+ const kLower = k.toLowerCase();
2435
+ return kLower === lowerKey || kLower.replace(/_/g, "") === lowerNoUnderscore;
2436
+ });
2437
+ if (matchedKey && target[matchedKey] !== void 0 && target[matchedKey] !== null) {
2438
+ return target[matchedKey];
2439
+ }
2440
+ if (key in target) return target[key];
2441
+ if (camelKey in target) return target[camelKey];
2442
+ if (snakeKey in target) return target[snakeKey];
2443
+ return void 0;
2444
+ }
2254
2445
  function getNestedValue(obj, path) {
2255
2446
  if (!obj || !path) return void 0;
2256
2447
  const parsed = parseDynamicZonePath(path);
@@ -2267,12 +2458,12 @@ function getNestedValue(obj, path) {
2267
2458
  const item = current[idx];
2268
2459
  if (parsed.targetIndex !== void 0 && idx !== parsed.targetIndex) continue;
2269
2460
  if (parsed.targetComponentUid && item?.__component !== parsed.targetComponentUid) continue;
2270
- const found = extract(item?.[head], tail);
2461
+ const found = extract(getPropertyValue(item, head), tail);
2271
2462
  if (found !== void 0 && found !== null) return found;
2272
2463
  }
2273
2464
  return void 0;
2274
2465
  }
2275
- return extract(current[head], tail);
2466
+ return extract(getPropertyValue(current, head), tail);
2276
2467
  }
2277
2468
  return extract(obj, parts);
2278
2469
  }
@@ -2297,24 +2488,59 @@ function getAvailableColumnsForCollection(col, selectedAttribute, searchAttribut
2297
2488
  return;
2298
2489
  }
2299
2490
  let formattedLabel = attr;
2300
- const bracketMatch = attr.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
2301
- if (bracketMatch) {
2302
- const root = bracketMatch[1].charAt(0).toUpperCase() + bracketMatch[1].slice(1);
2303
- const comp = formatComponentName(bracketMatch[2]);
2304
- const sub = bracketMatch[3].split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2305
- formattedLabel = `${root} [${comp}] > ${sub}`;
2306
- } else if (attr.includes(".")) {
2307
- formattedLabel = attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2491
+ if (attr === "updated_at" || attr === "updatedAt") {
2492
+ formattedLabel = "Updated At";
2493
+ } else if (attr === "created_at" || attr === "createdAt") {
2494
+ formattedLabel = "Created At";
2495
+ } else if (attr === "published_at" || attr === "publishedAt") {
2496
+ formattedLabel = "Published At";
2308
2497
  } else {
2309
- formattedLabel = attr.charAt(0).toUpperCase() + attr.slice(1);
2498
+ const bracketMatch = attr.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
2499
+ if (bracketMatch) {
2500
+ const root = bracketMatch[1].charAt(0).toUpperCase() + bracketMatch[1].slice(1);
2501
+ const comp = formatComponentName(bracketMatch[2]);
2502
+ const sub = bracketMatch[3].split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2503
+ formattedLabel = `${root} [${comp}] > ${sub}`;
2504
+ } else if (attr.includes(".")) {
2505
+ formattedLabel = attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2506
+ } else {
2507
+ formattedLabel = attr.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
2508
+ }
2310
2509
  }
2311
2510
  cols.push({
2312
2511
  key: `attr_${attr}`,
2313
2512
  label: formattedLabel,
2314
2513
  sortable: !col.isSingleType,
2315
- defaultVisible: ["department", "email", "location"].includes(attr)
2514
+ defaultVisible: ["department", "email", "location", "updated_at", "updatedAt"].includes(attr)
2316
2515
  });
2317
2516
  });
2517
+ const hasUpdatedAt = candidateAttrs.some((a) => a === "updatedAt" || a === "updated_at");
2518
+ const hasCreatedAt = candidateAttrs.some((a) => a === "createdAt" || a === "created_at");
2519
+ const hasPublishedAt = candidateAttrs.some((a) => a === "publishedAt" || a === "published_at");
2520
+ if (!hasUpdatedAt) {
2521
+ cols.push({
2522
+ key: "attr_updatedAt",
2523
+ label: "Updated At",
2524
+ sortable: !col.isSingleType,
2525
+ defaultVisible: false
2526
+ });
2527
+ }
2528
+ if (!hasCreatedAt) {
2529
+ cols.push({
2530
+ key: "attr_createdAt",
2531
+ label: "Created At",
2532
+ sortable: !col.isSingleType,
2533
+ defaultVisible: false
2534
+ });
2535
+ }
2536
+ if (!hasPublishedAt && col.draftAndPublish) {
2537
+ cols.push({
2538
+ key: "attr_publishedAt",
2539
+ label: "Published At",
2540
+ sortable: !col.isSingleType,
2541
+ defaultVisible: false
2542
+ });
2543
+ }
2318
2544
  if (searchAttribute && searchAttribute !== selectedAttribute) {
2319
2545
  let formattedSearchLabel = searchAttribute;
2320
2546
  const bracketMatch = searchAttribute.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
@@ -2360,11 +2586,136 @@ function getAvailableColumnsForCollection(col, selectedAttribute, searchAttribut
2360
2586
  });
2361
2587
  return cols;
2362
2588
  }
2589
+ function getActiveSortLabel(currentSort, col) {
2590
+ const f = currentSort.field;
2591
+ const o = currentSort.order;
2592
+ const dir = o === "asc" ? "▲" : "▼";
2593
+ if (f === "updatedAt" || f === "updated_at") {
2594
+ return `Updated: ${o === "desc" ? "Newest" : "Oldest"} ${dir}`;
2595
+ }
2596
+ if (f === "createdAt" || f === "created_at") {
2597
+ return `Created: ${o === "desc" ? "Newest" : "Oldest"} ${dir}`;
2598
+ }
2599
+ if (f === "publishedAt" || f === "published_at") {
2600
+ return `Published: ${o === "desc" ? "Newest" : "Oldest"} ${dir}`;
2601
+ }
2602
+ if (f === "id") {
2603
+ return `ID: ${o === "desc" ? "Highest" : "Lowest"} ${dir}`;
2604
+ }
2605
+ let attrLabel = f;
2606
+ if (f.includes(".")) {
2607
+ attrLabel = f.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2608
+ } else {
2609
+ attrLabel = f.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
2610
+ }
2611
+ return `${attrLabel} (${o === "asc" ? "A → Z" : "Z → A"} ${dir})`;
2612
+ }
2613
+ function getSortOptionsForCollection(col, selectedAttribute, searchAttribute) {
2614
+ const options = [];
2615
+ const hasCustomUpdatedAt = col.attributes?.includes("updated_at");
2616
+ const updatedField = hasCustomUpdatedAt ? "updated_at" : "updatedAt";
2617
+ options.push({
2618
+ field: updatedField,
2619
+ order: "desc",
2620
+ label: "Updated At (Newest First ↓)",
2621
+ columnLabel: "Updated At",
2622
+ group: "timestamps"
2623
+ });
2624
+ options.push({
2625
+ field: updatedField,
2626
+ order: "asc",
2627
+ label: "Updated At (Oldest First ↑)",
2628
+ columnLabel: "Updated At",
2629
+ group: "timestamps"
2630
+ });
2631
+ const hasCustomCreatedAt = col.attributes?.includes("created_at");
2632
+ const createdField = hasCustomCreatedAt ? "created_at" : "createdAt";
2633
+ options.push({
2634
+ field: createdField,
2635
+ order: "desc",
2636
+ label: "Created At (Newest First ↓)",
2637
+ columnLabel: "Created At",
2638
+ group: "timestamps"
2639
+ });
2640
+ options.push({
2641
+ field: createdField,
2642
+ order: "asc",
2643
+ label: "Created At (Oldest First ↑)",
2644
+ columnLabel: "Created At",
2645
+ group: "timestamps"
2646
+ });
2647
+ if (col.draftAndPublish) {
2648
+ const hasCustomPublishedAt = col.attributes?.includes("published_at");
2649
+ const publishedField = hasCustomPublishedAt ? "published_at" : "publishedAt";
2650
+ options.push({
2651
+ field: publishedField,
2652
+ order: "desc",
2653
+ label: "Published At (Newest First ↓)",
2654
+ columnLabel: "Published At",
2655
+ group: "timestamps"
2656
+ });
2657
+ options.push({
2658
+ field: publishedField,
2659
+ order: "asc",
2660
+ label: "Published At (Oldest First ↑)",
2661
+ columnLabel: "Published At",
2662
+ group: "timestamps"
2663
+ });
2664
+ }
2665
+ options.push({
2666
+ field: "id",
2667
+ order: "desc",
2668
+ label: "Entry ID (Highest First ↓)",
2669
+ columnLabel: "ID",
2670
+ group: "system"
2671
+ });
2672
+ options.push({
2673
+ field: "id",
2674
+ order: "asc",
2675
+ label: "Entry ID (Lowest First ↑)",
2676
+ columnLabel: "ID",
2677
+ group: "system"
2678
+ });
2679
+ const systemKeywords = [
2680
+ "createdat",
2681
+ "created_at",
2682
+ "updatedat",
2683
+ "updated_at",
2684
+ "publishedat",
2685
+ "published_at",
2686
+ "id",
2687
+ "documentid"
2688
+ ];
2689
+ const attrs = (col.attributes || []).filter((a) => !systemKeywords.includes(a.toLowerCase()));
2690
+ attrs.forEach((attr) => {
2691
+ let formattedLabel = attr;
2692
+ if (attr.includes(".")) {
2693
+ formattedLabel = attr.split(".").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" > ");
2694
+ } else {
2695
+ formattedLabel = attr.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
2696
+ }
2697
+ options.push({
2698
+ field: attr,
2699
+ order: "asc",
2700
+ label: `${formattedLabel} (A → Z / Lowest ↑)`,
2701
+ columnLabel: formattedLabel,
2702
+ group: "attributes"
2703
+ });
2704
+ options.push({
2705
+ field: attr,
2706
+ order: "desc",
2707
+ label: `${formattedLabel} (Z → A / Highest ↓)`,
2708
+ columnLabel: formattedLabel,
2709
+ group: "attributes"
2710
+ });
2711
+ });
2712
+ return options;
2713
+ }
2363
2714
  function renderHeaderCell(colDef, col, currentSort, selectedAttribute, searchAttribute, onSort) {
2364
2715
  if (colDef.key === "select") {
2365
2716
  return /* @__PURE__ */ jsx(Th, { style: { width: 48 }, children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Select" }) }, "select");
2366
2717
  }
2367
- 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_", "");
2718
+ 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");
2368
2719
  const sortArrow = isCurrentSorted ? currentSort.order === "asc" ? "▲" : "▼" : "⇅";
2369
2720
  const handleClick = () => {
2370
2721
  if (!colDef.sortable) return;
@@ -2396,6 +2747,15 @@ function formatDisplayValue(val) {
2396
2747
  if (val === void 0 || val === null || val === "") {
2397
2748
  return "(empty / not set)";
2398
2749
  }
2750
+ if (val instanceof Date) {
2751
+ return isNaN(val.getTime()) ? "(invalid date)" : val.toLocaleString();
2752
+ }
2753
+ if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(val)) {
2754
+ const d = new Date(val);
2755
+ if (!isNaN(d.getTime())) {
2756
+ return d.toLocaleString();
2757
+ }
2758
+ }
2399
2759
  if (Array.isArray(val)) {
2400
2760
  if (val.length === 0) return "(empty / unlinked)";
2401
2761
  return val.map(
@@ -2696,9 +3056,11 @@ const BulkUpdater = () => {
2696
3056
  const [pendingBatchesLoading, setPendingBatchesLoading] = useState(false);
2697
3057
  const [approvingBatchId, setApprovingBatchId] = useState(null);
2698
3058
  const [rejectingBatchId, setRejectingBatchId] = useState(null);
2699
- const [executingNowBatchId, setExecutingNowBatchId] = useState(null);
2700
3059
  const [makerNotesInput, setMakerNotesInput] = useState("");
2701
3060
  const [publishOnApproval, setPublishOnApproval] = useState(true);
3061
+ const [publishStrategy, setPublishStrategy] = useState("safe");
3062
+ const [unreviewedConsent, setUnreviewedConsent] = useState(false);
3063
+ const [publishConsent, setPublishConsent] = useState(false);
2702
3064
  const [showProcessingDashboard, setShowProcessingDashboard] = useState(false);
2703
3065
  const [dashboardBatchId, setDashboardBatchId] = useState("");
2704
3066
  const [dashboardJob, setDashboardJob] = useState(null);
@@ -2775,10 +3137,32 @@ const BulkUpdater = () => {
2775
3137
  } catch {
2776
3138
  }
2777
3139
  };
3140
+ const [searchRelationOptions, setSearchRelationOptions] = useState([]);
3141
+ const [searchRelationOptionsLoading, setSearchRelationOptionsLoading] = useState(false);
3142
+ const fetchSearchRelationOptions = async (targetUid) => {
3143
+ try {
3144
+ setSearchRelationOptionsLoading(true);
3145
+ const res = await get(`/bulk-updater/relation-options?targetUid=${encodeURIComponent(targetUid)}`);
3146
+ if (res.data && res.data.success) {
3147
+ setSearchRelationOptions(res.data.options || []);
3148
+ }
3149
+ } catch {
3150
+ setSearchRelationOptions([]);
3151
+ } finally {
3152
+ setSearchRelationOptionsLoading(false);
3153
+ }
3154
+ };
2778
3155
  const [searchAttribute, setSearchAttribute] = useState("");
2779
3156
  const [searchAttributeInput, setSearchAttributeInput] = useState("");
2780
3157
  const [attributesMeta, setAttributesMeta] = useState({});
2781
3158
  const effectiveSearchAttribute = searchAttribute || selectedAttribute;
3159
+ useEffect(() => {
3160
+ if (effectiveSearchAttribute && relationsMeta[effectiveSearchAttribute]) {
3161
+ fetchSearchRelationOptions(relationsMeta[effectiveSearchAttribute].targetUid);
3162
+ } else {
3163
+ setSearchRelationOptions([]);
3164
+ }
3165
+ }, [effectiveSearchAttribute, relationsMeta]);
2782
3166
  const [filterOperator, setFilterOperator] = useState("none");
2783
3167
  const [filterQuery, setFilterQuery] = useState("");
2784
3168
  const [entrySearch, setEntrySearch] = useState("");
@@ -2797,6 +3181,8 @@ const BulkUpdater = () => {
2797
3181
  const [collectionColumnVisibility, setCollectionColumnVisibility] = useState({});
2798
3182
  const [openColumnPickerUid, setOpenColumnPickerUid] = useState(null);
2799
3183
  const [columnSearchTerm, setColumnSearchTerm] = useState("");
3184
+ const [openSortPickerUid, setOpenSortPickerUid] = useState(null);
3185
+ const [sortSearchTerm, setSortSearchTerm] = useState("");
2800
3186
  const toggleColumn = (colUid, colKey, availableCols) => {
2801
3187
  setCollectionColumnVisibility((prev) => {
2802
3188
  const current = prev[colUid] || availableCols.filter((c) => c.defaultVisible).map((c) => c.key);
@@ -3329,21 +3715,45 @@ const BulkUpdater = () => {
3329
3715
  }
3330
3716
  const checkMatch = (val) => {
3331
3717
  const displayStr = formatDisplayValue(val);
3332
- 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)";
3333
- switch (filterOperator) {
3334
- case "empty":
3335
- return isValEmpty;
3336
- case "not_empty":
3337
- return !isValEmpty;
3338
- case "equals":
3339
- return !isValEmpty && displayStr.trim().toLowerCase() === filterQuery.trim().toLowerCase();
3340
- case "contains":
3341
- return !isValEmpty && displayStr.toLowerCase().includes(filterQuery.trim().toLowerCase());
3342
- case "starts_with":
3343
- return !isValEmpty && displayStr.trim().toLowerCase().startsWith(filterQuery.trim().toLowerCase());
3344
- default:
3345
- return true;
3718
+ 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)";
3719
+ if (filterOperator === "empty") return isValEmpty;
3720
+ if (filterOperator === "not_empty") return !isValEmpty;
3721
+ if (isValEmpty) return false;
3722
+ const q = filterQuery.trim().toLowerCase();
3723
+ if (!q) return true;
3724
+ const testString = (str) => {
3725
+ if (str === void 0 || str === null) return false;
3726
+ const s = String(str).trim().toLowerCase();
3727
+ switch (filterOperator) {
3728
+ case "equals":
3729
+ return s === q;
3730
+ case "contains":
3731
+ return s.includes(q);
3732
+ case "starts_with":
3733
+ return s.startsWith(q);
3734
+ default:
3735
+ return true;
3736
+ }
3737
+ };
3738
+ const testEntityObject = (obj) => {
3739
+ if (!obj || typeof obj !== "object") return false;
3740
+ if (testString(obj.name)) return true;
3741
+ if (testString(obj.title)) return true;
3742
+ if (testString(obj.email)) return true;
3743
+ if (testString(obj.username)) return true;
3744
+ if (testString(obj.documentId)) return true;
3745
+ if (testString(String(obj.id))) return true;
3746
+ if (testString(obj.slug)) return true;
3747
+ if (testString(formatDisplayValue(obj))) return true;
3748
+ return false;
3749
+ };
3750
+ if (Array.isArray(val)) {
3751
+ return val.some((item) => typeof item === "object" ? testEntityObject(item) : testString(item));
3346
3752
  }
3753
+ if (typeof val === "object" && val !== null) {
3754
+ if (testEntityObject(val)) return true;
3755
+ }
3756
+ return testString(displayStr);
3347
3757
  };
3348
3758
  if (filterOperator === "empty") {
3349
3759
  return matches.every((m) => checkMatch(m.value));
@@ -3455,6 +3865,40 @@ const BulkUpdater = () => {
3455
3865
  });
3456
3866
  return breakdown;
3457
3867
  };
3868
+ const selectedLifecycleStats = useMemo(() => {
3869
+ let published = 0;
3870
+ let modified = 0;
3871
+ let draft = 0;
3872
+ let pendingReview = 0;
3873
+ Object.keys(selectedEntries).forEach((key) => {
3874
+ if (!selectedEntries[key]) return;
3875
+ const parts = key.split(KEY_SEP);
3876
+ if (parts.length !== 2) return;
3877
+ const [uid, docId] = parts;
3878
+ const col = collections.find((c) => c.uid === uid) || null;
3879
+ 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);
3880
+ if (col && col.draftAndPublish === false) {
3881
+ published++;
3882
+ } else if (entry) {
3883
+ const { status } = getEntryLifecycleStatus(entry, col, pendingBatches);
3884
+ if (status === "published") published++;
3885
+ else if (status === "modified") modified++;
3886
+ else if (status === "draft") draft++;
3887
+ else if (status === "pending_review") pendingReview++;
3888
+ } else {
3889
+ draft++;
3890
+ }
3891
+ });
3892
+ const unreviewedCount = draft + modified + pendingReview;
3893
+ return {
3894
+ published,
3895
+ modified,
3896
+ draft,
3897
+ pendingReview,
3898
+ unreviewedCount,
3899
+ total: published + modified + draft + pendingReview
3900
+ };
3901
+ }, [selectedEntries, collections, collectionPages, pendingBatches]);
3458
3902
  const handleOpenConfirmModal = () => {
3459
3903
  if (!selectedAttribute) {
3460
3904
  toggleNotification({
@@ -3471,6 +3915,8 @@ const BulkUpdater = () => {
3471
3915
  });
3472
3916
  return;
3473
3917
  }
3918
+ setPublishStrategy("safe");
3919
+ setUnreviewedConsent(false);
3474
3920
  setShowConfirmModal(true);
3475
3921
  };
3476
3922
  const handleOpenPreviewModal = async () => {
@@ -3565,6 +4011,9 @@ const BulkUpdater = () => {
3565
4011
  failedCount: 0,
3566
4012
  isLive: false,
3567
4013
  publishOnApproval,
4014
+ publishStrategy,
4015
+ unreviewedConsent,
4016
+ unreviewedCount: selectedLifecycleStats.unreviewedCount,
3568
4017
  results: []
3569
4018
  });
3570
4019
  const payload = {
@@ -3576,7 +4025,10 @@ const BulkUpdater = () => {
3576
4025
  executionMode,
3577
4026
  makerNotes: makerNotesInput,
3578
4027
  forceImmediate,
3579
- publishOnApproval
4028
+ publishOnApproval,
4029
+ publishStrategy,
4030
+ unreviewedConsent,
4031
+ unreviewedCount: selectedLifecycleStats.unreviewedCount
3580
4032
  };
3581
4033
  const res = await post("/bulk-updater/batch-update", payload);
3582
4034
  setIsSubmittingBatch(false);
@@ -3744,6 +4196,7 @@ const BulkUpdater = () => {
3744
4196
  });
3745
4197
  return;
3746
4198
  }
4199
+ setPublishConsent(false);
3747
4200
  setShowPublishConfirmModal(true);
3748
4201
  };
3749
4202
  const executeBatchPublish = async (executionMode, forceImmediate = false) => {
@@ -3778,13 +4231,17 @@ const BulkUpdater = () => {
3778
4231
  failedCount: 0,
3779
4232
  isLive: true,
3780
4233
  publishOnApproval: true,
4234
+ unreviewedConsent: publishConsent,
4235
+ unreviewedCount: selectedLifecycleStats.unreviewedCount,
3781
4236
  results: []
3782
4237
  });
3783
4238
  const payload = {
3784
4239
  selectedEntries: targets,
3785
4240
  executionMode,
3786
4241
  makerNotes: publishMakerNotesInput,
3787
- forceImmediate
4242
+ forceImmediate,
4243
+ unreviewedConsent: publishConsent,
4244
+ unreviewedCount: selectedLifecycleStats.unreviewedCount
3788
4245
  };
3789
4246
  const res = await post("/bulk-updater/batch-publish", payload);
3790
4247
  setIsSubmittingBatch(false);
@@ -4365,7 +4822,27 @@ const BulkUpdater = () => {
4365
4822
  ]
4366
4823
  }
4367
4824
  ) }),
4368
- (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 180 }, children: /* @__PURE__ */ jsx(
4825
+ (filterOperator === "equals" || filterOperator === "contains" || filterOperator === "starts_with") && /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 200 }, children: effectiveSearchAttribute && relationsMeta[effectiveSearchAttribute] ? /* @__PURE__ */ jsxs(Field.Root, { children: [
4826
+ /* @__PURE__ */ jsx(Field.Label, { children: `Target ${relationsMeta[effectiveSearchAttribute].targetDisplayName} Relation` }),
4827
+ /* @__PURE__ */ jsx(
4828
+ Combobox,
4829
+ {
4830
+ autocomplete: { type: "list", filter: "contains" },
4831
+ placeholder: searchRelationOptionsLoading ? "Loading entities..." : `Search & pick ${relationsMeta[effectiveSearchAttribute].targetDisplayName} or type text...`,
4832
+ value: filterQuery,
4833
+ onClear: () => setFilterQuery(""),
4834
+ onChange: (val) => {
4835
+ if (typeof val === "string") {
4836
+ setFilterQuery(val);
4837
+ }
4838
+ },
4839
+ onInputChange: (e) => {
4840
+ setFilterQuery(e.target.value);
4841
+ },
4842
+ children: searchRelationOptions.map((opt) => /* @__PURE__ */ jsx(ComboboxOption, { value: opt.label, textValue: opt.label, children: opt.label }, opt.documentId))
4843
+ }
4844
+ )
4845
+ ] }) : /* @__PURE__ */ jsx(
4369
4846
  TextInput,
4370
4847
  {
4371
4848
  label: effectiveSearchAttribute ? `Value for "${effectiveSearchAttribute}"` : "Value to match",
@@ -4515,41 +4992,127 @@ const BulkUpdater = () => {
4515
4992
  ) : null
4516
4993
  ] }),
4517
4994
  /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", wrap: "wrap", children: [
4518
- !col.isSingleType && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
4519
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: "Sort:" }),
4520
- /* @__PURE__ */ jsx(Box, { style: { width: 190 }, children: /* @__PURE__ */ jsxs(
4521
- SingleSelect,
4995
+ !col.isSingleType && /* @__PURE__ */ jsxs(Box, { style: { position: "relative", display: "inline-block" }, children: [
4996
+ /* @__PURE__ */ jsx(
4997
+ Button,
4522
4998
  {
4999
+ variant: openSortPickerUid === col.uid ? "secondary" : "tertiary",
4523
5000
  size: "S",
4524
- value: `${currentSort.field}:${currentSort.order}`,
4525
- onChange: (val) => {
4526
- const [f, o] = String(val).split(":");
4527
- handleCollectionSortChange(col.uid, f, o);
5001
+ onClick: () => {
5002
+ setOpenSortPickerUid(openSortPickerUid === col.uid ? null : col.uid);
5003
+ setSortSearchTerm("");
5004
+ if (openColumnPickerUid === col.uid) setOpenColumnPickerUid(null);
5005
+ },
5006
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 1, alignItems: "center", children: [
5007
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", fontWeight: "bold", children: "Sort:" }),
5008
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "semiBold", children: getActiveSortLabel(currentSort) })
5009
+ ] })
5010
+ }
5011
+ ),
5012
+ openSortPickerUid === col.uid && /* @__PURE__ */ jsxs(
5013
+ Box,
5014
+ {
5015
+ style: {
5016
+ position: "absolute",
5017
+ top: "calc(100% + 6px)",
5018
+ left: 0,
5019
+ zIndex: 220,
5020
+ width: 330,
5021
+ maxHeight: 440,
5022
+ background: "#ffffff",
5023
+ borderRadius: 8,
5024
+ boxShadow: "0 10px 25px rgba(33, 33, 52, 0.18)",
5025
+ border: "1px solid #dcdce4",
5026
+ padding: 12,
5027
+ display: "flex",
5028
+ flexDirection: "column",
5029
+ gap: 8
4528
5030
  },
4529
5031
  children: [
4530
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "createdAt:desc", children: "Created: Newest" }),
4531
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "createdAt:asc", children: "Created: Oldest" }),
4532
- hasAttr && /* @__PURE__ */ jsxs(Fragment, { children: [
4533
- /* @__PURE__ */ jsxs(SingleSelectOption, { value: `${selectedAttribute}:asc`, children: [
4534
- selectedAttribute,
4535
- " (A → Z)"
4536
- ] }),
4537
- /* @__PURE__ */ jsxs(SingleSelectOption, { value: `${selectedAttribute}:desc`, children: [
4538
- selectedAttribute,
4539
- " (Z → A)"
4540
- ] })
4541
- ] }),
4542
- col.attributes.includes("name") && /* @__PURE__ */ jsxs(Fragment, { children: [
4543
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "name:asc", children: "Name (A → Z)" }),
4544
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "name:desc", children: "Name (Z → A)" })
5032
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
5033
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "Sort by Column" }),
5034
+ /* @__PURE__ */ jsx(
5035
+ Button,
5036
+ {
5037
+ variant: "ghost",
5038
+ size: "S",
5039
+ onClick: () => setOpenSortPickerUid(null),
5040
+ style: { padding: "2px 6px", height: "auto", minWidth: "unset" },
5041
+ children: "✕"
5042
+ }
5043
+ )
4545
5044
  ] }),
4546
- col.attributes.includes("title") && /* @__PURE__ */ jsxs(Fragment, { children: [
4547
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "title:asc", children: "Title (A → Z)" }),
4548
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "title:desc", children: "Title (Z → A)" })
4549
- ] })
5045
+ /* @__PURE__ */ jsx(
5046
+ TextInput,
5047
+ {
5048
+ size: "S",
5049
+ placeholder: "Search columns to sort...",
5050
+ value: sortSearchTerm,
5051
+ onChange: (e) => setSortSearchTerm(e.target.value)
5052
+ }
5053
+ ),
5054
+ /* @__PURE__ */ jsx(
5055
+ Box,
5056
+ {
5057
+ style: {
5058
+ maxHeight: 320,
5059
+ overflowY: "auto",
5060
+ display: "flex",
5061
+ flexDirection: "column",
5062
+ gap: 4
5063
+ },
5064
+ children: getSortOptionsForCollection(col).filter((opt) => {
5065
+ if (!sortSearchTerm.trim()) return true;
5066
+ const term = sortSearchTerm.toLowerCase().trim();
5067
+ return opt.label.toLowerCase().includes(term) || opt.field.toLowerCase().includes(term);
5068
+ }).map((opt) => {
5069
+ 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;
5070
+ return /* @__PURE__ */ jsxs(
5071
+ Box,
5072
+ {
5073
+ onClick: () => {
5074
+ handleCollectionSortChange(col.uid, opt.field, opt.order);
5075
+ setOpenSortPickerUid(null);
5076
+ setSortSearchTerm("");
5077
+ },
5078
+ style: {
5079
+ padding: "6px 10px",
5080
+ borderRadius: 6,
5081
+ cursor: "pointer",
5082
+ background: isSelected ? "rgba(73, 69, 255, 0.12)" : "transparent",
5083
+ border: isSelected ? "1px solid #4945ff" : "1px solid transparent",
5084
+ display: "flex",
5085
+ alignItems: "center",
5086
+ justifyContent: "space-between",
5087
+ transition: "background 0.15s ease"
5088
+ },
5089
+ onMouseEnter: (e) => {
5090
+ if (!isSelected) e.currentTarget.style.background = "#f6f6f9";
5091
+ },
5092
+ onMouseLeave: (e) => {
5093
+ if (!isSelected) e.currentTarget.style.background = "transparent";
5094
+ },
5095
+ children: [
5096
+ /* @__PURE__ */ jsx(
5097
+ Typography,
5098
+ {
5099
+ variant: "omega",
5100
+ fontWeight: isSelected ? "bold" : "normal",
5101
+ textColor: isSelected ? "primary700" : "neutral800",
5102
+ children: opt.label
5103
+ }
5104
+ ),
5105
+ isSelected && /* @__PURE__ */ jsx(Badge, { size: "S", backgroundColor: "primary100", textColor: "primary700", children: "Active" })
5106
+ ]
5107
+ },
5108
+ `${opt.field}:${opt.order}`
5109
+ );
5110
+ })
5111
+ }
5112
+ )
4550
5113
  ]
4551
5114
  }
4552
- ) })
5115
+ )
4553
5116
  ] }),
4554
5117
  /* @__PURE__ */ jsxs(Box, { style: { position: "relative", display: "inline-block" }, children: [
4555
5118
  /* @__PURE__ */ jsxs(
@@ -4561,6 +5124,7 @@ const BulkUpdater = () => {
4561
5124
  onClick: () => {
4562
5125
  setOpenColumnPickerUid(openColumnPickerUid === col.uid ? null : col.uid);
4563
5126
  setColumnSearchTerm("");
5127
+ if (openSortPickerUid === col.uid) setOpenSortPickerUid(null);
4564
5128
  },
4565
5129
  children: [
4566
5130
  "Columns (",
@@ -5186,27 +5750,42 @@ const BulkUpdater = () => {
5186
5750
  )
5187
5751
  ] })
5188
5752
  ] }),
5189
- /* @__PURE__ */ jsx(Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
5190
- /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
5191
- /* @__PURE__ */ jsx(
5192
- Checkbox,
5193
- {
5194
- checked: publishOnApproval,
5195
- onCheckedChange: (checked) => setPublishOnApproval(Boolean(checked)),
5196
- children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: "Auto-Publish after Update" })
5197
- }
5198
- ),
5199
- /* @__PURE__ */ jsx(
5200
- Badge,
5201
- {
5202
- backgroundColor: publishOnApproval ? "success100" : "warning100",
5203
- textColor: publishOnApproval ? "success700" : "warning700",
5204
- children: publishOnApproval ? "✓ LIVE PUBLISH" : "DRAFT ONLY"
5205
- }
5206
- )
5753
+ /* @__PURE__ */ jsxs(Box, { marginTop: 4, padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
5754
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: [
5755
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
5756
+ /* @__PURE__ */ jsx(
5757
+ Checkbox,
5758
+ {
5759
+ checked: publishOnApproval,
5760
+ onCheckedChange: (checked) => setPublishOnApproval(Boolean(checked)),
5761
+ children: /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: "Auto-Publish after Update" })
5762
+ }
5763
+ ),
5764
+ /* @__PURE__ */ jsx(
5765
+ Badge,
5766
+ {
5767
+ backgroundColor: publishOnApproval ? "success100" : "warning100",
5768
+ textColor: publishOnApproval ? "success700" : "warning700",
5769
+ children: publishOnApproval ? "✓ LIVE PUBLISH" : "DRAFT ONLY"
5770
+ }
5771
+ )
5772
+ ] }),
5773
+ /* @__PURE__ */ jsx(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." })
5207
5774
  ] }),
5208
- /* @__PURE__ */ jsx(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." })
5209
- ] }) }),
5775
+ publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsx(Box, { marginTop: 3, padding: 3, background: "warning100", hasRadius: true, style: { border: "1px solid #f5c0b8" }, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
5776
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", textColor: "warning700", children: "⚠️ Unreviewed Content Alert:" }),
5777
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "warning700", children: [
5778
+ selectedLifecycleStats.unreviewedCount,
5779
+ " of ",
5780
+ totalSelectedCount,
5781
+ " selected record(s) contain unreviewed draft changes (",
5782
+ selectedLifecycleStats.draft,
5783
+ " Draft, ",
5784
+ selectedLifecycleStats.modified,
5785
+ " 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."
5786
+ ] })
5787
+ ] }) })
5788
+ ] }),
5210
5789
  selectedCol && selectedAttribute && /* @__PURE__ */ jsx(Box, { marginTop: 4, padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "space-between", alignItems: "center", wrap: "wrap", gap: 3, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
5211
5790
  /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: "Target Selection:" }),
5212
5791
  /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
@@ -5392,6 +5971,12 @@ const BulkUpdater = () => {
5392
5971
  children: batch.summary.publishOnApproval === false ? "DRAFT ONLY" : "AUTO-PUBLISH"
5393
5972
  }
5394
5973
  ),
5974
+ batch.summary.publishStrategy === "safe" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: "SAFE PUBLISH" }),
5975
+ batch.summary.publishStrategy === "all" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "warning100", textColor: "warning700", children: "PUBLISH ALL (CONSENTED)" }),
5976
+ Boolean(batch.summary.unreviewedCount) && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral150", textColor: "neutral700", children: [
5977
+ "Unreviewed: ",
5978
+ batch.summary.unreviewedCount
5979
+ ] }),
5395
5980
  /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
5396
5981
  "• ",
5397
5982
  batch.summary.totalTargets,
@@ -5539,7 +6124,9 @@ const BulkUpdater = () => {
5539
6124
  rec.skippedCount > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", style: { marginLeft: 6 }, children: [
5540
6125
  rec.skippedCount,
5541
6126
  " skipped"
5542
- ] })
6127
+ ] }),
6128
+ rec.publishStrategy === "safe" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", style: { marginLeft: 6 }, children: "Safe Publish" }),
6129
+ rec.publishStrategy === "all" && /* @__PURE__ */ jsx(Badge, { backgroundColor: "warning100", textColor: "warning700", style: { marginLeft: 6 }, children: "Publish All (Consented)" })
5543
6130
  ] }),
5544
6131
  /* @__PURE__ */ jsx(Td, { style: { minWidth: 120 }, children: rec.isRolledBack ? /* @__PURE__ */ jsxs(Box, { children: [
5545
6132
  /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral200", textColor: "neutral700", children: "ROLLED BACK" }),
@@ -5758,6 +6345,31 @@ const BulkUpdater = () => {
5758
6345
  ] })
5759
6346
  ] })
5760
6347
  ] }),
6348
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 4, padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid rgba(120, 120, 150, 0.15)" }, children: [
6349
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: [
6350
+ "LIFECYCLE STATUS BREAKDOWN (",
6351
+ totalSelectedCount,
6352
+ " Selected)"
6353
+ ] }),
6354
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
6355
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
6356
+ "✓ Published / Approved: ",
6357
+ selectedLifecycleStats.published
6358
+ ] }),
6359
+ selectedLifecycleStats.modified > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
6360
+ "📝 Modified (Unreviewed Revisions): ",
6361
+ selectedLifecycleStats.modified
6362
+ ] }),
6363
+ selectedLifecycleStats.draft > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
6364
+ "📄 Draft (Never Published): ",
6365
+ selectedLifecycleStats.draft
6366
+ ] }),
6367
+ selectedLifecycleStats.pendingReview > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "warning100", textColor: "warning700", children: [
6368
+ "⏳ Pending Review: ",
6369
+ selectedLifecycleStats.pendingReview
6370
+ ] })
6371
+ ] })
6372
+ ] }),
5761
6373
  /* @__PURE__ */ jsxs(Box, { marginBottom: 4, children: [
5762
6374
  /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: "COLLECTION BREAKDOWN" }),
5763
6375
  /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 1, children: Object.entries(getSelectedBreakdown()).map(([name, count]) => /* @__PURE__ */ jsxs(
@@ -5808,6 +6420,128 @@ const BulkUpdater = () => {
5808
6420
  ] })
5809
6421
  }
5810
6422
  ),
6423
+ publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsxs(
6424
+ Box,
6425
+ {
6426
+ padding: 4,
6427
+ background: "neutral100",
6428
+ hasRadius: true,
6429
+ marginBottom: 4,
6430
+ style: { border: "1px solid #d9d8ff" },
6431
+ children: [
6432
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, display: "block", children: "PUBLISHING STRATEGY & GOVERNANCE CONSENT" }),
6433
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
6434
+ /* @__PURE__ */ jsx(
6435
+ Box,
6436
+ {
6437
+ padding: 3,
6438
+ hasRadius: true,
6439
+ style: {
6440
+ border: publishStrategy === "safe" ? "2px solid #4945ff" : "1px solid #eaeaef",
6441
+ backgroundColor: publishStrategy === "safe" ? "#f0f0ff" : "#ffffff",
6442
+ cursor: "pointer"
6443
+ },
6444
+ onClick: () => {
6445
+ setPublishStrategy("safe");
6446
+ setUnreviewedConsent(false);
6447
+ },
6448
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
6449
+ /* @__PURE__ */ jsx(
6450
+ "input",
6451
+ {
6452
+ type: "radio",
6453
+ id: "strategy-safe",
6454
+ name: "publishStrategy",
6455
+ checked: publishStrategy === "safe",
6456
+ onChange: () => {
6457
+ setPublishStrategy("safe");
6458
+ setUnreviewedConsent(false);
6459
+ },
6460
+ style: { marginTop: 3 }
6461
+ }
6462
+ ),
6463
+ /* @__PURE__ */ jsxs(Box, { children: [
6464
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", display: "block", children: "🛡️ Safe Publishing (Recommended)" }),
6465
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
6466
+ "Only publish records that are already approved & live (",
6467
+ selectedLifecycleStats.published,
6468
+ " record",
6469
+ selectedLifecycleStats.published === 1 ? "" : "s",
6470
+ "). The ",
6471
+ selectedLifecycleStats.unreviewedCount,
6472
+ " unreviewed or draft record(s) will receive the bulk update in Draft mode without publishing unreviewed draft revisions to visitors."
6473
+ ] })
6474
+ ] })
6475
+ ] })
6476
+ }
6477
+ ),
6478
+ /* @__PURE__ */ jsx(
6479
+ Box,
6480
+ {
6481
+ padding: 3,
6482
+ hasRadius: true,
6483
+ style: {
6484
+ border: publishStrategy === "all" ? "2px solid #d02b20" : "1px solid #eaeaef",
6485
+ backgroundColor: publishStrategy === "all" ? "#fff5f5" : "#ffffff",
6486
+ cursor: "pointer"
6487
+ },
6488
+ onClick: () => setPublishStrategy("all"),
6489
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "flex-start", children: [
6490
+ /* @__PURE__ */ jsx(
6491
+ "input",
6492
+ {
6493
+ type: "radio",
6494
+ id: "strategy-all",
6495
+ name: "publishStrategy",
6496
+ checked: publishStrategy === "all",
6497
+ onChange: () => setPublishStrategy("all"),
6498
+ style: { marginTop: 3 }
6499
+ }
6500
+ ),
6501
+ /* @__PURE__ */ jsxs(Box, { children: [
6502
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "⚠️ Publish All Selected Records (Includes Unreviewed Drafts)" }),
6503
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
6504
+ "Publish all ",
6505
+ totalSelectedCount,
6506
+ " records live to production immediately, pushing all pending unreviewed draft edits in the ",
6507
+ selectedLifecycleStats.unreviewedCount,
6508
+ " draft/modified record(s) live."
6509
+ ] })
6510
+ ] })
6511
+ ] })
6512
+ }
6513
+ ),
6514
+ publishStrategy === "all" && /* @__PURE__ */ jsx(
6515
+ Box,
6516
+ {
6517
+ padding: 3,
6518
+ background: "danger100",
6519
+ hasRadius: true,
6520
+ style: { border: "1px solid #f5c0b8" },
6521
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
6522
+ /* @__PURE__ */ jsx(
6523
+ Checkbox,
6524
+ {
6525
+ name: "unreviewedConsent",
6526
+ checked: unreviewedConsent,
6527
+ onCheckedChange: (checked) => setUnreviewedConsent(Boolean(checked))
6528
+ }
6529
+ ),
6530
+ /* @__PURE__ */ jsxs(Box, { style: { cursor: "pointer" }, onClick: () => setUnreviewedConsent(!unreviewedConsent), children: [
6531
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "Mandatory Risk Consent Required" }),
6532
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", children: [
6533
+ "I understand and explicitly consent that ",
6534
+ selectedLifecycleStats.unreviewedCount,
6535
+ " record(s) with unreviewed draft revisions or saved draft states will be published live to production visitors."
6536
+ ] })
6537
+ ] })
6538
+ ] })
6539
+ }
6540
+ )
6541
+ ] })
6542
+ ]
6543
+ }
6544
+ ),
5811
6545
  /* @__PURE__ */ jsx(Box, { marginBottom: 2, children: /* @__PURE__ */ jsx(
5812
6546
  TextInput,
5813
6547
  {
@@ -5834,54 +6568,61 @@ const BulkUpdater = () => {
5834
6568
  zIndex: 20,
5835
6569
  boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
5836
6570
  },
5837
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
5838
- /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
5839
- /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
5840
- /* @__PURE__ */ jsx(
5841
- Button,
5842
- {
5843
- variant: executionWindow?.isBusinessHours ? "default" : "secondary",
5844
- size: "M",
5845
- onClick: () => executeBatchWithMode("maker_checker"),
5846
- loading: updating,
5847
- children: "Submit for Checker Approval"
5848
- }
5849
- ),
5850
- executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
5851
- /* @__PURE__ */ jsxs(
6571
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, style: { width: "100%" }, children: [
6572
+ publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: "⚠️ Explicit consent is required before publishing records with unreviewed draft revisions." }),
6573
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
6574
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowConfirmModal(false), children: "Cancel" }),
6575
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
6576
+ /* @__PURE__ */ jsx(
5852
6577
  Button,
5853
6578
  {
5854
- variant: "secondary",
6579
+ variant: executionWindow?.isBusinessHours ? "default" : "secondary",
5855
6580
  size: "M",
5856
- onClick: () => executeBatchWithMode("queue_offpeak"),
6581
+ onClick: () => executeBatchWithMode("maker_checker"),
5857
6582
  loading: updating,
5858
- children: [
5859
- "Queue for Off-Peak (",
5860
- executionWindow?.offPeakStartTime || "18:00",
5861
- ")"
5862
- ]
6583
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6584
+ children: "Submit for Checker Approval"
5863
6585
  }
5864
6586
  ),
5865
- /* @__PURE__ */ jsx(
6587
+ executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
6588
+ /* @__PURE__ */ jsxs(
6589
+ Button,
6590
+ {
6591
+ variant: "secondary",
6592
+ size: "M",
6593
+ onClick: () => executeBatchWithMode("queue_offpeak"),
6594
+ loading: updating,
6595
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6596
+ children: [
6597
+ "Queue for Off-Peak (",
6598
+ executionWindow?.offPeakStartTime || "18:00",
6599
+ ")"
6600
+ ]
6601
+ }
6602
+ ),
6603
+ /* @__PURE__ */ jsx(
6604
+ Button,
6605
+ {
6606
+ variant: "danger-light",
6607
+ size: "S",
6608
+ onClick: () => executeBatchWithMode("immediate", true),
6609
+ loading: updating,
6610
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6611
+ children: "Force Run Now"
6612
+ }
6613
+ )
6614
+ ] }) : /* @__PURE__ */ jsx(
5866
6615
  Button,
5867
6616
  {
5868
- variant: "danger-light",
5869
- size: "S",
5870
- onClick: () => executeBatchWithMode("immediate", true),
6617
+ variant: "default",
6618
+ size: "M",
6619
+ onClick: () => executeBatchWithMode("immediate"),
5871
6620
  loading: updating,
5872
- children: "Force Run Now"
6621
+ disabled: updating || publishOnApproval && selectedLifecycleStats.unreviewedCount > 0 && publishStrategy === "all" && !unreviewedConsent,
6622
+ children: "Confirm & Execute Now"
5873
6623
  }
5874
6624
  )
5875
- ] }) : /* @__PURE__ */ jsx(
5876
- Button,
5877
- {
5878
- variant: "default",
5879
- size: "M",
5880
- onClick: () => executeBatchWithMode("immediate"),
5881
- loading: updating,
5882
- children: "Confirm & Execute Now"
5883
- }
5884
- )
6625
+ ] })
5885
6626
  ] })
5886
6627
  ] })
5887
6628
  }
@@ -5956,6 +6697,27 @@ const BulkUpdater = () => {
5956
6697
  gap: 16
5957
6698
  },
5958
6699
  children: [
6700
+ /* @__PURE__ */ jsxs(Box, { padding: 3, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
6701
+ /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 2, display: "block", children: [
6702
+ "LIFECYCLE STATUS BREAKDOWN (",
6703
+ totalSelectedCount,
6704
+ " Selected)"
6705
+ ] }),
6706
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", children: [
6707
+ /* @__PURE__ */ jsxs(Badge, { backgroundColor: "success100", textColor: "success700", children: [
6708
+ "✓ Published: ",
6709
+ selectedLifecycleStats.published
6710
+ ] }),
6711
+ selectedLifecycleStats.modified > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
6712
+ "📝 Modified (Unreviewed Revisions): ",
6713
+ selectedLifecycleStats.modified
6714
+ ] }),
6715
+ selectedLifecycleStats.draft > 0 && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "neutral200", textColor: "neutral800", children: [
6716
+ "📄 Draft (Never Published): ",
6717
+ selectedLifecycleStats.draft
6718
+ ] })
6719
+ ] })
6720
+ ] }),
5959
6721
  /* @__PURE__ */ jsxs(Box, { padding: 4, background: "neutral100", hasRadius: true, style: { border: "1px solid #eaeaef" }, children: [
5960
6722
  /* @__PURE__ */ jsx(Typography, { variant: "omega", fontWeight: "bold", children: "Collection Breakdown:" }),
5961
6723
  /* @__PURE__ */ jsx(Box, { marginTop: 2, children: collections.filter(
@@ -5994,6 +6756,37 @@ const BulkUpdater = () => {
5994
6756
  );
5995
6757
  }) })
5996
6758
  ] }),
6759
+ selectedLifecycleStats.unreviewedCount > 0 && /* @__PURE__ */ jsx(
6760
+ Box,
6761
+ {
6762
+ padding: 4,
6763
+ background: "danger100",
6764
+ hasRadius: true,
6765
+ style: { border: "1px solid #f5c0b8" },
6766
+ children: /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
6767
+ /* @__PURE__ */ jsx(
6768
+ Checkbox,
6769
+ {
6770
+ name: "publishConsent",
6771
+ checked: publishConsent,
6772
+ onCheckedChange: (checked) => setPublishConsent(Boolean(checked))
6773
+ }
6774
+ ),
6775
+ /* @__PURE__ */ jsxs(Box, { style: { cursor: "pointer" }, onClick: () => setPublishConsent(!publishConsent), children: [
6776
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", textColor: "danger700", display: "block", children: "Mandatory Risk Consent Required" }),
6777
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "danger700", children: [
6778
+ "I understand and explicitly consent that ",
6779
+ selectedLifecycleStats.unreviewedCount,
6780
+ " record(s) contain unreviewed draft changes (",
6781
+ selectedLifecycleStats.draft,
6782
+ " Draft, ",
6783
+ selectedLifecycleStats.modified,
6784
+ " Modified) and will be published live to production visitors."
6785
+ ] })
6786
+ ] })
6787
+ ] })
6788
+ }
6789
+ ),
5997
6790
  executionWindow?.isBusinessHours && /* @__PURE__ */ jsxs(
5998
6791
  Box,
5999
6792
  {
@@ -6038,54 +6831,61 @@ const BulkUpdater = () => {
6038
6831
  flexShrink: 0,
6039
6832
  boxShadow: "0 -4px 16px rgba(0, 0, 0, 0.08)"
6040
6833
  },
6041
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
6042
- /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowPublishConfirmModal(false), children: "Cancel" }),
6043
- /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
6044
- /* @__PURE__ */ jsx(
6045
- Button,
6046
- {
6047
- variant: executionWindow?.isBusinessHours ? "default" : "secondary",
6048
- size: "M",
6049
- onClick: () => executeBatchPublish("maker_checker"),
6050
- loading: publishing,
6051
- children: "Submit for Checker Approval"
6052
- }
6053
- ),
6054
- executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
6055
- /* @__PURE__ */ jsxs(
6834
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, style: { width: "100%" }, children: [
6835
+ selectedLifecycleStats.unreviewedCount > 0 && !publishConsent && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: "⚠️ Please check the mandatory consent box above to confirm publishing unreviewed draft records." }),
6836
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, style: { width: "100%" }, children: [
6837
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "M", onClick: () => setShowPublishConfirmModal(false), children: "Cancel" }),
6838
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", justifyContent: "flex-end", alignItems: "center", children: [
6839
+ /* @__PURE__ */ jsx(
6056
6840
  Button,
6057
6841
  {
6058
- variant: "secondary",
6842
+ variant: executionWindow?.isBusinessHours ? "default" : "secondary",
6059
6843
  size: "M",
6060
- onClick: () => executeBatchPublish("queue_offpeak"),
6844
+ onClick: () => executeBatchPublish("maker_checker"),
6061
6845
  loading: publishing,
6062
- children: [
6063
- "Queue for Off-Peak (",
6064
- executionWindow?.offPeakStartTime || "18:00",
6065
- ")"
6066
- ]
6846
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6847
+ children: "Submit for Checker Approval"
6067
6848
  }
6068
6849
  ),
6069
- /* @__PURE__ */ jsx(
6850
+ executionWindow?.isBusinessHours ? /* @__PURE__ */ jsxs(Fragment, { children: [
6851
+ /* @__PURE__ */ jsxs(
6852
+ Button,
6853
+ {
6854
+ variant: "secondary",
6855
+ size: "M",
6856
+ onClick: () => executeBatchPublish("queue_offpeak"),
6857
+ loading: publishing,
6858
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6859
+ children: [
6860
+ "Queue for Off-Peak (",
6861
+ executionWindow?.offPeakStartTime || "18:00",
6862
+ ")"
6863
+ ]
6864
+ }
6865
+ ),
6866
+ /* @__PURE__ */ jsx(
6867
+ Button,
6868
+ {
6869
+ variant: "danger-light",
6870
+ size: "S",
6871
+ onClick: () => executeBatchPublish("immediate", true),
6872
+ loading: publishing,
6873
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6874
+ children: "Force Publish Now"
6875
+ }
6876
+ )
6877
+ ] }) : /* @__PURE__ */ jsx(
6070
6878
  Button,
6071
6879
  {
6072
- variant: "danger-light",
6073
- size: "S",
6074
- onClick: () => executeBatchPublish("immediate", true),
6880
+ variant: "default",
6881
+ size: "M",
6882
+ onClick: () => executeBatchPublish("immediate"),
6075
6883
  loading: publishing,
6076
- children: "Force Publish Now"
6884
+ disabled: publishing || selectedLifecycleStats.unreviewedCount > 0 && !publishConsent,
6885
+ children: "Confirm & Publish Now"
6077
6886
  }
6078
6887
  )
6079
- ] }) : /* @__PURE__ */ jsx(
6080
- Button,
6081
- {
6082
- variant: "default",
6083
- size: "M",
6084
- onClick: () => executeBatchPublish("immediate"),
6085
- loading: publishing,
6086
- children: "Confirm & Publish Now"
6087
- }
6088
- )
6888
+ ] })
6089
6889
  ] })
6090
6890
  ] })
6091
6891
  }
@@ -6108,6 +6908,7 @@ const BulkUpdater = () => {
6108
6908
  previewData,
6109
6909
  attribute: selectedAttribute,
6110
6910
  publishOnApproval,
6911
+ lifecycleStats: selectedLifecycleStats,
6111
6912
  targetValueDisplay: relationsMeta[selectedAttribute] ? selectedRelationDocId === "__clear__" ? "Clear / Unlink Relation" : relationOptions.find((o) => o.documentId === selectedRelationDocId)?.label || selectedRelationDocId || "(none)" : useRegex ? `Replace /${regexPattern}/g with "${attributeValue}"` : attributeValue
6112
6913
  }
6113
6914
  ),
@@ -6129,10 +6930,13 @@ export {
6129
6930
  BulkUpdater as default,
6130
6931
  formatComponentName,
6131
6932
  formatDisplayValue,
6933
+ getActiveSortLabel,
6132
6934
  getAllNestedValues,
6133
6935
  getAvailableColumnsForCollection,
6134
6936
  getEntryLifecycleStatus,
6135
6937
  getNestedValue,
6938
+ getPropertyValue,
6939
+ getSortOptionsForCollection,
6136
6940
  parseDynamicZonePath
6137
6941
  };
6138
- //# sourceMappingURL=BulkUpdater-CMkPdA9_.mjs.map
6942
+ //# sourceMappingURL=BulkUpdater-CHdA0546.mjs.map