@optifye/dashboard-core 6.11.34 → 6.11.36

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.
package/dist/index.js CHANGED
@@ -1667,6 +1667,7 @@ var DEFAULT_SHIFT_DATA = {
1667
1667
  efficiency: 0,
1668
1668
  output: 0,
1669
1669
  cycleTime: 0,
1670
+ idealCycleTime: 0,
1670
1671
  pph: 0,
1671
1672
  pphThreshold: 0,
1672
1673
  idealOutput: 0,
@@ -4513,6 +4514,7 @@ var dashboardService = {
4513
4514
  avg_efficiency: item.avg_efficiency || 0,
4514
4515
  total_output: item.total_output || 0,
4515
4516
  avg_cycle_time: item.avg_cycle_time || 0,
4517
+ ideal_cycle_time: item.ideal_cycle_time ?? 0,
4516
4518
  ideal_output: item.ideal_output || 0,
4517
4519
  total_day_output: item.total_day_output ?? item.ideal_output ?? 0,
4518
4520
  avg_pph: item.avg_pph || 0,
@@ -17309,7 +17311,7 @@ var useActiveBreaks = (lineIds) => {
17309
17311
  const [isLoading, setIsLoading] = React143.useState(true);
17310
17312
  const [error, setError] = React143.useState(null);
17311
17313
  const supabase = useSupabase();
17312
- const parseTimeToMinutes4 = (timeStr) => {
17314
+ const parseTimeToMinutes5 = (timeStr) => {
17313
17315
  const [hours, minutes] = timeStr.split(":").map(Number);
17314
17316
  return hours * 60 + minutes;
17315
17317
  };
@@ -17318,8 +17320,8 @@ var useActiveBreaks = (lineIds) => {
17318
17320
  return now4.getHours() * 60 + now4.getMinutes();
17319
17321
  };
17320
17322
  const isTimeInBreak = (breakStart, breakEnd, currentMinutes) => {
17321
- const startMinutes = parseTimeToMinutes4(breakStart);
17322
- const endMinutes = parseTimeToMinutes4(breakEnd);
17323
+ const startMinutes = parseTimeToMinutes5(breakStart);
17324
+ const endMinutes = parseTimeToMinutes5(breakEnd);
17323
17325
  if (endMinutes < startMinutes) {
17324
17326
  return currentMinutes >= startMinutes || currentMinutes < endMinutes;
17325
17327
  } else {
@@ -17327,8 +17329,8 @@ var useActiveBreaks = (lineIds) => {
17327
17329
  }
17328
17330
  };
17329
17331
  const calculateBreakProgress = (breakStart, breakEnd, currentMinutes) => {
17330
- const startMinutes = parseTimeToMinutes4(breakStart);
17331
- const endMinutes = parseTimeToMinutes4(breakEnd);
17332
+ const startMinutes = parseTimeToMinutes5(breakStart);
17333
+ const endMinutes = parseTimeToMinutes5(breakEnd);
17332
17334
  let elapsedMinutes = 0;
17333
17335
  let remainingMinutes = 0;
17334
17336
  if (endMinutes < startMinutes) {
@@ -17346,8 +17348,8 @@ var useActiveBreaks = (lineIds) => {
17346
17348
  return { elapsedMinutes, remainingMinutes };
17347
17349
  };
17348
17350
  const isTimeInShift = (startTime, endTime, currentMinutes) => {
17349
- const startMinutes = parseTimeToMinutes4(startTime);
17350
- const endMinutes = parseTimeToMinutes4(endTime);
17351
+ const startMinutes = parseTimeToMinutes5(startTime);
17352
+ const endMinutes = parseTimeToMinutes5(endTime);
17351
17353
  if (endMinutes < startMinutes) {
17352
17354
  return currentMinutes >= startMinutes || currentMinutes < endMinutes;
17353
17355
  } else {
@@ -17407,8 +17409,8 @@ var useActiveBreaks = (lineIds) => {
17407
17409
  const endTime = breakItem.end || breakItem.endTime || "00:00";
17408
17410
  let duration = breakItem.duration || 0;
17409
17411
  if (!duration || duration === 0) {
17410
- const startMinutes = parseTimeToMinutes4(startTime);
17411
- const endMinutes = parseTimeToMinutes4(endTime);
17412
+ const startMinutes = parseTimeToMinutes5(startTime);
17413
+ const endMinutes = parseTimeToMinutes5(endTime);
17412
17414
  duration = endMinutes < startMinutes ? endMinutes + 24 * 60 - startMinutes : endMinutes - startMinutes;
17413
17415
  }
17414
17416
  return {
@@ -17424,8 +17426,8 @@ var useActiveBreaks = (lineIds) => {
17424
17426
  const endTime = breakItem.end || breakItem.endTime || "00:00";
17425
17427
  let duration = breakItem.duration || 0;
17426
17428
  if (!duration || duration === 0) {
17427
- const startMinutes = parseTimeToMinutes4(startTime);
17428
- const endMinutes = parseTimeToMinutes4(endTime);
17429
+ const startMinutes = parseTimeToMinutes5(startTime);
17430
+ const endMinutes = parseTimeToMinutes5(endTime);
17429
17431
  duration = endMinutes < startMinutes ? endMinutes + 24 * 60 - startMinutes : endMinutes - startMinutes;
17430
17432
  }
17431
17433
  return {
@@ -33440,13 +33442,13 @@ var CycleTimeOverTimeChart = ({
33440
33442
  observer.observe(containerRef.current);
33441
33443
  return () => observer.disconnect();
33442
33444
  }, []);
33443
- const parseTimeToMinutes4 = (value) => {
33445
+ const parseTimeToMinutes5 = (value) => {
33444
33446
  const [hours, minutes] = value.split(":").map(Number);
33445
33447
  if (!Number.isFinite(hours) || !Number.isFinite(minutes)) return 0;
33446
33448
  return hours * 60 + minutes;
33447
33449
  };
33448
33450
  const formatHourLabel = (slotIndex) => {
33449
- const baseMinutes = parseTimeToMinutes4(shiftStart);
33451
+ const baseMinutes = parseTimeToMinutes5(shiftStart);
33450
33452
  const absoluteMinutes = baseMinutes + slotIndex * 60;
33451
33453
  const hour24 = Math.floor(absoluteMinutes % (24 * 60) / 60);
33452
33454
  const ampm = hour24 >= 12 ? "PM" : "AM";
@@ -37447,6 +37449,14 @@ var formatTimestampRange = (startTime, endTime, timezone) => {
37447
37449
  });
37448
37450
  return `${startFormatted} - ${endFormatted}`;
37449
37451
  };
37452
+ var buildPrefetchedExplorerMetadata = (activeFilter, metadataCategoryId, categoryMetadata) => {
37453
+ if (!activeFilter || !metadataCategoryId || metadataCategoryId !== activeFilter || categoryMetadata.length === 0) {
37454
+ return void 0;
37455
+ }
37456
+ return {
37457
+ [activeFilter]: categoryMetadata
37458
+ };
37459
+ };
37450
37460
  var getSecondsBetweenTimestamps = (startTime, endTime) => {
37451
37461
  const startDate = parseTimestamp(startTime);
37452
37462
  const endDate = parseTimestamp(endTime);
@@ -42644,6 +42654,7 @@ var BottlenecksContent = ({
42644
42654
  const [error, setError] = React143.useState(null);
42645
42655
  const [clipClassifications, setClipClassifications] = React143.useState({});
42646
42656
  const [categoryMetadata, setCategoryMetadata] = React143.useState([]);
42657
+ const [categoryMetadataCategoryId, setCategoryMetadataCategoryId] = React143.useState(null);
42647
42658
  const [currentMetadataIndex, setCurrentMetadataIndex] = React143.useState(0);
42648
42659
  const [metadataCache, setMetadataCache] = React143.useState({});
42649
42660
  const invalidateMetadataCache = React143.useCallback((categories) => {
@@ -43115,6 +43126,15 @@ var BottlenecksContent = ({
43115
43126
  const getMetadataCacheKey = React143.useCallback((categoryId) => {
43116
43127
  return `${categoryId}-${effectiveDateString}-${effectiveShiftId}-${snapshotDateTime ?? "nosnap"}-${snapshotClipId ?? "nosnap"}`;
43117
43128
  }, [effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId]);
43129
+ const setVisibleCategoryMetadata = React143.useCallback((categoryId, clips) => {
43130
+ if (activeFilterRef.current !== categoryId) {
43131
+ return false;
43132
+ }
43133
+ categoryMetadataRef.current = clips;
43134
+ setCategoryMetadata(clips);
43135
+ setCategoryMetadataCategoryId(clips.length > 0 ? categoryId : null);
43136
+ return true;
43137
+ }, []);
43118
43138
  const applyMetadataSnapshot = React143.useCallback((categoryId, clips, total) => {
43119
43139
  if (!clips || clips.length === 0) {
43120
43140
  return;
@@ -43124,13 +43144,12 @@ var BottlenecksContent = ({
43124
43144
  ...prev,
43125
43145
  [cacheKey]: clips
43126
43146
  }));
43127
- categoryMetadataRef.current = clips;
43128
- setCategoryMetadata(clips);
43147
+ setVisibleCategoryMetadata(categoryId, clips);
43129
43148
  if (!isPercentileCategory(categoryId) && typeof total === "number") {
43130
43149
  currentTotalRef.current = total;
43131
43150
  setCurrentTotal(total);
43132
43151
  }
43133
- }, [getMetadataCacheKey, isPercentileCategory]);
43152
+ }, [getMetadataCacheKey, isPercentileCategory, setVisibleCategoryMetadata]);
43134
43153
  const getClipTypesForPercentileCategory = React143.useCallback((categoryId) => {
43135
43154
  switch (categoryId) {
43136
43155
  case "fast-cycles":
@@ -43155,6 +43174,7 @@ var BottlenecksContent = ({
43155
43174
  return;
43156
43175
  }
43157
43176
  setCategoryMetadata([]);
43177
+ setCategoryMetadataCategoryId(null);
43158
43178
  categoryMetadataRef.current = [];
43159
43179
  updateActiveFilter(fallbackFilter);
43160
43180
  }, [isFastSlowClipFiltersEnabled, activeFilter, dynamicCounts, clipTypes, updateActiveFilter]);
@@ -43201,6 +43221,7 @@ var BottlenecksContent = ({
43201
43221
  }
43202
43222
  if (!isFastSlowClipFiltersEnabled && (categoryId === "fast-cycles" || categoryId === "slow-cycles")) {
43203
43223
  setCategoryMetadata([]);
43224
+ setCategoryMetadataCategoryId(null);
43204
43225
  categoryMetadataRef.current = [];
43205
43226
  return;
43206
43227
  }
@@ -43214,8 +43235,7 @@ var BottlenecksContent = ({
43214
43235
  try {
43215
43236
  if (cachedMetadata) {
43216
43237
  console.log(`[BottlenecksContent] Using cached metadata for ${categoryId}`);
43217
- setCategoryMetadata(cachedMetadata);
43218
- categoryMetadataRef.current = cachedMetadata;
43238
+ setVisibleCategoryMetadata(categoryId, cachedMetadata);
43219
43239
  if (autoLoadFirstVideo && cachedMetadata.length > 0 && s3ClipsService) {
43220
43240
  const firstClipMeta = cachedMetadata[0];
43221
43241
  try {
@@ -43329,8 +43349,7 @@ var BottlenecksContent = ({
43329
43349
  ...prev,
43330
43350
  [cacheKey]: metadataClips
43331
43351
  }));
43332
- setCategoryMetadata(metadataClips);
43333
- categoryMetadataRef.current = metadataClips;
43352
+ setVisibleCategoryMetadata(categoryId, metadataClips);
43334
43353
  console.log(`[BottlenecksContent] Loaded metadata for ${categoryId}: ${metadataClips.length} clips`);
43335
43354
  if (autoLoadFirstVideo && metadataClips.length > 0 && s3ClipsService) {
43336
43355
  const firstClipMeta = metadataClips[0];
@@ -43349,15 +43368,18 @@ var BottlenecksContent = ({
43349
43368
  }
43350
43369
  }
43351
43370
  } else {
43352
- setCategoryMetadata([]);
43353
- categoryMetadataRef.current = [];
43371
+ if (activeFilterRef.current === categoryId) {
43372
+ setCategoryMetadata([]);
43373
+ setCategoryMetadataCategoryId(null);
43374
+ categoryMetadataRef.current = [];
43375
+ }
43354
43376
  }
43355
43377
  } catch (error2) {
43356
43378
  console.error(`[BottlenecksContent] Error loading category metadata:`, error2);
43357
43379
  } finally {
43358
43380
  setIsCategoryLoading(false);
43359
43381
  }
43360
- }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, isFastSlowClipFiltersEnabled, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, supabase]);
43382
+ }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, isFastSlowClipFiltersEnabled, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, supabase, setVisibleCategoryMetadata]);
43361
43383
  React143.useEffect(() => {
43362
43384
  if (previousFilterRef.current !== activeFilter) {
43363
43385
  console.log(`Filter changed from ${previousFilterRef.current} to ${activeFilter} - resetting to first video`);
@@ -43367,6 +43389,7 @@ var BottlenecksContent = ({
43367
43389
  setIsNavigating(false);
43368
43390
  loadingCategoryRef.current = null;
43369
43391
  setCategoryMetadata([]);
43392
+ setCategoryMetadataCategoryId(null);
43370
43393
  setCurrentMetadataIndex(0);
43371
43394
  categoryMetadataRef.current = [];
43372
43395
  currentMetadataIndexRef.current = 0;
@@ -43925,14 +43948,11 @@ var BottlenecksContent = ({
43925
43948
  }
43926
43949
  return currentPosition;
43927
43950
  }, [activeFilter, categoryMetadata.length, currentMetadataIndex, currentPosition, isPercentileCategory]);
43928
- const prefetchedExplorerMetadata = React143.useMemo(() => {
43929
- if (!activeFilter || categoryMetadata.length === 0) {
43930
- return void 0;
43931
- }
43932
- return {
43933
- [activeFilter]: categoryMetadata
43934
- };
43935
- }, [activeFilter, categoryMetadata]);
43951
+ const prefetchedExplorerMetadata = React143.useMemo(() => buildPrefetchedExplorerMetadata(
43952
+ activeFilter,
43953
+ categoryMetadataCategoryId,
43954
+ categoryMetadata
43955
+ ), [activeFilter, categoryMetadata, categoryMetadataCategoryId]);
43936
43956
  const classificationClipIds = React143.useMemo(() => {
43937
43957
  if (!idleTimeVlmEnabled) {
43938
43958
  return [];
@@ -47885,6 +47905,7 @@ var LineMonthlyPdfGenerator = ({
47885
47905
  rangeEnd,
47886
47906
  selectedShiftId,
47887
47907
  availableShifts,
47908
+ lineAssembly = false,
47888
47909
  compact = false,
47889
47910
  className
47890
47911
  }) => {
@@ -48168,8 +48189,8 @@ var LineMonthlyPdfGenerator = ({
48168
48189
  timeZone: "Asia/Kolkata"
48169
48190
  });
48170
48191
  doc.text(dateStr, 25, yPos);
48171
- doc.text(`${shift.total_workspaces - shift.underperforming_workspaces}`, 60, yPos);
48172
- doc.text(`${shift.total_workspaces}`, 95, yPos);
48192
+ doc.text(`${Math.round(shift.output || 0)}`, 60, yPos);
48193
+ doc.text(`${Math.round(shift.idealOutput || 0)}`, 95, yPos);
48173
48194
  doc.text(`${shift.avg_efficiency.toFixed(1)}%`, 135, yPos);
48174
48195
  const statusColor = getEfficiencyColor(shift.avg_efficiency, effectiveLegend);
48175
48196
  if (statusColor === "green") {
@@ -48199,6 +48220,8 @@ var LineMonthlyPdfGenerator = ({
48199
48220
  doc.setTextColor(0, 0, 0);
48200
48221
  }
48201
48222
  const poorestWorkspaces = underperformingWorkspaces[selectedShiftId] || [];
48223
+ const isCycleTimeWorkspace = (workspace) => workspace.metric_mode === "cycle_time" || lineAssembly && (workspace.avg_cycle_time !== void 0 || workspace.ideal_cycle_time !== void 0 || workspace.cycle_ratio !== void 0);
48224
+ const showCycleTimePoorestPerformers = !isUptimeMode && poorestWorkspaces.some(isCycleTimeWorkspace);
48202
48225
  if (poorestWorkspaces && poorestWorkspaces.length > 0) {
48203
48226
  doc.addPage();
48204
48227
  doc.setFontSize(14);
@@ -48224,7 +48247,11 @@ var LineMonthlyPdfGenerator = ({
48224
48247
  doc.setFillColor(245, 245, 245);
48225
48248
  doc.roundedRect(20, 45, 170, 8, 1, 1, "F");
48226
48249
  doc.text("Workspace", 25, 50);
48227
- doc.text(isUptimeMode ? "Avg Utilization" : "Avg Efficiency", 120, 50);
48250
+ doc.text(
48251
+ isUptimeMode ? "Avg Utilization" : showCycleTimePoorestPerformers ? "Cycle Time" : "Avg Efficiency",
48252
+ 120,
48253
+ 50
48254
+ );
48228
48255
  doc.text("Last 5 Days", 160, 50);
48229
48256
  doc.setLineWidth(0.2);
48230
48257
  doc.setDrawColor(220, 220, 220);
@@ -48243,7 +48270,16 @@ var LineMonthlyPdfGenerator = ({
48243
48270
  );
48244
48271
  const workspaceName = rawWorkspaceName.length > 30 ? `${rawWorkspaceName.substring(0, 27)}...` : rawWorkspaceName;
48245
48272
  doc.text(workspaceName, 25, yPos2);
48246
- doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}%`, 120, yPos2);
48273
+ if (isUptimeMode) {
48274
+ doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}%`, 120, yPos2);
48275
+ } else if (isCycleTimeWorkspace(workspace)) {
48276
+ const actualCycleTime = Number.isFinite(workspace.avg_cycle_time) ? Number(workspace.avg_cycle_time) : null;
48277
+ const targetCycleTime = Number.isFinite(workspace.ideal_cycle_time) ? Number(workspace.ideal_cycle_time) : null;
48278
+ const cycleTimeText = actualCycleTime !== null ? `${actualCycleTime.toFixed(1)}s${targetCycleTime !== null ? ` / ${targetCycleTime.toFixed(1)}s` : ""}` : "-";
48279
+ doc.text(cycleTimeText, 120, yPos2);
48280
+ } else {
48281
+ doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}%`, 120, yPos2);
48282
+ }
48247
48283
  const squareSize = 3;
48248
48284
  const squareSpacing = 1;
48249
48285
  let squareX = 160;
@@ -48338,12 +48374,279 @@ Underperforming Workspaces: ${lineInfo.metrics.underperforming_workspaces} / ${l
48338
48374
  }
48339
48375
  );
48340
48376
  };
48377
+
48378
+ // src/lib/utils/hourlyTargets.ts
48379
+ var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
48380
+ var MINUTES_PER_DAY = 24 * 60;
48381
+ var parseTimeToMinutes2 = (timeString) => {
48382
+ const normalized = stripSeconds2(timeString || "");
48383
+ if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
48384
+ const [hours, minutes] = normalized.split(":").map(Number);
48385
+ return hours * 60 + minutes;
48386
+ };
48387
+ var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
48388
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
48389
+ if (!Number.isFinite(shiftStartMinutes)) return [];
48390
+ const normalizedBreaks = [];
48391
+ for (const entry of breaks) {
48392
+ const startRaw = parseTimeToMinutes2(entry.startTime);
48393
+ const endRaw = parseTimeToMinutes2(entry.endTime);
48394
+ if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
48395
+ let start = startRaw;
48396
+ let end = endRaw;
48397
+ if (end <= start) {
48398
+ end += 24 * 60;
48399
+ }
48400
+ if (start < shiftStartMinutes) {
48401
+ start += 24 * 60;
48402
+ end += 24 * 60;
48403
+ }
48404
+ const label = entry.remarks?.trim() || "Break";
48405
+ normalizedBreaks.push({ start, end, label });
48406
+ }
48407
+ return normalizedBreaks;
48408
+ };
48409
+ var roundTarget = (value, mode) => {
48410
+ if (!Number.isFinite(value)) return 0;
48411
+ switch (mode) {
48412
+ case "floor":
48413
+ return Math.floor(value);
48414
+ case "ceil":
48415
+ return Math.ceil(value);
48416
+ case "round":
48417
+ default:
48418
+ return Math.round(value);
48419
+ }
48420
+ };
48421
+ var formatDateKey = (date) => {
48422
+ const year = date.getUTCFullYear();
48423
+ const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
48424
+ const day = `${date.getUTCDate()}`.padStart(2, "0");
48425
+ return `${year}-${month}-${day}`;
48426
+ };
48427
+ var shiftDateKey = (dateKey, deltaDays) => {
48428
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
48429
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
48430
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
48431
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
48432
+ const date = new Date(Date.UTC(year, month - 1, day));
48433
+ date.setUTCDate(date.getUTCDate() + deltaDays);
48434
+ return formatDateKey(date);
48435
+ };
48436
+ var getZonedNowSnapshot = (timeZone, now4) => {
48437
+ const formatter = new Intl.DateTimeFormat("en-US", {
48438
+ timeZone,
48439
+ year: "numeric",
48440
+ month: "2-digit",
48441
+ day: "2-digit",
48442
+ hour: "2-digit",
48443
+ minute: "2-digit",
48444
+ hourCycle: "h23"
48445
+ });
48446
+ const parts = formatter.formatToParts(now4).reduce((acc, part) => {
48447
+ if (part.type !== "literal") {
48448
+ acc[part.type] = part.value;
48449
+ }
48450
+ return acc;
48451
+ }, {});
48452
+ const year = Number(parts.year);
48453
+ const month = Number(parts.month);
48454
+ const day = Number(parts.day);
48455
+ const hour = Number(parts.hour);
48456
+ const minute = Number(parts.minute);
48457
+ return {
48458
+ dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
48459
+ minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
48460
+ };
48461
+ };
48462
+ var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
48463
+ var buildHourlyIntervals = ({
48464
+ shiftStart,
48465
+ shiftEnd,
48466
+ bucketMinutes = 60,
48467
+ fallbackHours = 11
48468
+ }) => {
48469
+ const startMinutes = parseTimeToMinutes2(shiftStart);
48470
+ if (!Number.isFinite(startMinutes)) return [];
48471
+ const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
48472
+ let totalMinutes;
48473
+ const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
48474
+ if (!Number.isFinite(endRaw)) {
48475
+ totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
48476
+ } else {
48477
+ let endMinutes = endRaw;
48478
+ if (endMinutes <= startMinutes) {
48479
+ endMinutes += 24 * 60;
48480
+ }
48481
+ totalMinutes = endMinutes - startMinutes;
48482
+ }
48483
+ if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
48484
+ const count = Math.ceil(totalMinutes / bucket);
48485
+ const shiftEndMinutes = startMinutes + totalMinutes;
48486
+ const intervals = [];
48487
+ for (let i = 0; i < count; i += 1) {
48488
+ const start = startMinutes + i * bucket;
48489
+ const end = Math.min(start + bucket, shiftEndMinutes);
48490
+ const minutes = Math.max(0, end - start);
48491
+ if (minutes <= 0) continue;
48492
+ intervals.push({ start, end, minutes });
48493
+ }
48494
+ return intervals;
48495
+ };
48496
+ var computeBreakMinutesByInterval = ({
48497
+ intervals,
48498
+ shiftStart,
48499
+ breaks
48500
+ }) => {
48501
+ if (!intervals.length || !breaks.length) return intervals.map(() => 0);
48502
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
48503
+ return intervals.map((interval) => {
48504
+ if (!normalizedBreaks.length) return 0;
48505
+ let total = 0;
48506
+ for (const brk of normalizedBreaks) {
48507
+ const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
48508
+ total += overlap;
48509
+ if (total >= interval.minutes) return interval.minutes;
48510
+ }
48511
+ return Math.min(interval.minutes, total);
48512
+ });
48513
+ };
48514
+ var computeBreakRemarksByInterval = ({
48515
+ intervals,
48516
+ shiftStart,
48517
+ breaks
48518
+ }) => {
48519
+ if (!intervals.length || !breaks.length) return intervals.map(() => "");
48520
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
48521
+ return intervals.map((interval) => {
48522
+ const labels = normalizedBreaks.filter((brk) => Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start)) > 0).map((brk) => brk.label).filter((label, index, values) => label && values.indexOf(label) === index);
48523
+ return labels.join(", ");
48524
+ });
48525
+ };
48526
+ var computeEffectiveTargets = ({
48527
+ intervals,
48528
+ breakMinutes,
48529
+ pphThreshold,
48530
+ rounding = "round"
48531
+ }) => {
48532
+ return intervals.map((interval, idx) => {
48533
+ const intervalMinutes = Number(interval?.minutes) || 0;
48534
+ const breakMins = Number(breakMinutes?.[idx]) || 0;
48535
+ const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
48536
+ if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
48537
+ if (plannedWorkMinutes <= 0) return 0;
48538
+ return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
48539
+ });
48540
+ };
48541
+ var buildHourlyTargetPlan = ({
48542
+ shiftStart,
48543
+ shiftEnd,
48544
+ breaks = [],
48545
+ pphThreshold,
48546
+ bucketMinutes = 60,
48547
+ fallbackHours = 11,
48548
+ rounding = "round"
48549
+ }) => {
48550
+ const intervals = buildHourlyIntervals({
48551
+ shiftStart,
48552
+ shiftEnd,
48553
+ bucketMinutes,
48554
+ fallbackHours
48555
+ });
48556
+ const breakMinutes = computeBreakMinutesByInterval({
48557
+ intervals,
48558
+ shiftStart,
48559
+ breaks
48560
+ });
48561
+ const breakRemarks = computeBreakRemarksByInterval({
48562
+ intervals,
48563
+ shiftStart,
48564
+ breaks
48565
+ });
48566
+ const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
48567
+ const targets = computeEffectiveTargets({
48568
+ intervals,
48569
+ breakMinutes,
48570
+ pphThreshold,
48571
+ rounding
48572
+ });
48573
+ return {
48574
+ intervals,
48575
+ breakMinutes,
48576
+ breakRemarks,
48577
+ productiveMinutes,
48578
+ targets
48579
+ };
48580
+ };
48581
+ var isHourlyIntervalComplete = ({
48582
+ reportDate,
48583
+ shiftStart,
48584
+ shiftEnd,
48585
+ interval,
48586
+ timeZone = "Asia/Kolkata",
48587
+ now: now4 = /* @__PURE__ */ new Date()
48588
+ }) => {
48589
+ if (!reportDate) return true;
48590
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
48591
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
48592
+ const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
48593
+ const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
48594
+ if (reportDate === snapshot.dateKey) {
48595
+ return interval.end <= snapshot.minutesOfDay;
48596
+ }
48597
+ if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
48598
+ return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
48599
+ }
48600
+ return reportDate < snapshot.dateKey;
48601
+ };
48602
+ var isShiftInProgressForReportDate = ({
48603
+ reportDate,
48604
+ shiftStart,
48605
+ shiftEnd,
48606
+ timeZone = "Asia/Kolkata",
48607
+ now: now4 = /* @__PURE__ */ new Date()
48608
+ }) => {
48609
+ if (!reportDate || !shiftStart || !shiftEnd) return false;
48610
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
48611
+ const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
48612
+ if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
48613
+ return false;
48614
+ }
48615
+ let shiftEndMinutes = shiftEndMinutesRaw;
48616
+ const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
48617
+ if (wrapsMidnight) {
48618
+ shiftEndMinutes += MINUTES_PER_DAY;
48619
+ }
48620
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
48621
+ let currentMinutes = null;
48622
+ if (reportDate === snapshot.dateKey) {
48623
+ currentMinutes = snapshot.minutesOfDay;
48624
+ } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
48625
+ currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
48626
+ }
48627
+ if (currentMinutes === null) {
48628
+ return false;
48629
+ }
48630
+ return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
48631
+ };
48632
+ var formatOperationalDateKey = (dateKey, options) => {
48633
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
48634
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
48635
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
48636
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
48637
+ return new Date(Date.UTC(year, month - 1, day, 12)).toLocaleDateString("en-IN", {
48638
+ ...options,
48639
+ timeZone: "UTC"
48640
+ });
48641
+ };
48341
48642
  var LinePdfGenerator = ({
48342
48643
  lineInfo,
48343
48644
  workspaceData,
48344
48645
  issueResolutionSummary,
48345
48646
  shiftName,
48346
- className
48647
+ className,
48648
+ shiftBreaks = [],
48649
+ reportTimezone = "Asia/Kolkata"
48347
48650
  }) => {
48348
48651
  const [isGenerating, setIsGenerating] = React143.useState(false);
48349
48652
  const formatResolutionDuration2 = (seconds) => {
@@ -48370,7 +48673,7 @@ var LinePdfGenerator = ({
48370
48673
  doc.setFontSize(9);
48371
48674
  doc.setFont("helvetica", "normal");
48372
48675
  doc.setTextColor(100, 100, 100);
48373
- const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
48676
+ const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: reportTimezone })}`;
48374
48677
  const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
48375
48678
  doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
48376
48679
  doc.setDrawColor(200, 200, 200);
@@ -48388,11 +48691,10 @@ var LinePdfGenerator = ({
48388
48691
  const isUptimeMode = lineInfo.monitoring_mode === "uptime";
48389
48692
  const rawShiftType = shiftName || (lineInfo.shift_id === 0 ? "Day" : lineInfo.shift_id === 1 ? "Night" : `Shift ${lineInfo.shift_id}`);
48390
48693
  const shiftType = rawShiftType.toLowerCase().includes("shift") ? rawShiftType : `${rawShiftType} Shift`;
48391
- const date = new Date(lineInfo.date).toLocaleDateString("en-IN", {
48694
+ const date = formatOperationalDateKey(lineInfo.date, {
48392
48695
  weekday: "long",
48393
48696
  day: "numeric",
48394
- month: "long",
48395
- timeZone: "Asia/Kolkata"
48697
+ month: "long"
48396
48698
  });
48397
48699
  const shiftStartTime = lineInfo.metrics.shift_start ? (/* @__PURE__ */ new Date(`2000-01-01 ${lineInfo.metrics.shift_start}`)).toLocaleTimeString("en-IN", {
48398
48700
  hour: "2-digit",
@@ -48400,24 +48702,25 @@ var LinePdfGenerator = ({
48400
48702
  hour12: true,
48401
48703
  timeZone: "Asia/Kolkata"
48402
48704
  }) : "N/A";
48403
- const isToday2 = new Date(lineInfo.date).toDateString() === (/* @__PURE__ */ new Date()).toDateString();
48404
- let reportEndTime;
48405
- if (isToday2) {
48406
- reportEndTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-IN", {
48407
- hour: "2-digit",
48408
- minute: "2-digit",
48409
- timeZone: "Asia/Kolkata"
48410
- });
48411
- } else {
48412
- reportEndTime = lineInfo.metrics.shift_end ? (/* @__PURE__ */ new Date(`2000-01-01 ${lineInfo.metrics.shift_end}`)).toLocaleTimeString("en-IN", {
48413
- hour: "2-digit",
48414
- minute: "2-digit",
48415
- hour12: true,
48416
- timeZone: "Asia/Kolkata"
48417
- }) : "N/A";
48418
- }
48705
+ const currentTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-IN", {
48706
+ hour: "2-digit",
48707
+ minute: "2-digit",
48708
+ timeZone: reportTimezone
48709
+ });
48710
+ const reportEndTime = isShiftInProgressForReportDate({
48711
+ reportDate: lineInfo.date,
48712
+ shiftStart: lineInfo.metrics.shift_start || "",
48713
+ shiftEnd: lineInfo.metrics.shift_end,
48714
+ timeZone: reportTimezone
48715
+ }) ? currentTime : lineInfo.metrics.shift_end ? (/* @__PURE__ */ new Date(`2000-01-01 ${lineInfo.metrics.shift_end}`)).toLocaleTimeString("en-IN", {
48716
+ hour: "2-digit",
48717
+ minute: "2-digit",
48718
+ hour12: true,
48719
+ timeZone: "Asia/Kolkata"
48720
+ }) : "N/A";
48419
48721
  if (isUptimeMode) {
48420
48722
  const configuredTimezone = "Asia/Kolkata";
48723
+ const effectiveUptimeTimezone = reportTimezone || configuredTimezone;
48421
48724
  const lineShiftStart = lineInfo.metrics.shift_start || "06:00";
48422
48725
  const lineShiftEnd = lineInfo.metrics.shift_end || "14:00";
48423
48726
  const shiftMinutes = getShiftDurationMinutes(lineShiftStart, lineShiftEnd) || 0;
@@ -48431,7 +48734,7 @@ var LinePdfGenerator = ({
48431
48734
  shiftStart,
48432
48735
  shiftEnd,
48433
48736
  shiftDate,
48434
- timezone: configuredTimezone
48737
+ timezone: effectiveUptimeTimezone
48435
48738
  });
48436
48739
  let activeMinutes = uptimeSeries.activeMinutes;
48437
48740
  let idleMinutes = uptimeSeries.idleMinutes;
@@ -48512,7 +48815,7 @@ var LinePdfGenerator = ({
48512
48815
  shiftStart: lineShiftStart,
48513
48816
  shiftEnd: lineShiftEnd,
48514
48817
  shiftDate: lineInfo.date,
48515
- timezone: configuredTimezone
48818
+ timezone: effectiveUptimeTimezone
48516
48819
  });
48517
48820
  hourlyData = buildHourlyFromSeries(lineUptimeSeries);
48518
48821
  }
@@ -48620,13 +48923,13 @@ var LinePdfGenerator = ({
48620
48923
  doc.setFontSize(contentFontSize);
48621
48924
  doc.setFont("helvetica", "normal");
48622
48925
  let yPos2 = headerBottomY2 + 5.5;
48623
- const now5 = /* @__PURE__ */ new Date();
48624
- const currentTimeIST2 = new Date(now5.toLocaleString("en-US", { timeZone: configuredTimezone }));
48625
- const [sYear2, sMonth2, sDay2] = lineInfo.date.split("-").map(Number);
48626
- const [sStartH2, sStartM2] = (lineShiftStart || "06:00").split(":").map(Number);
48627
- const shiftStartBase2 = new Date(sYear2, sMonth2 - 1, sDay2, sStartH2, sStartM2 || 0);
48926
+ const now4 = /* @__PURE__ */ new Date();
48927
+ const currentTimeIST = new Date(now4.toLocaleString("en-US", { timeZone: configuredTimezone }));
48928
+ const [sYear, sMonth, sDay] = lineInfo.date.split("-").map(Number);
48929
+ const [sStartH, sStartM] = (lineShiftStart || "06:00").split(":").map(Number);
48930
+ const shiftStartBase = new Date(sYear, sMonth - 1, sDay, sStartH, sStartM || 0);
48628
48931
  hourlyData.forEach((entry, index) => {
48629
- const bucketStartTime = new Date(shiftStartBase2);
48932
+ const bucketStartTime = new Date(shiftStartBase);
48630
48933
  bucketStartTime.setHours(bucketStartTime.getHours() + index);
48631
48934
  const bucketEndTime = new Date(bucketStartTime);
48632
48935
  bucketEndTime.setHours(bucketEndTime.getHours() + 1);
@@ -48637,7 +48940,7 @@ var LinePdfGenerator = ({
48637
48940
  hour: "numeric",
48638
48941
  hour12: true
48639
48942
  })}`;
48640
- const dataCollected = bucketEndTime.getTime() <= currentTimeIST2.getTime();
48943
+ const dataCollected = bucketEndTime.getTime() <= currentTimeIST.getTime();
48641
48944
  if (index < totalRows2 - 1) {
48642
48945
  const rowBottomY = headerBottomY2 + (index + 1) * rowHeight;
48643
48946
  doc.setDrawColor(200, 200, 200);
@@ -48648,11 +48951,10 @@ var LinePdfGenerator = ({
48648
48951
  doc.text(utilizationStr, 147, yPos2);
48649
48952
  yPos2 += rowHeight;
48650
48953
  });
48651
- const fileDate2 = new Date(lineInfo.date).toLocaleDateString("en-IN", {
48954
+ const fileDate2 = formatOperationalDateKey(lineInfo.date, {
48652
48955
  day: "2-digit",
48653
48956
  month: "short",
48654
- year: "numeric",
48655
- timeZone: "Asia/Kolkata"
48957
+ year: "numeric"
48656
48958
  }).replace(/ /g, "_");
48657
48959
  const fileShift2 = shiftType.replace(/ /g, "_");
48658
48960
  const fileName2 = `${lineInfo.line_name}_${fileDate2}_${fileShift2}.pdf`;
@@ -48705,66 +49007,32 @@ var LinePdfGenerator = ({
48705
49007
  doc.setLineWidth(0.8);
48706
49008
  doc.line(20, 123, 190, 123);
48707
49009
  const hourlyOverviewStartY = 128;
48708
- const parseTimeToMinutes4 = (timeStr) => {
48709
- const [hours, minutes] = timeStr.split(":");
48710
- const hour = parseInt(hours, 10);
48711
- const minute = parseInt(minutes || "0", 10);
48712
- if (Number.isNaN(hour) || Number.isNaN(minute)) {
48713
- return NaN;
48714
- }
48715
- return (hour * 60 + minute) % (24 * 60);
48716
- };
48717
49010
  const formatMinutesLabel = (totalMinutes) => {
48718
49011
  const normalized = (totalMinutes % (24 * 60) + 24 * 60) % (24 * 60);
48719
49012
  const hour = Math.floor(normalized / 60);
48720
49013
  const minute = normalized % 60;
48721
- const time2 = /* @__PURE__ */ new Date();
48722
- time2.setHours(hour);
48723
- time2.setMinutes(minute);
48724
- time2.setSeconds(0);
48725
- time2.setMilliseconds(0);
49014
+ const time2 = new Date(Date.UTC(2e3, 0, 1, hour, minute));
48726
49015
  return time2.toLocaleTimeString("en-IN", {
48727
49016
  hour: "2-digit",
48728
49017
  minute: "2-digit",
48729
49018
  hour12: true,
48730
- timeZone: "Asia/Kolkata"
49019
+ timeZone: "UTC"
48731
49020
  });
48732
49021
  };
48733
- const buildRange = (startMinutes, minutes) => {
48734
- const endMinutes = startMinutes + minutes;
48735
- return {
48736
- label: `${formatMinutesLabel(startMinutes)} - ${formatMinutesLabel(endMinutes)}`,
48737
- minutes
48738
- };
48739
- };
48740
- const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
48741
- const startMinutes = parseTimeToMinutes4(startTimeStr);
48742
- if (Number.isNaN(startMinutes)) {
48743
- return [];
48744
- }
48745
- if (!endTimeStr) {
48746
- const defaultHours = 11;
48747
- return Array.from({ length: defaultHours }, (_, i) => buildRange(startMinutes + i * 60, 60));
48748
- }
48749
- const endMinutes = parseTimeToMinutes4(endTimeStr);
48750
- if (Number.isNaN(endMinutes)) {
48751
- const fallbackHours = 11;
48752
- return Array.from({ length: fallbackHours }, (_, i) => buildRange(startMinutes + i * 60, 60));
48753
- }
48754
- let durationMinutes = endMinutes - startMinutes;
48755
- if (durationMinutes <= 0) {
48756
- durationMinutes += 24 * 60;
48757
- }
48758
- const rangeCount = Math.max(1, Math.ceil(durationMinutes / 60));
48759
- return Array.from({ length: rangeCount }, (_, i) => {
48760
- const remainingMinutes = durationMinutes - i * 60;
48761
- const rangeMinutes = remainingMinutes >= 60 ? 60 : remainingMinutes;
48762
- return buildRange(startMinutes + i * 60, rangeMinutes);
48763
- });
48764
- };
48765
- const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
49022
+ const hourlyTimeRanges = buildHourlyIntervals({
49023
+ shiftStart: lineInfo.metrics.shift_start || "06:00",
49024
+ shiftEnd: lineInfo.metrics.shift_end,
49025
+ fallbackHours: 11
49026
+ });
49027
+ const targetPlan = buildHourlyTargetPlan({
49028
+ shiftStart: lineInfo.metrics.shift_start || "06:00",
49029
+ shiftEnd: lineInfo.metrics.shift_end,
49030
+ breaks: shiftBreaks,
49031
+ pphThreshold: Number(lineInfo.metrics.threshold_pph ?? 0),
49032
+ fallbackHours: Math.max(hourlyTimeRanges.length, 1),
49033
+ rounding: "floor"
49034
+ });
48766
49035
  const shiftDuration = hourlyTimeRanges.length || 11;
48767
- const targetOutputPerHour = Math.round(lineInfo.metrics.threshold_pph ?? 0);
48768
49036
  let hourlyActualOutput = [];
48769
49037
  if (lineInfo.metrics.output_hourly && Object.keys(lineInfo.metrics.output_hourly).length > 0) {
48770
49038
  const [startHourStr, startMinuteStr] = (lineInfo.metrics.shift_start || "6:00").split(":");
@@ -48950,29 +49218,29 @@ var LinePdfGenerator = ({
48950
49218
  doc.text("Remarks", 160, tableHeaderY);
48951
49219
  doc.setFont("helvetica", "normal");
48952
49220
  let yPos = tableStartY;
48953
- const now4 = /* @__PURE__ */ new Date();
48954
- const currentTimeIST = new Date(now4.toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
48955
- const [sYear, sMonth, sDay] = lineInfo.date.split("-").map(Number);
48956
- const [sStartH, sStartM] = (lineInfo.metrics.shift_start || "06:00").split(":").map(Number);
48957
- const shiftStartBase = new Date(sYear, sMonth - 1, sDay, sStartH, sStartM || 0);
48958
49221
  hourlyTimeRanges.forEach((timeRange, index) => {
48959
49222
  const actualOutput = hourlyActualOutput[index] || 0;
48960
- const bucketStartTime = new Date(shiftStartBase);
48961
- bucketStartTime.setHours(bucketStartTime.getHours() + index);
48962
- const bucketEndTime = new Date(bucketStartTime.getTime() + timeRange.minutes * 60 * 1e3);
48963
- const dataCollected = bucketEndTime.getTime() <= currentTimeIST.getTime();
49223
+ const dataCollected = isHourlyIntervalComplete({
49224
+ reportDate: lineInfo.date,
49225
+ shiftStart: lineInfo.metrics.shift_start || "06:00",
49226
+ shiftEnd: lineInfo.metrics.shift_end,
49227
+ interval: timeRange,
49228
+ timeZone: reportTimezone
49229
+ });
48964
49230
  const outputStr = dataCollected ? actualOutput.toString() : "TBD";
48965
49231
  if (index < totalRows - 1) {
48966
49232
  const rowBottomY = headerBottomY + (index + 1) * rowSpacing;
48967
49233
  doc.setDrawColor(200, 200, 200);
48968
49234
  doc.line(20, rowBottomY, 190, rowBottomY);
48969
49235
  }
48970
- const rangeMinutes = timeRange.minutes || 60;
48971
- const targetForRange = targetOutputPerHour * (rangeMinutes / 60);
48972
- const targetStr = rangeMinutes === 60 ? targetOutputPerHour.toString() : targetForRange.toFixed(1).replace(/\.0$/, "");
48973
- doc.text(timeRange.label, 25, yPos);
49236
+ const targetForRange = targetPlan.targets[index] ?? 0;
49237
+ const targetStr = targetForRange.toString();
49238
+ const remarkText = targetPlan.breakRemarks[index] || "";
49239
+ doc.text(`${formatMinutesLabel(timeRange.start)} - ${formatMinutesLabel(timeRange.end)}`, 25, yPos);
48974
49240
  doc.text(outputStr, 75, yPos);
48975
49241
  doc.text(targetStr, 105, yPos);
49242
+ const remarkDisplay = remarkText ? doc.splitTextToSize(remarkText, 26)[0] : "";
49243
+ doc.text(remarkDisplay, 160, yPos);
48976
49244
  if (!dataCollected) {
48977
49245
  doc.setTextColor(100, 100, 100);
48978
49246
  doc.text("-", 135, yPos);
@@ -48988,11 +49256,10 @@ var LinePdfGenerator = ({
48988
49256
  doc.setTextColor(0, 0, 0);
48989
49257
  yPos += rowSpacing;
48990
49258
  });
48991
- const fileDate = new Date(lineInfo.date).toLocaleDateString("en-IN", {
49259
+ const fileDate = formatOperationalDateKey(lineInfo.date, {
48992
49260
  day: "2-digit",
48993
49261
  month: "short",
48994
- year: "numeric",
48995
- timeZone: "Asia/Kolkata"
49262
+ year: "numeric"
48996
49263
  }).replace(/ /g, "_");
48997
49264
  const fileShift = shiftType.replace(/ /g, "_");
48998
49265
  const fileName = `${lineInfo.line_name}_${fileDate}_${fileShift}.pdf`;
@@ -49920,6 +50187,13 @@ var WorkspaceMonthlyHistory = ({
49920
50187
  }, [analysisMonthlyData, selectedShiftId, isUptimeMode, shiftWorkSeconds]);
49921
50188
  const efficiencyDelta = trendSummary?.avg_efficiency?.delta_pp ?? 0;
49922
50189
  const efficiencyImproved = efficiencyDelta >= 0;
50190
+ const assemblyRangeCycleTime = React143.useMemo(() => {
50191
+ const trendCycleTime = Number(trendSummary?.avg_cycle_time?.current);
50192
+ if (Number.isFinite(trendCycleTime) && trendCycleTime > 0) {
50193
+ return Math.round(trendCycleTime);
50194
+ }
50195
+ return metrics2?.avgCycleTime ?? 0;
50196
+ }, [trendSummary?.avg_cycle_time?.current, metrics2?.avgCycleTime]);
49923
50197
  const cycleDeltaRaw = trendSummary?.avg_cycle_time?.delta_seconds ?? 0;
49924
50198
  const cyclePrev = trendSummary?.avg_cycle_time?.previous ?? 0;
49925
50199
  const cycleDelta = cyclePrev ? cycleDeltaRaw / cyclePrev * 100 : 0;
@@ -50105,7 +50379,7 @@ var WorkspaceMonthlyHistory = ({
50105
50379
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-semibold text-gray-600 mb-1", children: "Avg Cycle Time" }),
50106
50380
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-nowrap", children: [
50107
50381
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl font-bold text-gray-900", children: [
50108
- metrics2?.avgCycleTime ?? 0,
50382
+ assemblyRangeCycleTime,
50109
50383
  "s"
50110
50384
  ] }),
50111
50385
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center gap-1 ${cycleWorsened ? "bg-red-50 text-red-600" : "bg-emerald-50 text-emerald-600"} px-2 py-0.5 rounded-full text-[10px] font-medium whitespace-nowrap flex-shrink-0`, children: [
@@ -50441,7 +50715,25 @@ var WorkspaceWhatsAppShareButton = ({
50441
50715
  }
50442
50716
  );
50443
50717
  };
50444
- var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiencyLegend, hourlyCycleTimes }) => {
50718
+ var formatOperationalDateKey2 = (dateKey, options) => {
50719
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50720
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
50721
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
50722
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
50723
+ return new Date(Date.UTC(year, month - 1, day, 12)).toLocaleDateString("en-IN", {
50724
+ ...options,
50725
+ timeZone: "UTC"
50726
+ });
50727
+ };
50728
+ var WorkspacePdfGenerator = ({
50729
+ workspace,
50730
+ className,
50731
+ idleTimeReasons,
50732
+ efficiencyLegend,
50733
+ hourlyCycleTimes,
50734
+ shiftBreaks = [],
50735
+ reportTimezone = "Asia/Kolkata"
50736
+ }) => {
50445
50737
  const [isGenerating, setIsGenerating] = React143.useState(false);
50446
50738
  const entityConfig = useEntityConfig();
50447
50739
  const effectiveLegend = efficiencyLegend || DEFAULT_EFFICIENCY_LEGEND;
@@ -50471,7 +50763,7 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50471
50763
  doc.setFontSize(9);
50472
50764
  doc.setFont("helvetica", "normal");
50473
50765
  doc.setTextColor(100, 100, 100);
50474
- const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
50766
+ const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: reportTimezone })}`;
50475
50767
  const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
50476
50768
  doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
50477
50769
  doc.setDrawColor(200, 200, 200);
@@ -50490,11 +50782,10 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50490
50782
  doc.setFontSize(13);
50491
50783
  doc.setFont("helvetica", "normal");
50492
50784
  doc.setTextColor(60, 60, 60);
50493
- const date = new Date(workspace.date).toLocaleDateString("en-IN", {
50785
+ const date = formatOperationalDateKey2(workspace.date, {
50494
50786
  weekday: "long",
50495
50787
  day: "numeric",
50496
- month: "long",
50497
- timeZone: "Asia/Kolkata"
50788
+ month: "long"
50498
50789
  });
50499
50790
  const rawShiftType = workspace.shift_type || (workspace.shift_id === 0 ? "Day" : workspace.shift_id === 1 ? "Night" : `Shift ${workspace.shift_id}`);
50500
50791
  const shiftType = rawShiftType.toLowerCase().includes("shift") ? rawShiftType : `${rawShiftType} Shift`;
@@ -50503,7 +50794,7 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50503
50794
  const currentTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-IN", {
50504
50795
  hour: "2-digit",
50505
50796
  minute: "2-digit",
50506
- timeZone: "Asia/Kolkata"
50797
+ timeZone: reportTimezone
50507
50798
  });
50508
50799
  const shiftStartTime = (/* @__PURE__ */ new Date(`2000-01-01 ${workspace.shift_start}`)).toLocaleTimeString("en-IN", {
50509
50800
  hour: "2-digit",
@@ -50515,29 +50806,12 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50515
50806
  minute: "2-digit",
50516
50807
  hour12: true
50517
50808
  });
50518
- const parseTimeToMinutes4 = (timeValue) => {
50519
- const [hourPart, minutePart] = timeValue.split(":").map(Number);
50520
- const hour = Number.isFinite(hourPart) ? hourPart : 0;
50521
- const minute = Number.isFinite(minutePart) ? minutePart : 0;
50522
- return hour * 60 + minute;
50523
- };
50524
- const toShiftUtcMs = (dateKey, timeValue) => {
50525
- const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50526
- const year = Number.isFinite(yearPart) ? yearPart : 1970;
50527
- const month = Number.isFinite(monthPart) ? monthPart : 1;
50528
- const day = Number.isFinite(dayPart) ? dayPart : 1;
50529
- const [hourPart, minutePart] = timeValue.split(":").map(Number);
50530
- const hour = Number.isFinite(hourPart) ? hourPart : 0;
50531
- const minute = Number.isFinite(minutePart) ? minutePart : 0;
50532
- const IST_OFFSET_MINUTES = 330;
50533
- return Date.UTC(year, month - 1, day, hour, minute) - IST_OFFSET_MINUTES * 60 * 1e3;
50534
- };
50535
- const shiftStartMinutes = parseTimeToMinutes4(workspace.shift_start);
50536
- const shiftEndMinutes = parseTimeToMinutes4(workspace.shift_end);
50537
- const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
50538
- const shiftStartUtcMs = toShiftUtcMs(workspace.date, workspace.shift_start);
50539
- const shiftEndUtcMs = toShiftUtcMs(workspace.date, workspace.shift_end) + (wrapsMidnight ? 24 * 60 * 60 * 1e3 : 0);
50540
- const isShiftInProgress = Date.now() >= shiftStartUtcMs && Date.now() < shiftEndUtcMs;
50809
+ const isShiftInProgress = isShiftInProgressForReportDate({
50810
+ reportDate: workspace.date,
50811
+ shiftStart: workspace.shift_start,
50812
+ shiftEnd: workspace.shift_end,
50813
+ timeZone: reportTimezone
50814
+ });
50541
50815
  const reportPeriodEndTime = isShiftInProgress ? currentTime : shiftEndTime;
50542
50816
  doc.setFontSize(12);
50543
50817
  doc.setTextColor(80, 80, 80);
@@ -50643,7 +50917,7 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50643
50917
  shiftStart: workspace.shift_start,
50644
50918
  shiftEnd: workspace.shift_end,
50645
50919
  shiftDate: workspace.date,
50646
- timezone: "Asia/Kolkata"
50920
+ timezone: reportTimezone
50647
50921
  }) : null;
50648
50922
  const hourlyUptime = uptimeSeries?.points?.length ? Array.from({ length: Math.ceil(uptimeSeries.shiftMinutes / 60) }, (_, index) => {
50649
50923
  const start = index * 60;
@@ -50658,6 +50932,31 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50658
50932
  const hourlyData = isUptimeMode ? hourlyUptime : isAssemblyCycleMode ? hourlyCycleTimes && hourlyCycleTimes.length > 0 ? hourlyCycleTimes : workspace.hourly_action_counts || [] : workspace.hourly_action_counts || [];
50659
50933
  const hourlyTarget = workspace.pph_threshold;
50660
50934
  const cycleTarget = workspace.ideal_cycle_time || 0;
50935
+ const hourlyIntervals = buildHourlyIntervals({
50936
+ shiftStart: workspace.shift_start,
50937
+ shiftEnd: workspace.shift_end,
50938
+ fallbackHours: Math.max(hourlyData.length, 1)
50939
+ });
50940
+ const outputTargetPlan = !isUptimeMode && !isAssemblyCycleMode ? buildHourlyTargetPlan({
50941
+ shiftStart: workspace.shift_start,
50942
+ shiftEnd: workspace.shift_end,
50943
+ breaks: shiftBreaks,
50944
+ pphThreshold: workspace.pph_threshold,
50945
+ fallbackHours: Math.max(hourlyData.length, 1),
50946
+ rounding: "floor"
50947
+ }) : null;
50948
+ const formatIntervalLabel = (totalMinutes) => {
50949
+ const normalized = (totalMinutes % (24 * 60) + 24 * 60) % (24 * 60);
50950
+ const hour = Math.floor(normalized / 60);
50951
+ const minute = normalized % 60;
50952
+ const time2 = new Date(Date.UTC(2e3, 0, 1, hour, minute));
50953
+ return time2.toLocaleTimeString("en-IN", {
50954
+ hour: "numeric",
50955
+ ...minute > 0 ? { minute: "2-digit" } : {},
50956
+ hour12: true,
50957
+ timeZone: "UTC"
50958
+ });
50959
+ };
50661
50960
  const pageHeight = doc.internal.pageSize.height;
50662
50961
  const maxContentY = pageHeight - 15;
50663
50962
  const baseTableStartY = hourlyPerfStartY + 31;
@@ -50725,36 +51024,30 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50725
51024
  doc.setFontSize(contentFontSize);
50726
51025
  doc.setFont("helvetica", "normal");
50727
51026
  let yPos = headerBottomY + 5.5;
50728
- const workspaceDate = new Date(workspace.date);
50729
- const today = /* @__PURE__ */ new Date();
50730
- today.setHours(0, 0, 0, 0);
50731
- workspaceDate.setHours(0, 0, 0, 0);
50732
- const isToday2 = workspaceDate.getTime() === today.getTime();
51027
+ const isToday2 = getDateKeyInTimeZone(reportTimezone) === workspace.date;
50733
51028
  let currentHour = 24;
50734
51029
  if (isToday2) {
50735
51030
  const now4 = /* @__PURE__ */ new Date();
50736
- const currentTimeIST = new Date(now4.toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
50737
- currentHour = currentTimeIST.getHours();
51031
+ const currentTimeInReportTimezone = new Date(now4.toLocaleString("en-US", { timeZone: reportTimezone }));
51032
+ currentHour = currentTimeInReportTimezone.getHours();
50738
51033
  }
50739
51034
  hourlyData.forEach((entry, index) => {
50740
- const startTime = /* @__PURE__ */ new Date(`2000-01-01 ${workspace.shift_start}`);
50741
- startTime.setHours(startTime.getHours() + index);
50742
- const endTime = new Date(startTime);
50743
- endTime.setHours(endTime.getHours() + 1);
50744
- const timeRange = `${startTime.toLocaleTimeString("en-IN", {
50745
- hour: "numeric",
50746
- hour12: true
50747
- })} - ${endTime.toLocaleTimeString("en-IN", {
50748
- hour: "numeric",
50749
- hour12: true
50750
- })}`;
50751
- const hourNumber = startTime.getHours();
50752
- const dataCollected = !isToday2 || hourNumber < currentHour;
51035
+ const interval = hourlyIntervals[index];
51036
+ const timeRange = interval ? `${formatIntervalLabel(interval.start)} - ${formatIntervalLabel(interval.end)}` : `${index + 1}`;
51037
+ const dataCollected = interval ? isHourlyIntervalComplete({
51038
+ reportDate: workspace.date,
51039
+ shiftStart: workspace.shift_start,
51040
+ shiftEnd: workspace.shift_end,
51041
+ interval,
51042
+ timeZone: reportTimezone
51043
+ }) : !isToday2 || currentHour >= 24;
50753
51044
  const outputValue = isUptimeMode ? entry.activeMinutes ?? 0 : entry;
50754
51045
  const idleValue = isUptimeMode ? entry.idleMinutes ?? 0 : 0;
50755
51046
  const uptimePercent = isUptimeMode ? entry.uptimePercent ?? 0 : 0;
50756
51047
  const outputStr = dataCollected ? outputValue.toString() : "TBD";
50757
- const targetStr = isUptimeMode ? dataCollected ? idleValue.toString() : "TBD" : hourlyTarget.toString();
51048
+ const effectiveTarget = outputTargetPlan?.targets[index] ?? hourlyTarget;
51049
+ const remarkText = outputTargetPlan?.breakRemarks[index] || "";
51050
+ const targetStr = isUptimeMode ? dataCollected ? idleValue.toString() : "TBD" : effectiveTarget.toString();
50758
51051
  if (index < totalRows - 1) {
50759
51052
  const rowBottomY = headerBottomY + (index + 1) * rowHeight;
50760
51053
  doc.setDrawColor(200, 200, 200);
@@ -50786,10 +51079,12 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50786
51079
  } else {
50787
51080
  doc.text(outputStr, 75, yPos);
50788
51081
  doc.text(targetStr, 105, yPos);
51082
+ const remarkDisplay = remarkText ? doc.splitTextToSize(remarkText, 26)[0] : "";
51083
+ doc.text(remarkDisplay, 160, yPos);
50789
51084
  if (!dataCollected) {
50790
51085
  doc.setTextColor(100, 100, 100);
50791
51086
  doc.text("-", 135, yPos);
50792
- } else if (outputValue >= hourlyTarget) {
51087
+ } else if (outputValue >= effectiveTarget) {
50793
51088
  doc.setTextColor(0, 171, 69);
50794
51089
  doc.setFont("ZapfDingbats", "normal");
50795
51090
  doc.text("4", 135, yPos);
@@ -50803,11 +51098,10 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50803
51098
  yPos += rowHeight;
50804
51099
  });
50805
51100
  const workspaceDisplayName = getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
50806
- const fileDate = new Date(workspace.date).toLocaleDateString("en-IN", {
51101
+ const fileDate = formatOperationalDateKey2(workspace.date, {
50807
51102
  day: "2-digit",
50808
51103
  month: "short",
50809
- year: "numeric",
50810
- timeZone: "Asia/Kolkata"
51104
+ year: "numeric"
50811
51105
  }).replace(/ /g, "_");
50812
51106
  const fileShift = shiftType.replace(/ /g, "_");
50813
51107
  const fileName = `${workspaceDisplayName}_${fileDate}_${fileShift}.pdf`;
@@ -50853,12 +51147,25 @@ var WorkspaceMonthlyPdfGenerator = ({
50853
51147
  availableShifts,
50854
51148
  shiftConfig,
50855
51149
  efficiencyLegend,
51150
+ trendSummary,
50856
51151
  className,
50857
51152
  compact = false,
50858
51153
  isAssemblyWorkspace = false
50859
51154
  }) => {
50860
51155
  const [isGenerating, setIsGenerating] = React143.useState(false);
50861
51156
  const effectiveLegend = efficiencyLegend || DEFAULT_EFFICIENCY_LEGEND;
51157
+ const drawStatusMark = (doc, x, y, met) => {
51158
+ doc.setLineWidth(0.8);
51159
+ if (met) {
51160
+ doc.setDrawColor(0, 171, 69);
51161
+ doc.line(x - 1.8, y - 0.2, x - 0.5, y + 1.2);
51162
+ doc.line(x - 0.5, y + 1.2, x + 2.1, y - 1.6);
51163
+ return;
51164
+ }
51165
+ doc.setDrawColor(227, 67, 41);
51166
+ doc.line(x - 1.8, y - 1.6, x + 1.8, y + 1.6);
51167
+ doc.line(x - 1.8, y + 1.6, x + 1.8, y - 1.6);
51168
+ };
50862
51169
  const generatePDF = async () => {
50863
51170
  setIsGenerating(true);
50864
51171
  try {
@@ -51053,12 +51360,13 @@ var WorkspaceMonthlyPdfGenerator = ({
51053
51360
  doc.setFont("helvetica", "bold");
51054
51361
  doc.text(`${outputMetrics.underperformingDays} of ${outputMetrics.totalDays}`, 120, kpiStartY + kpiSpacing * 3);
51055
51362
  } else {
51363
+ const medianCycleTime = Number.isFinite(trendSummary?.avg_cycle_time?.current) ? Number(trendSummary?.avg_cycle_time?.current) : outputMetrics.avgCycleTime;
51056
51364
  createKPIBox(kpiStartY);
51057
51365
  doc.setFontSize(11);
51058
51366
  doc.setFont("helvetica", "normal");
51059
51367
  doc.text("Average Cycle Time:", 25, kpiStartY);
51060
51368
  doc.setFont("helvetica", "bold");
51061
- doc.text(`${Math.round(outputMetrics.avgCycleTime)}s`, 120, kpiStartY);
51369
+ doc.text(`${medianCycleTime.toFixed(1)}s`, 120, kpiStartY);
51062
51370
  createKPIBox(kpiStartY + kpiSpacing);
51063
51371
  doc.setFont("helvetica", "normal");
51064
51372
  doc.text("Average Idle Time:", 25, kpiStartY + kpiSpacing);
@@ -51093,8 +51401,8 @@ var WorkspaceMonthlyPdfGenerator = ({
51093
51401
  doc.roundedRect(20, tableHeaderY, 170, 7, 1, 1, "F");
51094
51402
  const textY = tableHeaderY + 5;
51095
51403
  doc.text("Date", 25, textY);
51096
- doc.text(isUptimeMode ? "Productive" : isAssemblyWorkspace ? "Cycle Time" : "Actual", 60, textY);
51097
- doc.text(isUptimeMode ? "Idle" : isAssemblyWorkspace ? "Target CT" : "Standard", 95, textY);
51404
+ doc.text(isUptimeMode ? "Productive" : isAssemblyWorkspace ? "Actual Cycle Time" : "Actual", 60, textY);
51405
+ doc.text(isUptimeMode ? "Idle" : isAssemblyWorkspace ? "Standard Cycle Time" : "Standard", 95, textY);
51098
51406
  doc.text(isUptimeMode ? "Utilization" : "Efficiency", 135, textY);
51099
51407
  doc.text("Status", 170, textY);
51100
51408
  doc.setLineWidth(0.2);
@@ -51126,31 +51434,18 @@ var WorkspaceMonthlyPdfGenerator = ({
51126
51434
  doc.text(formatIdleTime(productiveSeconds), 60, yPos);
51127
51435
  doc.text(formatIdleTime(clampedIdleSeconds), 95, yPos);
51128
51436
  doc.text(`${utilization}%`, 135, yPos);
51129
- if (utilization >= effectiveLegend.green_min) {
51130
- doc.setTextColor(0, 171, 69);
51131
- doc.text("\u2713", 170, yPos);
51132
- } else {
51133
- doc.setTextColor(227, 67, 41);
51134
- doc.text("\xD7", 170, yPos);
51135
- }
51136
- doc.setTextColor(0, 0, 0);
51437
+ drawStatusMark(doc, 171, yPos - 0.3, utilization >= effectiveLegend.green_min);
51137
51438
  } else {
51138
51439
  if (isAssemblyWorkspace) {
51440
+ const targetCycleTime = Number.isFinite(shift.idealCycleTime) && Number(shift.idealCycleTime) > 0 ? Number(shift.idealCycleTime) : shift.pphThreshold > 0 ? 3600 / shift.pphThreshold : null;
51139
51441
  doc.text(`${shift.cycleTime.toFixed(1)}`, 60, yPos);
51140
- doc.text(`${shift.pphThreshold > 0 ? (3600 / shift.pphThreshold).toFixed(1) : "-"}`, 95, yPos);
51442
+ doc.text(targetCycleTime !== null ? `${targetCycleTime.toFixed(1)}` : "-", 95, yPos);
51141
51443
  } else {
51142
51444
  doc.text(`${shift.output}`, 60, yPos);
51143
51445
  doc.text(`${shift.targetOutput}`, 95, yPos);
51144
51446
  }
51145
51447
  doc.text(`${shift.efficiency.toFixed(1)}%`, 135, yPos);
51146
- if (shift.efficiency >= effectiveLegend.green_min) {
51147
- doc.setTextColor(0, 171, 69);
51148
- doc.text("\u2713", 170, yPos);
51149
- } else {
51150
- doc.setTextColor(227, 67, 41);
51151
- doc.text("\xD7", 170, yPos);
51152
- }
51153
- doc.setTextColor(0, 0, 0);
51448
+ drawStatusMark(doc, 171, yPos - 0.3, shift.efficiency >= effectiveLegend.green_min);
51154
51449
  }
51155
51450
  yPos += 8;
51156
51451
  });
@@ -64316,7 +64611,17 @@ var KPIDetailView = ({
64316
64611
  )
64317
64612
  ] }),
64318
64613
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 ml-auto", children: [
64319
- resolvedLineInfo && activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx(LinePdfGenerator, { lineInfo: resolvedLineInfo, workspaceData: resolvedWorkspaces || [], issueResolutionSummary, shiftName: getShiftName(resolvedLineInfo.shift_id) }),
64614
+ resolvedLineInfo && activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx(
64615
+ LinePdfGenerator,
64616
+ {
64617
+ lineInfo: resolvedLineInfo,
64618
+ workspaceData: resolvedWorkspaces || [],
64619
+ issueResolutionSummary,
64620
+ shiftName: getShiftName(resolvedLineInfo.shift_id),
64621
+ shiftBreaks: shiftConfig?.shifts?.find((shift) => shift.shiftId === resolvedLineInfo.shift_id)?.breaks || [],
64622
+ reportTimezone: shiftConfig?.timezone || configuredTimezone
64623
+ }
64624
+ ),
64320
64625
  activeTab === "monthly_history" && !urlDate && !urlShift && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
64321
64626
  /* @__PURE__ */ jsxRuntime.jsx(
64322
64627
  MonthlyRangeFilter_default,
@@ -64345,6 +64650,8 @@ var KPIDetailView = ({
64345
64650
  rangeStart,
64346
64651
  rangeEnd,
64347
64652
  selectedShiftId,
64653
+ availableShifts: shiftConfig?.shifts?.map((shift) => ({ id: shift.shiftId, name: shift.shiftName })),
64654
+ lineAssembly: resolvedLineInfo?.assembly === true,
64348
64655
  compact: true
64349
64656
  }
64350
64657
  )
@@ -64523,7 +64830,7 @@ var KPIDetailViewWithDisplayNames = withSelectedLineDisplayNames(KPIDetailView);
64523
64830
  var KPIDetailView_default = KPIDetailViewWithDisplayNames;
64524
64831
  var isNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0;
64525
64832
  var resolveCompanyId = (...candidates) => candidates.find(isNonEmptyString);
64526
- var parseTimeToMinutes2 = (value) => {
64833
+ var parseTimeToMinutes3 = (value) => {
64527
64834
  if (!value) return null;
64528
64835
  const [hourStr, minuteStr] = value.split(":");
64529
64836
  const hour = Number.parseInt(hourStr ?? "", 10);
@@ -64535,8 +64842,8 @@ var getShiftEndDate = (shift, timezone) => {
64535
64842
  if (!shift?.date) return null;
64536
64843
  const startTime = shift.startTime || "06:00";
64537
64844
  const endTime = shift.endTime || "18:00";
64538
- const startMinutes = parseTimeToMinutes2(startTime);
64539
- const endMinutes = parseTimeToMinutes2(endTime);
64845
+ const startMinutes = parseTimeToMinutes3(startTime);
64846
+ const endMinutes = parseTimeToMinutes3(endTime);
64540
64847
  if (startMinutes === null || endMinutes === null) return null;
64541
64848
  const shiftStartDate = dateFnsTz.fromZonedTime(`${shift.date}T${startTime}:00`, timezone);
64542
64849
  let durationMinutes = endMinutes - startMinutes;
@@ -64574,7 +64881,7 @@ var createKpisOverviewUrl = ({
64574
64881
  return queryString ? `/kpis?${queryString}` : "/kpis";
64575
64882
  };
64576
64883
  var getZonedDateAtMidday = (dateKey, timezone) => dateFnsTz.fromZonedTime(`${dateKey}T12:00:00`, timezone);
64577
- var formatDateKey = (dateKey, timezone, options) => {
64884
+ var formatDateKey2 = (dateKey, timezone, options) => {
64578
64885
  try {
64579
64886
  return new Intl.DateTimeFormat("en-US", {
64580
64887
  ...options,
@@ -65642,7 +65949,7 @@ var KPIsOverviewView = ({
65642
65949
  setActiveTab(newTab);
65643
65950
  }, [activeTab, leaderboardLines.length, lines.length]);
65644
65951
  const formatLocalDate2 = React143.useCallback((dateKey) => {
65645
- return formatDateKey(dateKey, configuredTimezone, {
65952
+ return formatDateKey2(dateKey, configuredTimezone, {
65646
65953
  year: "numeric",
65647
65954
  month: "long",
65648
65955
  day: "numeric"
@@ -65656,8 +65963,8 @@ var KPIsOverviewView = ({
65656
65963
  zonedNow.getMonth(),
65657
65964
  new Date(zonedNow.getFullYear(), zonedNow.getMonth() + 1, 0).getDate()
65658
65965
  );
65659
- const startLabel = formatDateKey(startOfMonthKey, configuredTimezone, { month: "short", day: "numeric" });
65660
- const endLabel = formatDateKey(endOfMonthKey, configuredTimezone, { month: "short", day: "numeric" });
65966
+ const startLabel = formatDateKey2(startOfMonthKey, configuredTimezone, { month: "short", day: "numeric" });
65967
+ const endLabel = formatDateKey2(endOfMonthKey, configuredTimezone, { month: "short", day: "numeric" });
65661
65968
  return `${startLabel} - ${endLabel}, ${zonedNow.getFullYear()}`;
65662
65969
  };
65663
65970
  const isMonthlyMode = activeTab === "leaderboard" && timeRange === "monthly";
@@ -71292,6 +71599,7 @@ var WorkspaceDetailView = ({
71292
71599
  efficiency: monitoringMode === "uptime" ? Number.isFinite(metric.avg_efficiency) ? Number(metric.avg_efficiency) : computedUptimeEfficiency : Number.isFinite(metric.avg_efficiency) ? Number(metric.avg_efficiency) : 0,
71293
71600
  output: metric.total_output || 0,
71294
71601
  cycleTime: metric.avg_cycle_time || 0,
71602
+ idealCycleTime: Number(metric.ideal_cycle_time || 0),
71295
71603
  pph: metric.avg_pph || 0,
71296
71604
  pphThreshold: metric.pph_threshold || 0,
71297
71605
  idealOutput: Number(metric.ideal_output || 0),
@@ -71847,7 +72155,9 @@ var WorkspaceDetailView = ({
71847
72155
  workspace,
71848
72156
  idleTimeReasons: idleTimeChartData,
71849
72157
  efficiencyLegend,
71850
- hourlyCycleTimes: cycleTimeChartData
72158
+ hourlyCycleTimes: cycleTimeChartData,
72159
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
72160
+ reportTimezone: shiftConfig?.timezone || timezone
71851
72161
  }
71852
72162
  ) }),
71853
72163
  activeTab === "monthly_history" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
@@ -71882,6 +72192,7 @@ var WorkspaceDetailView = ({
71882
72192
  workspaceId,
71883
72193
  workspaceName: formattedWorkspaceName,
71884
72194
  monthlyData,
72195
+ analysisData: analysisMonthlyData,
71885
72196
  selectedMonth,
71886
72197
  selectedYear,
71887
72198
  monitoringMode: workspace?.monitoring_mode,
@@ -71890,7 +72201,10 @@ var WorkspaceDetailView = ({
71890
72201
  selectedShiftId: selectedShift,
71891
72202
  availableShifts: shiftConfig?.shifts?.map((s) => ({ id: s.shiftId, name: s.shiftName })),
71892
72203
  shiftConfig,
71893
- compact: true
72204
+ efficiencyLegend,
72205
+ trendSummary: workspaceMonthlyTrend,
72206
+ compact: true,
72207
+ isAssemblyWorkspace
71894
72208
  }
71895
72209
  )
71896
72210
  ] })
@@ -80105,7 +80419,7 @@ var useOperationsOverviewRefresh = ({
80105
80419
  };
80106
80420
  }, [companyId, enabled, getIsPageActive, isLiveScope, lineIds.length, refreshFromResume, startPolling, stopPolling, supabase]);
80107
80421
  };
80108
- var parseTimeToMinutes3 = (value) => {
80422
+ var parseTimeToMinutes4 = (value) => {
80109
80423
  if (!value) return null;
80110
80424
  const parts = value.split(":");
80111
80425
  if (parts.length < 2) return null;
@@ -80140,8 +80454,8 @@ var classifyShiftBucket = ({
80140
80454
  return "day";
80141
80455
  }
80142
80456
  }
80143
- const startMinutes = parseTimeToMinutes3(startTime);
80144
- const endMinutes = parseTimeToMinutes3(endTime);
80457
+ const startMinutes = parseTimeToMinutes4(startTime);
80458
+ const endMinutes = parseTimeToMinutes4(endTime);
80145
80459
  if (startMinutes !== null) {
80146
80460
  if (startMinutes >= 4 * 60 && startMinutes < 18 * 60) return "day";
80147
80461
  return "night";
@@ -80200,8 +80514,8 @@ var getShiftWindowsForConfig = (shiftConfig, timezone) => {
80200
80514
  ];
80201
80515
  };
80202
80516
  var normalizeShiftWindowMinutes = (startTime, endTime) => {
80203
- const startMinutes = parseTimeToMinutes3(startTime);
80204
- const endMinutesRaw = parseTimeToMinutes3(endTime);
80517
+ const startMinutes = parseTimeToMinutes4(startTime);
80518
+ const endMinutesRaw = parseTimeToMinutes4(endTime);
80205
80519
  if (startMinutes === null || endMinutesRaw === null) {
80206
80520
  return null;
80207
80521
  }
@@ -80367,7 +80681,7 @@ var PlantHeadView = () => {
80367
80681
  startTime: shift.startTime,
80368
80682
  endTime: shift.endTime
80369
80683
  });
80370
- const startMinutes = parseTimeToMinutes3(shift.startTime);
80684
+ const startMinutes = parseTimeToMinutes4(shift.startTime);
80371
80685
  if (bucket === "day" && startMinutes !== null) {
80372
80686
  candidateStarts.push(startMinutes);
80373
80687
  }
@@ -80377,7 +80691,7 @@ var PlantHeadView = () => {
80377
80691
  scopedLineIds.forEach((lineId) => {
80378
80692
  const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
80379
80693
  getShiftWindowsForConfig(shiftConfig).forEach((shift) => {
80380
- const startMinutes = parseTimeToMinutes3(shift.startTime);
80694
+ const startMinutes = parseTimeToMinutes4(shift.startTime);
80381
80695
  if (startMinutes !== null) {
80382
80696
  candidateStarts.push(startMinutes);
80383
80697
  }
@@ -80468,7 +80782,7 @@ var PlantHeadView = () => {
80468
80782
  startTime: shift.startTime,
80469
80783
  endTime: shift.endTime
80470
80784
  });
80471
- return bucket === "day" ? parseTimeToMinutes3(shift.startTime) : null;
80785
+ return bucket === "day" ? parseTimeToMinutes4(shift.startTime) : null;
80472
80786
  }).filter((value) => value !== null);
80473
80787
  }) : [];
80474
80788
  if (dayStartCandidates.length > 0) {