@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.mjs CHANGED
@@ -1638,6 +1638,7 @@ var DEFAULT_SHIFT_DATA = {
1638
1638
  efficiency: 0,
1639
1639
  output: 0,
1640
1640
  cycleTime: 0,
1641
+ idealCycleTime: 0,
1641
1642
  pph: 0,
1642
1643
  pphThreshold: 0,
1643
1644
  idealOutput: 0,
@@ -4484,6 +4485,7 @@ var dashboardService = {
4484
4485
  avg_efficiency: item.avg_efficiency || 0,
4485
4486
  total_output: item.total_output || 0,
4486
4487
  avg_cycle_time: item.avg_cycle_time || 0,
4488
+ ideal_cycle_time: item.ideal_cycle_time ?? 0,
4487
4489
  ideal_output: item.ideal_output || 0,
4488
4490
  total_day_output: item.total_day_output ?? item.ideal_output ?? 0,
4489
4491
  avg_pph: item.avg_pph || 0,
@@ -17280,7 +17282,7 @@ var useActiveBreaks = (lineIds) => {
17280
17282
  const [isLoading, setIsLoading] = useState(true);
17281
17283
  const [error, setError] = useState(null);
17282
17284
  const supabase = useSupabase();
17283
- const parseTimeToMinutes4 = (timeStr) => {
17285
+ const parseTimeToMinutes5 = (timeStr) => {
17284
17286
  const [hours, minutes] = timeStr.split(":").map(Number);
17285
17287
  return hours * 60 + minutes;
17286
17288
  };
@@ -17289,8 +17291,8 @@ var useActiveBreaks = (lineIds) => {
17289
17291
  return now4.getHours() * 60 + now4.getMinutes();
17290
17292
  };
17291
17293
  const isTimeInBreak = (breakStart, breakEnd, currentMinutes) => {
17292
- const startMinutes = parseTimeToMinutes4(breakStart);
17293
- const endMinutes = parseTimeToMinutes4(breakEnd);
17294
+ const startMinutes = parseTimeToMinutes5(breakStart);
17295
+ const endMinutes = parseTimeToMinutes5(breakEnd);
17294
17296
  if (endMinutes < startMinutes) {
17295
17297
  return currentMinutes >= startMinutes || currentMinutes < endMinutes;
17296
17298
  } else {
@@ -17298,8 +17300,8 @@ var useActiveBreaks = (lineIds) => {
17298
17300
  }
17299
17301
  };
17300
17302
  const calculateBreakProgress = (breakStart, breakEnd, currentMinutes) => {
17301
- const startMinutes = parseTimeToMinutes4(breakStart);
17302
- const endMinutes = parseTimeToMinutes4(breakEnd);
17303
+ const startMinutes = parseTimeToMinutes5(breakStart);
17304
+ const endMinutes = parseTimeToMinutes5(breakEnd);
17303
17305
  let elapsedMinutes = 0;
17304
17306
  let remainingMinutes = 0;
17305
17307
  if (endMinutes < startMinutes) {
@@ -17317,8 +17319,8 @@ var useActiveBreaks = (lineIds) => {
17317
17319
  return { elapsedMinutes, remainingMinutes };
17318
17320
  };
17319
17321
  const isTimeInShift = (startTime, endTime, currentMinutes) => {
17320
- const startMinutes = parseTimeToMinutes4(startTime);
17321
- const endMinutes = parseTimeToMinutes4(endTime);
17322
+ const startMinutes = parseTimeToMinutes5(startTime);
17323
+ const endMinutes = parseTimeToMinutes5(endTime);
17322
17324
  if (endMinutes < startMinutes) {
17323
17325
  return currentMinutes >= startMinutes || currentMinutes < endMinutes;
17324
17326
  } else {
@@ -17378,8 +17380,8 @@ var useActiveBreaks = (lineIds) => {
17378
17380
  const endTime = breakItem.end || breakItem.endTime || "00:00";
17379
17381
  let duration = breakItem.duration || 0;
17380
17382
  if (!duration || duration === 0) {
17381
- const startMinutes = parseTimeToMinutes4(startTime);
17382
- const endMinutes = parseTimeToMinutes4(endTime);
17383
+ const startMinutes = parseTimeToMinutes5(startTime);
17384
+ const endMinutes = parseTimeToMinutes5(endTime);
17383
17385
  duration = endMinutes < startMinutes ? endMinutes + 24 * 60 - startMinutes : endMinutes - startMinutes;
17384
17386
  }
17385
17387
  return {
@@ -17395,8 +17397,8 @@ var useActiveBreaks = (lineIds) => {
17395
17397
  const endTime = breakItem.end || breakItem.endTime || "00:00";
17396
17398
  let duration = breakItem.duration || 0;
17397
17399
  if (!duration || duration === 0) {
17398
- const startMinutes = parseTimeToMinutes4(startTime);
17399
- const endMinutes = parseTimeToMinutes4(endTime);
17400
+ const startMinutes = parseTimeToMinutes5(startTime);
17401
+ const endMinutes = parseTimeToMinutes5(endTime);
17400
17402
  duration = endMinutes < startMinutes ? endMinutes + 24 * 60 - startMinutes : endMinutes - startMinutes;
17401
17403
  }
17402
17404
  return {
@@ -33411,13 +33413,13 @@ var CycleTimeOverTimeChart = ({
33411
33413
  observer.observe(containerRef.current);
33412
33414
  return () => observer.disconnect();
33413
33415
  }, []);
33414
- const parseTimeToMinutes4 = (value) => {
33416
+ const parseTimeToMinutes5 = (value) => {
33415
33417
  const [hours, minutes] = value.split(":").map(Number);
33416
33418
  if (!Number.isFinite(hours) || !Number.isFinite(minutes)) return 0;
33417
33419
  return hours * 60 + minutes;
33418
33420
  };
33419
33421
  const formatHourLabel = (slotIndex) => {
33420
- const baseMinutes = parseTimeToMinutes4(shiftStart);
33422
+ const baseMinutes = parseTimeToMinutes5(shiftStart);
33421
33423
  const absoluteMinutes = baseMinutes + slotIndex * 60;
33422
33424
  const hour24 = Math.floor(absoluteMinutes % (24 * 60) / 60);
33423
33425
  const ampm = hour24 >= 12 ? "PM" : "AM";
@@ -37418,6 +37420,14 @@ var formatTimestampRange = (startTime, endTime, timezone) => {
37418
37420
  });
37419
37421
  return `${startFormatted} - ${endFormatted}`;
37420
37422
  };
37423
+ var buildPrefetchedExplorerMetadata = (activeFilter, metadataCategoryId, categoryMetadata) => {
37424
+ if (!activeFilter || !metadataCategoryId || metadataCategoryId !== activeFilter || categoryMetadata.length === 0) {
37425
+ return void 0;
37426
+ }
37427
+ return {
37428
+ [activeFilter]: categoryMetadata
37429
+ };
37430
+ };
37421
37431
  var getSecondsBetweenTimestamps = (startTime, endTime) => {
37422
37432
  const startDate = parseTimestamp(startTime);
37423
37433
  const endDate = parseTimestamp(endTime);
@@ -42615,6 +42625,7 @@ var BottlenecksContent = ({
42615
42625
  const [error, setError] = useState(null);
42616
42626
  const [clipClassifications, setClipClassifications] = useState({});
42617
42627
  const [categoryMetadata, setCategoryMetadata] = useState([]);
42628
+ const [categoryMetadataCategoryId, setCategoryMetadataCategoryId] = useState(null);
42618
42629
  const [currentMetadataIndex, setCurrentMetadataIndex] = useState(0);
42619
42630
  const [metadataCache, setMetadataCache] = useState({});
42620
42631
  const invalidateMetadataCache = useCallback((categories) => {
@@ -43086,6 +43097,15 @@ var BottlenecksContent = ({
43086
43097
  const getMetadataCacheKey = useCallback((categoryId) => {
43087
43098
  return `${categoryId}-${effectiveDateString}-${effectiveShiftId}-${snapshotDateTime ?? "nosnap"}-${snapshotClipId ?? "nosnap"}`;
43088
43099
  }, [effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId]);
43100
+ const setVisibleCategoryMetadata = useCallback((categoryId, clips) => {
43101
+ if (activeFilterRef.current !== categoryId) {
43102
+ return false;
43103
+ }
43104
+ categoryMetadataRef.current = clips;
43105
+ setCategoryMetadata(clips);
43106
+ setCategoryMetadataCategoryId(clips.length > 0 ? categoryId : null);
43107
+ return true;
43108
+ }, []);
43089
43109
  const applyMetadataSnapshot = useCallback((categoryId, clips, total) => {
43090
43110
  if (!clips || clips.length === 0) {
43091
43111
  return;
@@ -43095,13 +43115,12 @@ var BottlenecksContent = ({
43095
43115
  ...prev,
43096
43116
  [cacheKey]: clips
43097
43117
  }));
43098
- categoryMetadataRef.current = clips;
43099
- setCategoryMetadata(clips);
43118
+ setVisibleCategoryMetadata(categoryId, clips);
43100
43119
  if (!isPercentileCategory(categoryId) && typeof total === "number") {
43101
43120
  currentTotalRef.current = total;
43102
43121
  setCurrentTotal(total);
43103
43122
  }
43104
- }, [getMetadataCacheKey, isPercentileCategory]);
43123
+ }, [getMetadataCacheKey, isPercentileCategory, setVisibleCategoryMetadata]);
43105
43124
  const getClipTypesForPercentileCategory = useCallback((categoryId) => {
43106
43125
  switch (categoryId) {
43107
43126
  case "fast-cycles":
@@ -43126,6 +43145,7 @@ var BottlenecksContent = ({
43126
43145
  return;
43127
43146
  }
43128
43147
  setCategoryMetadata([]);
43148
+ setCategoryMetadataCategoryId(null);
43129
43149
  categoryMetadataRef.current = [];
43130
43150
  updateActiveFilter(fallbackFilter);
43131
43151
  }, [isFastSlowClipFiltersEnabled, activeFilter, dynamicCounts, clipTypes, updateActiveFilter]);
@@ -43172,6 +43192,7 @@ var BottlenecksContent = ({
43172
43192
  }
43173
43193
  if (!isFastSlowClipFiltersEnabled && (categoryId === "fast-cycles" || categoryId === "slow-cycles")) {
43174
43194
  setCategoryMetadata([]);
43195
+ setCategoryMetadataCategoryId(null);
43175
43196
  categoryMetadataRef.current = [];
43176
43197
  return;
43177
43198
  }
@@ -43185,8 +43206,7 @@ var BottlenecksContent = ({
43185
43206
  try {
43186
43207
  if (cachedMetadata) {
43187
43208
  console.log(`[BottlenecksContent] Using cached metadata for ${categoryId}`);
43188
- setCategoryMetadata(cachedMetadata);
43189
- categoryMetadataRef.current = cachedMetadata;
43209
+ setVisibleCategoryMetadata(categoryId, cachedMetadata);
43190
43210
  if (autoLoadFirstVideo && cachedMetadata.length > 0 && s3ClipsService) {
43191
43211
  const firstClipMeta = cachedMetadata[0];
43192
43212
  try {
@@ -43300,8 +43320,7 @@ var BottlenecksContent = ({
43300
43320
  ...prev,
43301
43321
  [cacheKey]: metadataClips
43302
43322
  }));
43303
- setCategoryMetadata(metadataClips);
43304
- categoryMetadataRef.current = metadataClips;
43323
+ setVisibleCategoryMetadata(categoryId, metadataClips);
43305
43324
  console.log(`[BottlenecksContent] Loaded metadata for ${categoryId}: ${metadataClips.length} clips`);
43306
43325
  if (autoLoadFirstVideo && metadataClips.length > 0 && s3ClipsService) {
43307
43326
  const firstClipMeta = metadataClips[0];
@@ -43320,15 +43339,18 @@ var BottlenecksContent = ({
43320
43339
  }
43321
43340
  }
43322
43341
  } else {
43323
- setCategoryMetadata([]);
43324
- categoryMetadataRef.current = [];
43342
+ if (activeFilterRef.current === categoryId) {
43343
+ setCategoryMetadata([]);
43344
+ setCategoryMetadataCategoryId(null);
43345
+ categoryMetadataRef.current = [];
43346
+ }
43325
43347
  }
43326
43348
  } catch (error2) {
43327
43349
  console.error(`[BottlenecksContent] Error loading category metadata:`, error2);
43328
43350
  } finally {
43329
43351
  setIsCategoryLoading(false);
43330
43352
  }
43331
- }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, isFastSlowClipFiltersEnabled, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, supabase]);
43353
+ }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, isFastSlowClipFiltersEnabled, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, supabase, setVisibleCategoryMetadata]);
43332
43354
  useEffect(() => {
43333
43355
  if (previousFilterRef.current !== activeFilter) {
43334
43356
  console.log(`Filter changed from ${previousFilterRef.current} to ${activeFilter} - resetting to first video`);
@@ -43338,6 +43360,7 @@ var BottlenecksContent = ({
43338
43360
  setIsNavigating(false);
43339
43361
  loadingCategoryRef.current = null;
43340
43362
  setCategoryMetadata([]);
43363
+ setCategoryMetadataCategoryId(null);
43341
43364
  setCurrentMetadataIndex(0);
43342
43365
  categoryMetadataRef.current = [];
43343
43366
  currentMetadataIndexRef.current = 0;
@@ -43896,14 +43919,11 @@ var BottlenecksContent = ({
43896
43919
  }
43897
43920
  return currentPosition;
43898
43921
  }, [activeFilter, categoryMetadata.length, currentMetadataIndex, currentPosition, isPercentileCategory]);
43899
- const prefetchedExplorerMetadata = useMemo(() => {
43900
- if (!activeFilter || categoryMetadata.length === 0) {
43901
- return void 0;
43902
- }
43903
- return {
43904
- [activeFilter]: categoryMetadata
43905
- };
43906
- }, [activeFilter, categoryMetadata]);
43922
+ const prefetchedExplorerMetadata = useMemo(() => buildPrefetchedExplorerMetadata(
43923
+ activeFilter,
43924
+ categoryMetadataCategoryId,
43925
+ categoryMetadata
43926
+ ), [activeFilter, categoryMetadata, categoryMetadataCategoryId]);
43907
43927
  const classificationClipIds = useMemo(() => {
43908
43928
  if (!idleTimeVlmEnabled) {
43909
43929
  return [];
@@ -47856,6 +47876,7 @@ var LineMonthlyPdfGenerator = ({
47856
47876
  rangeEnd,
47857
47877
  selectedShiftId,
47858
47878
  availableShifts,
47879
+ lineAssembly = false,
47859
47880
  compact = false,
47860
47881
  className
47861
47882
  }) => {
@@ -48139,8 +48160,8 @@ var LineMonthlyPdfGenerator = ({
48139
48160
  timeZone: "Asia/Kolkata"
48140
48161
  });
48141
48162
  doc.text(dateStr, 25, yPos);
48142
- doc.text(`${shift.total_workspaces - shift.underperforming_workspaces}`, 60, yPos);
48143
- doc.text(`${shift.total_workspaces}`, 95, yPos);
48163
+ doc.text(`${Math.round(shift.output || 0)}`, 60, yPos);
48164
+ doc.text(`${Math.round(shift.idealOutput || 0)}`, 95, yPos);
48144
48165
  doc.text(`${shift.avg_efficiency.toFixed(1)}%`, 135, yPos);
48145
48166
  const statusColor = getEfficiencyColor(shift.avg_efficiency, effectiveLegend);
48146
48167
  if (statusColor === "green") {
@@ -48170,6 +48191,8 @@ var LineMonthlyPdfGenerator = ({
48170
48191
  doc.setTextColor(0, 0, 0);
48171
48192
  }
48172
48193
  const poorestWorkspaces = underperformingWorkspaces[selectedShiftId] || [];
48194
+ 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);
48195
+ const showCycleTimePoorestPerformers = !isUptimeMode && poorestWorkspaces.some(isCycleTimeWorkspace);
48173
48196
  if (poorestWorkspaces && poorestWorkspaces.length > 0) {
48174
48197
  doc.addPage();
48175
48198
  doc.setFontSize(14);
@@ -48195,7 +48218,11 @@ var LineMonthlyPdfGenerator = ({
48195
48218
  doc.setFillColor(245, 245, 245);
48196
48219
  doc.roundedRect(20, 45, 170, 8, 1, 1, "F");
48197
48220
  doc.text("Workspace", 25, 50);
48198
- doc.text(isUptimeMode ? "Avg Utilization" : "Avg Efficiency", 120, 50);
48221
+ doc.text(
48222
+ isUptimeMode ? "Avg Utilization" : showCycleTimePoorestPerformers ? "Cycle Time" : "Avg Efficiency",
48223
+ 120,
48224
+ 50
48225
+ );
48199
48226
  doc.text("Last 5 Days", 160, 50);
48200
48227
  doc.setLineWidth(0.2);
48201
48228
  doc.setDrawColor(220, 220, 220);
@@ -48214,7 +48241,16 @@ var LineMonthlyPdfGenerator = ({
48214
48241
  );
48215
48242
  const workspaceName = rawWorkspaceName.length > 30 ? `${rawWorkspaceName.substring(0, 27)}...` : rawWorkspaceName;
48216
48243
  doc.text(workspaceName, 25, yPos2);
48217
- doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}%`, 120, yPos2);
48244
+ if (isUptimeMode) {
48245
+ doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}%`, 120, yPos2);
48246
+ } else if (isCycleTimeWorkspace(workspace)) {
48247
+ const actualCycleTime = Number.isFinite(workspace.avg_cycle_time) ? Number(workspace.avg_cycle_time) : null;
48248
+ const targetCycleTime = Number.isFinite(workspace.ideal_cycle_time) ? Number(workspace.ideal_cycle_time) : null;
48249
+ const cycleTimeText = actualCycleTime !== null ? `${actualCycleTime.toFixed(1)}s${targetCycleTime !== null ? ` / ${targetCycleTime.toFixed(1)}s` : ""}` : "-";
48250
+ doc.text(cycleTimeText, 120, yPos2);
48251
+ } else {
48252
+ doc.text(`${(workspace.avg_efficiency || 0).toFixed(1)}%`, 120, yPos2);
48253
+ }
48218
48254
  const squareSize = 3;
48219
48255
  const squareSpacing = 1;
48220
48256
  let squareX = 160;
@@ -48309,12 +48345,279 @@ Underperforming Workspaces: ${lineInfo.metrics.underperforming_workspaces} / ${l
48309
48345
  }
48310
48346
  );
48311
48347
  };
48348
+
48349
+ // src/lib/utils/hourlyTargets.ts
48350
+ var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
48351
+ var MINUTES_PER_DAY = 24 * 60;
48352
+ var parseTimeToMinutes2 = (timeString) => {
48353
+ const normalized = stripSeconds2(timeString || "");
48354
+ if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
48355
+ const [hours, minutes] = normalized.split(":").map(Number);
48356
+ return hours * 60 + minutes;
48357
+ };
48358
+ var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
48359
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
48360
+ if (!Number.isFinite(shiftStartMinutes)) return [];
48361
+ const normalizedBreaks = [];
48362
+ for (const entry of breaks) {
48363
+ const startRaw = parseTimeToMinutes2(entry.startTime);
48364
+ const endRaw = parseTimeToMinutes2(entry.endTime);
48365
+ if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
48366
+ let start = startRaw;
48367
+ let end = endRaw;
48368
+ if (end <= start) {
48369
+ end += 24 * 60;
48370
+ }
48371
+ if (start < shiftStartMinutes) {
48372
+ start += 24 * 60;
48373
+ end += 24 * 60;
48374
+ }
48375
+ const label = entry.remarks?.trim() || "Break";
48376
+ normalizedBreaks.push({ start, end, label });
48377
+ }
48378
+ return normalizedBreaks;
48379
+ };
48380
+ var roundTarget = (value, mode) => {
48381
+ if (!Number.isFinite(value)) return 0;
48382
+ switch (mode) {
48383
+ case "floor":
48384
+ return Math.floor(value);
48385
+ case "ceil":
48386
+ return Math.ceil(value);
48387
+ case "round":
48388
+ default:
48389
+ return Math.round(value);
48390
+ }
48391
+ };
48392
+ var formatDateKey = (date) => {
48393
+ const year = date.getUTCFullYear();
48394
+ const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
48395
+ const day = `${date.getUTCDate()}`.padStart(2, "0");
48396
+ return `${year}-${month}-${day}`;
48397
+ };
48398
+ var shiftDateKey = (dateKey, deltaDays) => {
48399
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
48400
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
48401
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
48402
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
48403
+ const date = new Date(Date.UTC(year, month - 1, day));
48404
+ date.setUTCDate(date.getUTCDate() + deltaDays);
48405
+ return formatDateKey(date);
48406
+ };
48407
+ var getZonedNowSnapshot = (timeZone, now4) => {
48408
+ const formatter = new Intl.DateTimeFormat("en-US", {
48409
+ timeZone,
48410
+ year: "numeric",
48411
+ month: "2-digit",
48412
+ day: "2-digit",
48413
+ hour: "2-digit",
48414
+ minute: "2-digit",
48415
+ hourCycle: "h23"
48416
+ });
48417
+ const parts = formatter.formatToParts(now4).reduce((acc, part) => {
48418
+ if (part.type !== "literal") {
48419
+ acc[part.type] = part.value;
48420
+ }
48421
+ return acc;
48422
+ }, {});
48423
+ const year = Number(parts.year);
48424
+ const month = Number(parts.month);
48425
+ const day = Number(parts.day);
48426
+ const hour = Number(parts.hour);
48427
+ const minute = Number(parts.minute);
48428
+ return {
48429
+ dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
48430
+ minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
48431
+ };
48432
+ };
48433
+ var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
48434
+ var buildHourlyIntervals = ({
48435
+ shiftStart,
48436
+ shiftEnd,
48437
+ bucketMinutes = 60,
48438
+ fallbackHours = 11
48439
+ }) => {
48440
+ const startMinutes = parseTimeToMinutes2(shiftStart);
48441
+ if (!Number.isFinite(startMinutes)) return [];
48442
+ const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
48443
+ let totalMinutes;
48444
+ const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
48445
+ if (!Number.isFinite(endRaw)) {
48446
+ totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
48447
+ } else {
48448
+ let endMinutes = endRaw;
48449
+ if (endMinutes <= startMinutes) {
48450
+ endMinutes += 24 * 60;
48451
+ }
48452
+ totalMinutes = endMinutes - startMinutes;
48453
+ }
48454
+ if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
48455
+ const count = Math.ceil(totalMinutes / bucket);
48456
+ const shiftEndMinutes = startMinutes + totalMinutes;
48457
+ const intervals = [];
48458
+ for (let i = 0; i < count; i += 1) {
48459
+ const start = startMinutes + i * bucket;
48460
+ const end = Math.min(start + bucket, shiftEndMinutes);
48461
+ const minutes = Math.max(0, end - start);
48462
+ if (minutes <= 0) continue;
48463
+ intervals.push({ start, end, minutes });
48464
+ }
48465
+ return intervals;
48466
+ };
48467
+ var computeBreakMinutesByInterval = ({
48468
+ intervals,
48469
+ shiftStart,
48470
+ breaks
48471
+ }) => {
48472
+ if (!intervals.length || !breaks.length) return intervals.map(() => 0);
48473
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
48474
+ return intervals.map((interval) => {
48475
+ if (!normalizedBreaks.length) return 0;
48476
+ let total = 0;
48477
+ for (const brk of normalizedBreaks) {
48478
+ const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
48479
+ total += overlap;
48480
+ if (total >= interval.minutes) return interval.minutes;
48481
+ }
48482
+ return Math.min(interval.minutes, total);
48483
+ });
48484
+ };
48485
+ var computeBreakRemarksByInterval = ({
48486
+ intervals,
48487
+ shiftStart,
48488
+ breaks
48489
+ }) => {
48490
+ if (!intervals.length || !breaks.length) return intervals.map(() => "");
48491
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
48492
+ return intervals.map((interval) => {
48493
+ 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);
48494
+ return labels.join(", ");
48495
+ });
48496
+ };
48497
+ var computeEffectiveTargets = ({
48498
+ intervals,
48499
+ breakMinutes,
48500
+ pphThreshold,
48501
+ rounding = "round"
48502
+ }) => {
48503
+ return intervals.map((interval, idx) => {
48504
+ const intervalMinutes = Number(interval?.minutes) || 0;
48505
+ const breakMins = Number(breakMinutes?.[idx]) || 0;
48506
+ const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
48507
+ if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
48508
+ if (plannedWorkMinutes <= 0) return 0;
48509
+ return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
48510
+ });
48511
+ };
48512
+ var buildHourlyTargetPlan = ({
48513
+ shiftStart,
48514
+ shiftEnd,
48515
+ breaks = [],
48516
+ pphThreshold,
48517
+ bucketMinutes = 60,
48518
+ fallbackHours = 11,
48519
+ rounding = "round"
48520
+ }) => {
48521
+ const intervals = buildHourlyIntervals({
48522
+ shiftStart,
48523
+ shiftEnd,
48524
+ bucketMinutes,
48525
+ fallbackHours
48526
+ });
48527
+ const breakMinutes = computeBreakMinutesByInterval({
48528
+ intervals,
48529
+ shiftStart,
48530
+ breaks
48531
+ });
48532
+ const breakRemarks = computeBreakRemarksByInterval({
48533
+ intervals,
48534
+ shiftStart,
48535
+ breaks
48536
+ });
48537
+ const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
48538
+ const targets = computeEffectiveTargets({
48539
+ intervals,
48540
+ breakMinutes,
48541
+ pphThreshold,
48542
+ rounding
48543
+ });
48544
+ return {
48545
+ intervals,
48546
+ breakMinutes,
48547
+ breakRemarks,
48548
+ productiveMinutes,
48549
+ targets
48550
+ };
48551
+ };
48552
+ var isHourlyIntervalComplete = ({
48553
+ reportDate,
48554
+ shiftStart,
48555
+ shiftEnd,
48556
+ interval,
48557
+ timeZone = "Asia/Kolkata",
48558
+ now: now4 = /* @__PURE__ */ new Date()
48559
+ }) => {
48560
+ if (!reportDate) return true;
48561
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
48562
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
48563
+ const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
48564
+ const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
48565
+ if (reportDate === snapshot.dateKey) {
48566
+ return interval.end <= snapshot.minutesOfDay;
48567
+ }
48568
+ if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
48569
+ return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
48570
+ }
48571
+ return reportDate < snapshot.dateKey;
48572
+ };
48573
+ var isShiftInProgressForReportDate = ({
48574
+ reportDate,
48575
+ shiftStart,
48576
+ shiftEnd,
48577
+ timeZone = "Asia/Kolkata",
48578
+ now: now4 = /* @__PURE__ */ new Date()
48579
+ }) => {
48580
+ if (!reportDate || !shiftStart || !shiftEnd) return false;
48581
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
48582
+ const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
48583
+ if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
48584
+ return false;
48585
+ }
48586
+ let shiftEndMinutes = shiftEndMinutesRaw;
48587
+ const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
48588
+ if (wrapsMidnight) {
48589
+ shiftEndMinutes += MINUTES_PER_DAY;
48590
+ }
48591
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
48592
+ let currentMinutes = null;
48593
+ if (reportDate === snapshot.dateKey) {
48594
+ currentMinutes = snapshot.minutesOfDay;
48595
+ } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
48596
+ currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
48597
+ }
48598
+ if (currentMinutes === null) {
48599
+ return false;
48600
+ }
48601
+ return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
48602
+ };
48603
+ var formatOperationalDateKey = (dateKey, options) => {
48604
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
48605
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
48606
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
48607
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
48608
+ return new Date(Date.UTC(year, month - 1, day, 12)).toLocaleDateString("en-IN", {
48609
+ ...options,
48610
+ timeZone: "UTC"
48611
+ });
48612
+ };
48312
48613
  var LinePdfGenerator = ({
48313
48614
  lineInfo,
48314
48615
  workspaceData,
48315
48616
  issueResolutionSummary,
48316
48617
  shiftName,
48317
- className
48618
+ className,
48619
+ shiftBreaks = [],
48620
+ reportTimezone = "Asia/Kolkata"
48318
48621
  }) => {
48319
48622
  const [isGenerating, setIsGenerating] = useState(false);
48320
48623
  const formatResolutionDuration2 = (seconds) => {
@@ -48341,7 +48644,7 @@ var LinePdfGenerator = ({
48341
48644
  doc.setFontSize(9);
48342
48645
  doc.setFont("helvetica", "normal");
48343
48646
  doc.setTextColor(100, 100, 100);
48344
- const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
48647
+ const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: reportTimezone })}`;
48345
48648
  const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
48346
48649
  doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
48347
48650
  doc.setDrawColor(200, 200, 200);
@@ -48359,11 +48662,10 @@ var LinePdfGenerator = ({
48359
48662
  const isUptimeMode = lineInfo.monitoring_mode === "uptime";
48360
48663
  const rawShiftType = shiftName || (lineInfo.shift_id === 0 ? "Day" : lineInfo.shift_id === 1 ? "Night" : `Shift ${lineInfo.shift_id}`);
48361
48664
  const shiftType = rawShiftType.toLowerCase().includes("shift") ? rawShiftType : `${rawShiftType} Shift`;
48362
- const date = new Date(lineInfo.date).toLocaleDateString("en-IN", {
48665
+ const date = formatOperationalDateKey(lineInfo.date, {
48363
48666
  weekday: "long",
48364
48667
  day: "numeric",
48365
- month: "long",
48366
- timeZone: "Asia/Kolkata"
48668
+ month: "long"
48367
48669
  });
48368
48670
  const shiftStartTime = lineInfo.metrics.shift_start ? (/* @__PURE__ */ new Date(`2000-01-01 ${lineInfo.metrics.shift_start}`)).toLocaleTimeString("en-IN", {
48369
48671
  hour: "2-digit",
@@ -48371,24 +48673,25 @@ var LinePdfGenerator = ({
48371
48673
  hour12: true,
48372
48674
  timeZone: "Asia/Kolkata"
48373
48675
  }) : "N/A";
48374
- const isToday2 = new Date(lineInfo.date).toDateString() === (/* @__PURE__ */ new Date()).toDateString();
48375
- let reportEndTime;
48376
- if (isToday2) {
48377
- reportEndTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-IN", {
48378
- hour: "2-digit",
48379
- minute: "2-digit",
48380
- timeZone: "Asia/Kolkata"
48381
- });
48382
- } else {
48383
- reportEndTime = lineInfo.metrics.shift_end ? (/* @__PURE__ */ new Date(`2000-01-01 ${lineInfo.metrics.shift_end}`)).toLocaleTimeString("en-IN", {
48384
- hour: "2-digit",
48385
- minute: "2-digit",
48386
- hour12: true,
48387
- timeZone: "Asia/Kolkata"
48388
- }) : "N/A";
48389
- }
48676
+ const currentTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-IN", {
48677
+ hour: "2-digit",
48678
+ minute: "2-digit",
48679
+ timeZone: reportTimezone
48680
+ });
48681
+ const reportEndTime = isShiftInProgressForReportDate({
48682
+ reportDate: lineInfo.date,
48683
+ shiftStart: lineInfo.metrics.shift_start || "",
48684
+ shiftEnd: lineInfo.metrics.shift_end,
48685
+ timeZone: reportTimezone
48686
+ }) ? currentTime : lineInfo.metrics.shift_end ? (/* @__PURE__ */ new Date(`2000-01-01 ${lineInfo.metrics.shift_end}`)).toLocaleTimeString("en-IN", {
48687
+ hour: "2-digit",
48688
+ minute: "2-digit",
48689
+ hour12: true,
48690
+ timeZone: "Asia/Kolkata"
48691
+ }) : "N/A";
48390
48692
  if (isUptimeMode) {
48391
48693
  const configuredTimezone = "Asia/Kolkata";
48694
+ const effectiveUptimeTimezone = reportTimezone || configuredTimezone;
48392
48695
  const lineShiftStart = lineInfo.metrics.shift_start || "06:00";
48393
48696
  const lineShiftEnd = lineInfo.metrics.shift_end || "14:00";
48394
48697
  const shiftMinutes = getShiftDurationMinutes(lineShiftStart, lineShiftEnd) || 0;
@@ -48402,7 +48705,7 @@ var LinePdfGenerator = ({
48402
48705
  shiftStart,
48403
48706
  shiftEnd,
48404
48707
  shiftDate,
48405
- timezone: configuredTimezone
48708
+ timezone: effectiveUptimeTimezone
48406
48709
  });
48407
48710
  let activeMinutes = uptimeSeries.activeMinutes;
48408
48711
  let idleMinutes = uptimeSeries.idleMinutes;
@@ -48483,7 +48786,7 @@ var LinePdfGenerator = ({
48483
48786
  shiftStart: lineShiftStart,
48484
48787
  shiftEnd: lineShiftEnd,
48485
48788
  shiftDate: lineInfo.date,
48486
- timezone: configuredTimezone
48789
+ timezone: effectiveUptimeTimezone
48487
48790
  });
48488
48791
  hourlyData = buildHourlyFromSeries(lineUptimeSeries);
48489
48792
  }
@@ -48591,13 +48894,13 @@ var LinePdfGenerator = ({
48591
48894
  doc.setFontSize(contentFontSize);
48592
48895
  doc.setFont("helvetica", "normal");
48593
48896
  let yPos2 = headerBottomY2 + 5.5;
48594
- const now5 = /* @__PURE__ */ new Date();
48595
- const currentTimeIST2 = new Date(now5.toLocaleString("en-US", { timeZone: configuredTimezone }));
48596
- const [sYear2, sMonth2, sDay2] = lineInfo.date.split("-").map(Number);
48597
- const [sStartH2, sStartM2] = (lineShiftStart || "06:00").split(":").map(Number);
48598
- const shiftStartBase2 = new Date(sYear2, sMonth2 - 1, sDay2, sStartH2, sStartM2 || 0);
48897
+ const now4 = /* @__PURE__ */ new Date();
48898
+ const currentTimeIST = new Date(now4.toLocaleString("en-US", { timeZone: configuredTimezone }));
48899
+ const [sYear, sMonth, sDay] = lineInfo.date.split("-").map(Number);
48900
+ const [sStartH, sStartM] = (lineShiftStart || "06:00").split(":").map(Number);
48901
+ const shiftStartBase = new Date(sYear, sMonth - 1, sDay, sStartH, sStartM || 0);
48599
48902
  hourlyData.forEach((entry, index) => {
48600
- const bucketStartTime = new Date(shiftStartBase2);
48903
+ const bucketStartTime = new Date(shiftStartBase);
48601
48904
  bucketStartTime.setHours(bucketStartTime.getHours() + index);
48602
48905
  const bucketEndTime = new Date(bucketStartTime);
48603
48906
  bucketEndTime.setHours(bucketEndTime.getHours() + 1);
@@ -48608,7 +48911,7 @@ var LinePdfGenerator = ({
48608
48911
  hour: "numeric",
48609
48912
  hour12: true
48610
48913
  })}`;
48611
- const dataCollected = bucketEndTime.getTime() <= currentTimeIST2.getTime();
48914
+ const dataCollected = bucketEndTime.getTime() <= currentTimeIST.getTime();
48612
48915
  if (index < totalRows2 - 1) {
48613
48916
  const rowBottomY = headerBottomY2 + (index + 1) * rowHeight;
48614
48917
  doc.setDrawColor(200, 200, 200);
@@ -48619,11 +48922,10 @@ var LinePdfGenerator = ({
48619
48922
  doc.text(utilizationStr, 147, yPos2);
48620
48923
  yPos2 += rowHeight;
48621
48924
  });
48622
- const fileDate2 = new Date(lineInfo.date).toLocaleDateString("en-IN", {
48925
+ const fileDate2 = formatOperationalDateKey(lineInfo.date, {
48623
48926
  day: "2-digit",
48624
48927
  month: "short",
48625
- year: "numeric",
48626
- timeZone: "Asia/Kolkata"
48928
+ year: "numeric"
48627
48929
  }).replace(/ /g, "_");
48628
48930
  const fileShift2 = shiftType.replace(/ /g, "_");
48629
48931
  const fileName2 = `${lineInfo.line_name}_${fileDate2}_${fileShift2}.pdf`;
@@ -48676,66 +48978,32 @@ var LinePdfGenerator = ({
48676
48978
  doc.setLineWidth(0.8);
48677
48979
  doc.line(20, 123, 190, 123);
48678
48980
  const hourlyOverviewStartY = 128;
48679
- const parseTimeToMinutes4 = (timeStr) => {
48680
- const [hours, minutes] = timeStr.split(":");
48681
- const hour = parseInt(hours, 10);
48682
- const minute = parseInt(minutes || "0", 10);
48683
- if (Number.isNaN(hour) || Number.isNaN(minute)) {
48684
- return NaN;
48685
- }
48686
- return (hour * 60 + minute) % (24 * 60);
48687
- };
48688
48981
  const formatMinutesLabel = (totalMinutes) => {
48689
48982
  const normalized = (totalMinutes % (24 * 60) + 24 * 60) % (24 * 60);
48690
48983
  const hour = Math.floor(normalized / 60);
48691
48984
  const minute = normalized % 60;
48692
- const time2 = /* @__PURE__ */ new Date();
48693
- time2.setHours(hour);
48694
- time2.setMinutes(minute);
48695
- time2.setSeconds(0);
48696
- time2.setMilliseconds(0);
48985
+ const time2 = new Date(Date.UTC(2e3, 0, 1, hour, minute));
48697
48986
  return time2.toLocaleTimeString("en-IN", {
48698
48987
  hour: "2-digit",
48699
48988
  minute: "2-digit",
48700
48989
  hour12: true,
48701
- timeZone: "Asia/Kolkata"
48990
+ timeZone: "UTC"
48702
48991
  });
48703
48992
  };
48704
- const buildRange = (startMinutes, minutes) => {
48705
- const endMinutes = startMinutes + minutes;
48706
- return {
48707
- label: `${formatMinutesLabel(startMinutes)} - ${formatMinutesLabel(endMinutes)}`,
48708
- minutes
48709
- };
48710
- };
48711
- const getHourlyTimeRanges = (startTimeStr, endTimeStr) => {
48712
- const startMinutes = parseTimeToMinutes4(startTimeStr);
48713
- if (Number.isNaN(startMinutes)) {
48714
- return [];
48715
- }
48716
- if (!endTimeStr) {
48717
- const defaultHours = 11;
48718
- return Array.from({ length: defaultHours }, (_, i) => buildRange(startMinutes + i * 60, 60));
48719
- }
48720
- const endMinutes = parseTimeToMinutes4(endTimeStr);
48721
- if (Number.isNaN(endMinutes)) {
48722
- const fallbackHours = 11;
48723
- return Array.from({ length: fallbackHours }, (_, i) => buildRange(startMinutes + i * 60, 60));
48724
- }
48725
- let durationMinutes = endMinutes - startMinutes;
48726
- if (durationMinutes <= 0) {
48727
- durationMinutes += 24 * 60;
48728
- }
48729
- const rangeCount = Math.max(1, Math.ceil(durationMinutes / 60));
48730
- return Array.from({ length: rangeCount }, (_, i) => {
48731
- const remainingMinutes = durationMinutes - i * 60;
48732
- const rangeMinutes = remainingMinutes >= 60 ? 60 : remainingMinutes;
48733
- return buildRange(startMinutes + i * 60, rangeMinutes);
48734
- });
48735
- };
48736
- const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
48993
+ const hourlyTimeRanges = buildHourlyIntervals({
48994
+ shiftStart: lineInfo.metrics.shift_start || "06:00",
48995
+ shiftEnd: lineInfo.metrics.shift_end,
48996
+ fallbackHours: 11
48997
+ });
48998
+ const targetPlan = buildHourlyTargetPlan({
48999
+ shiftStart: lineInfo.metrics.shift_start || "06:00",
49000
+ shiftEnd: lineInfo.metrics.shift_end,
49001
+ breaks: shiftBreaks,
49002
+ pphThreshold: Number(lineInfo.metrics.threshold_pph ?? 0),
49003
+ fallbackHours: Math.max(hourlyTimeRanges.length, 1),
49004
+ rounding: "floor"
49005
+ });
48737
49006
  const shiftDuration = hourlyTimeRanges.length || 11;
48738
- const targetOutputPerHour = Math.round(lineInfo.metrics.threshold_pph ?? 0);
48739
49007
  let hourlyActualOutput = [];
48740
49008
  if (lineInfo.metrics.output_hourly && Object.keys(lineInfo.metrics.output_hourly).length > 0) {
48741
49009
  const [startHourStr, startMinuteStr] = (lineInfo.metrics.shift_start || "6:00").split(":");
@@ -48921,29 +49189,29 @@ var LinePdfGenerator = ({
48921
49189
  doc.text("Remarks", 160, tableHeaderY);
48922
49190
  doc.setFont("helvetica", "normal");
48923
49191
  let yPos = tableStartY;
48924
- const now4 = /* @__PURE__ */ new Date();
48925
- const currentTimeIST = new Date(now4.toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
48926
- const [sYear, sMonth, sDay] = lineInfo.date.split("-").map(Number);
48927
- const [sStartH, sStartM] = (lineInfo.metrics.shift_start || "06:00").split(":").map(Number);
48928
- const shiftStartBase = new Date(sYear, sMonth - 1, sDay, sStartH, sStartM || 0);
48929
49192
  hourlyTimeRanges.forEach((timeRange, index) => {
48930
49193
  const actualOutput = hourlyActualOutput[index] || 0;
48931
- const bucketStartTime = new Date(shiftStartBase);
48932
- bucketStartTime.setHours(bucketStartTime.getHours() + index);
48933
- const bucketEndTime = new Date(bucketStartTime.getTime() + timeRange.minutes * 60 * 1e3);
48934
- const dataCollected = bucketEndTime.getTime() <= currentTimeIST.getTime();
49194
+ const dataCollected = isHourlyIntervalComplete({
49195
+ reportDate: lineInfo.date,
49196
+ shiftStart: lineInfo.metrics.shift_start || "06:00",
49197
+ shiftEnd: lineInfo.metrics.shift_end,
49198
+ interval: timeRange,
49199
+ timeZone: reportTimezone
49200
+ });
48935
49201
  const outputStr = dataCollected ? actualOutput.toString() : "TBD";
48936
49202
  if (index < totalRows - 1) {
48937
49203
  const rowBottomY = headerBottomY + (index + 1) * rowSpacing;
48938
49204
  doc.setDrawColor(200, 200, 200);
48939
49205
  doc.line(20, rowBottomY, 190, rowBottomY);
48940
49206
  }
48941
- const rangeMinutes = timeRange.minutes || 60;
48942
- const targetForRange = targetOutputPerHour * (rangeMinutes / 60);
48943
- const targetStr = rangeMinutes === 60 ? targetOutputPerHour.toString() : targetForRange.toFixed(1).replace(/\.0$/, "");
48944
- doc.text(timeRange.label, 25, yPos);
49207
+ const targetForRange = targetPlan.targets[index] ?? 0;
49208
+ const targetStr = targetForRange.toString();
49209
+ const remarkText = targetPlan.breakRemarks[index] || "";
49210
+ doc.text(`${formatMinutesLabel(timeRange.start)} - ${formatMinutesLabel(timeRange.end)}`, 25, yPos);
48945
49211
  doc.text(outputStr, 75, yPos);
48946
49212
  doc.text(targetStr, 105, yPos);
49213
+ const remarkDisplay = remarkText ? doc.splitTextToSize(remarkText, 26)[0] : "";
49214
+ doc.text(remarkDisplay, 160, yPos);
48947
49215
  if (!dataCollected) {
48948
49216
  doc.setTextColor(100, 100, 100);
48949
49217
  doc.text("-", 135, yPos);
@@ -48959,11 +49227,10 @@ var LinePdfGenerator = ({
48959
49227
  doc.setTextColor(0, 0, 0);
48960
49228
  yPos += rowSpacing;
48961
49229
  });
48962
- const fileDate = new Date(lineInfo.date).toLocaleDateString("en-IN", {
49230
+ const fileDate = formatOperationalDateKey(lineInfo.date, {
48963
49231
  day: "2-digit",
48964
49232
  month: "short",
48965
- year: "numeric",
48966
- timeZone: "Asia/Kolkata"
49233
+ year: "numeric"
48967
49234
  }).replace(/ /g, "_");
48968
49235
  const fileShift = shiftType.replace(/ /g, "_");
48969
49236
  const fileName = `${lineInfo.line_name}_${fileDate}_${fileShift}.pdf`;
@@ -49891,6 +50158,13 @@ var WorkspaceMonthlyHistory = ({
49891
50158
  }, [analysisMonthlyData, selectedShiftId, isUptimeMode, shiftWorkSeconds]);
49892
50159
  const efficiencyDelta = trendSummary?.avg_efficiency?.delta_pp ?? 0;
49893
50160
  const efficiencyImproved = efficiencyDelta >= 0;
50161
+ const assemblyRangeCycleTime = useMemo(() => {
50162
+ const trendCycleTime = Number(trendSummary?.avg_cycle_time?.current);
50163
+ if (Number.isFinite(trendCycleTime) && trendCycleTime > 0) {
50164
+ return Math.round(trendCycleTime);
50165
+ }
50166
+ return metrics2?.avgCycleTime ?? 0;
50167
+ }, [trendSummary?.avg_cycle_time?.current, metrics2?.avgCycleTime]);
49894
50168
  const cycleDeltaRaw = trendSummary?.avg_cycle_time?.delta_seconds ?? 0;
49895
50169
  const cyclePrev = trendSummary?.avg_cycle_time?.previous ?? 0;
49896
50170
  const cycleDelta = cyclePrev ? cycleDeltaRaw / cyclePrev * 100 : 0;
@@ -50076,7 +50350,7 @@ var WorkspaceMonthlyHistory = ({
50076
50350
  /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-gray-600 mb-1", children: "Avg Cycle Time" }),
50077
50351
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-nowrap", children: [
50078
50352
  /* @__PURE__ */ jsxs("div", { className: "text-2xl font-bold text-gray-900", children: [
50079
- metrics2?.avgCycleTime ?? 0,
50353
+ assemblyRangeCycleTime,
50080
50354
  "s"
50081
50355
  ] }),
50082
50356
  /* @__PURE__ */ 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: [
@@ -50412,7 +50686,25 @@ var WorkspaceWhatsAppShareButton = ({
50412
50686
  }
50413
50687
  );
50414
50688
  };
50415
- var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiencyLegend, hourlyCycleTimes }) => {
50689
+ var formatOperationalDateKey2 = (dateKey, options) => {
50690
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50691
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
50692
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
50693
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
50694
+ return new Date(Date.UTC(year, month - 1, day, 12)).toLocaleDateString("en-IN", {
50695
+ ...options,
50696
+ timeZone: "UTC"
50697
+ });
50698
+ };
50699
+ var WorkspacePdfGenerator = ({
50700
+ workspace,
50701
+ className,
50702
+ idleTimeReasons,
50703
+ efficiencyLegend,
50704
+ hourlyCycleTimes,
50705
+ shiftBreaks = [],
50706
+ reportTimezone = "Asia/Kolkata"
50707
+ }) => {
50416
50708
  const [isGenerating, setIsGenerating] = useState(false);
50417
50709
  const entityConfig = useEntityConfig();
50418
50710
  const effectiveLegend = efficiencyLegend || DEFAULT_EFFICIENCY_LEGEND;
@@ -50442,7 +50734,7 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50442
50734
  doc.setFontSize(9);
50443
50735
  doc.setFont("helvetica", "normal");
50444
50736
  doc.setTextColor(100, 100, 100);
50445
- const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: "Asia/Kolkata" })}`;
50737
+ const generatedText = `Generated on ${(/* @__PURE__ */ new Date()).toLocaleString("en-IN", { timeZone: reportTimezone })}`;
50446
50738
  const generatedTextWidth = doc.getStringUnitWidth(generatedText) * 9 / doc.internal.scaleFactor;
50447
50739
  doc.text(generatedText, doc.internal.pageSize.width - 20 - generatedTextWidth, 15);
50448
50740
  doc.setDrawColor(200, 200, 200);
@@ -50461,11 +50753,10 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50461
50753
  doc.setFontSize(13);
50462
50754
  doc.setFont("helvetica", "normal");
50463
50755
  doc.setTextColor(60, 60, 60);
50464
- const date = new Date(workspace.date).toLocaleDateString("en-IN", {
50756
+ const date = formatOperationalDateKey2(workspace.date, {
50465
50757
  weekday: "long",
50466
50758
  day: "numeric",
50467
- month: "long",
50468
- timeZone: "Asia/Kolkata"
50759
+ month: "long"
50469
50760
  });
50470
50761
  const rawShiftType = workspace.shift_type || (workspace.shift_id === 0 ? "Day" : workspace.shift_id === 1 ? "Night" : `Shift ${workspace.shift_id}`);
50471
50762
  const shiftType = rawShiftType.toLowerCase().includes("shift") ? rawShiftType : `${rawShiftType} Shift`;
@@ -50474,7 +50765,7 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50474
50765
  const currentTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-IN", {
50475
50766
  hour: "2-digit",
50476
50767
  minute: "2-digit",
50477
- timeZone: "Asia/Kolkata"
50768
+ timeZone: reportTimezone
50478
50769
  });
50479
50770
  const shiftStartTime = (/* @__PURE__ */ new Date(`2000-01-01 ${workspace.shift_start}`)).toLocaleTimeString("en-IN", {
50480
50771
  hour: "2-digit",
@@ -50486,29 +50777,12 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50486
50777
  minute: "2-digit",
50487
50778
  hour12: true
50488
50779
  });
50489
- const parseTimeToMinutes4 = (timeValue) => {
50490
- const [hourPart, minutePart] = timeValue.split(":").map(Number);
50491
- const hour = Number.isFinite(hourPart) ? hourPart : 0;
50492
- const minute = Number.isFinite(minutePart) ? minutePart : 0;
50493
- return hour * 60 + minute;
50494
- };
50495
- const toShiftUtcMs = (dateKey, timeValue) => {
50496
- const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50497
- const year = Number.isFinite(yearPart) ? yearPart : 1970;
50498
- const month = Number.isFinite(monthPart) ? monthPart : 1;
50499
- const day = Number.isFinite(dayPart) ? dayPart : 1;
50500
- const [hourPart, minutePart] = timeValue.split(":").map(Number);
50501
- const hour = Number.isFinite(hourPart) ? hourPart : 0;
50502
- const minute = Number.isFinite(minutePart) ? minutePart : 0;
50503
- const IST_OFFSET_MINUTES = 330;
50504
- return Date.UTC(year, month - 1, day, hour, minute) - IST_OFFSET_MINUTES * 60 * 1e3;
50505
- };
50506
- const shiftStartMinutes = parseTimeToMinutes4(workspace.shift_start);
50507
- const shiftEndMinutes = parseTimeToMinutes4(workspace.shift_end);
50508
- const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
50509
- const shiftStartUtcMs = toShiftUtcMs(workspace.date, workspace.shift_start);
50510
- const shiftEndUtcMs = toShiftUtcMs(workspace.date, workspace.shift_end) + (wrapsMidnight ? 24 * 60 * 60 * 1e3 : 0);
50511
- const isShiftInProgress = Date.now() >= shiftStartUtcMs && Date.now() < shiftEndUtcMs;
50780
+ const isShiftInProgress = isShiftInProgressForReportDate({
50781
+ reportDate: workspace.date,
50782
+ shiftStart: workspace.shift_start,
50783
+ shiftEnd: workspace.shift_end,
50784
+ timeZone: reportTimezone
50785
+ });
50512
50786
  const reportPeriodEndTime = isShiftInProgress ? currentTime : shiftEndTime;
50513
50787
  doc.setFontSize(12);
50514
50788
  doc.setTextColor(80, 80, 80);
@@ -50614,7 +50888,7 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50614
50888
  shiftStart: workspace.shift_start,
50615
50889
  shiftEnd: workspace.shift_end,
50616
50890
  shiftDate: workspace.date,
50617
- timezone: "Asia/Kolkata"
50891
+ timezone: reportTimezone
50618
50892
  }) : null;
50619
50893
  const hourlyUptime = uptimeSeries?.points?.length ? Array.from({ length: Math.ceil(uptimeSeries.shiftMinutes / 60) }, (_, index) => {
50620
50894
  const start = index * 60;
@@ -50629,6 +50903,31 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50629
50903
  const hourlyData = isUptimeMode ? hourlyUptime : isAssemblyCycleMode ? hourlyCycleTimes && hourlyCycleTimes.length > 0 ? hourlyCycleTimes : workspace.hourly_action_counts || [] : workspace.hourly_action_counts || [];
50630
50904
  const hourlyTarget = workspace.pph_threshold;
50631
50905
  const cycleTarget = workspace.ideal_cycle_time || 0;
50906
+ const hourlyIntervals = buildHourlyIntervals({
50907
+ shiftStart: workspace.shift_start,
50908
+ shiftEnd: workspace.shift_end,
50909
+ fallbackHours: Math.max(hourlyData.length, 1)
50910
+ });
50911
+ const outputTargetPlan = !isUptimeMode && !isAssemblyCycleMode ? buildHourlyTargetPlan({
50912
+ shiftStart: workspace.shift_start,
50913
+ shiftEnd: workspace.shift_end,
50914
+ breaks: shiftBreaks,
50915
+ pphThreshold: workspace.pph_threshold,
50916
+ fallbackHours: Math.max(hourlyData.length, 1),
50917
+ rounding: "floor"
50918
+ }) : null;
50919
+ const formatIntervalLabel = (totalMinutes) => {
50920
+ const normalized = (totalMinutes % (24 * 60) + 24 * 60) % (24 * 60);
50921
+ const hour = Math.floor(normalized / 60);
50922
+ const minute = normalized % 60;
50923
+ const time2 = new Date(Date.UTC(2e3, 0, 1, hour, minute));
50924
+ return time2.toLocaleTimeString("en-IN", {
50925
+ hour: "numeric",
50926
+ ...minute > 0 ? { minute: "2-digit" } : {},
50927
+ hour12: true,
50928
+ timeZone: "UTC"
50929
+ });
50930
+ };
50632
50931
  const pageHeight = doc.internal.pageSize.height;
50633
50932
  const maxContentY = pageHeight - 15;
50634
50933
  const baseTableStartY = hourlyPerfStartY + 31;
@@ -50696,36 +50995,30 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50696
50995
  doc.setFontSize(contentFontSize);
50697
50996
  doc.setFont("helvetica", "normal");
50698
50997
  let yPos = headerBottomY + 5.5;
50699
- const workspaceDate = new Date(workspace.date);
50700
- const today = /* @__PURE__ */ new Date();
50701
- today.setHours(0, 0, 0, 0);
50702
- workspaceDate.setHours(0, 0, 0, 0);
50703
- const isToday2 = workspaceDate.getTime() === today.getTime();
50998
+ const isToday2 = getDateKeyInTimeZone(reportTimezone) === workspace.date;
50704
50999
  let currentHour = 24;
50705
51000
  if (isToday2) {
50706
51001
  const now4 = /* @__PURE__ */ new Date();
50707
- const currentTimeIST = new Date(now4.toLocaleString("en-US", { timeZone: "Asia/Kolkata" }));
50708
- currentHour = currentTimeIST.getHours();
51002
+ const currentTimeInReportTimezone = new Date(now4.toLocaleString("en-US", { timeZone: reportTimezone }));
51003
+ currentHour = currentTimeInReportTimezone.getHours();
50709
51004
  }
50710
51005
  hourlyData.forEach((entry, index) => {
50711
- const startTime = /* @__PURE__ */ new Date(`2000-01-01 ${workspace.shift_start}`);
50712
- startTime.setHours(startTime.getHours() + index);
50713
- const endTime = new Date(startTime);
50714
- endTime.setHours(endTime.getHours() + 1);
50715
- const timeRange = `${startTime.toLocaleTimeString("en-IN", {
50716
- hour: "numeric",
50717
- hour12: true
50718
- })} - ${endTime.toLocaleTimeString("en-IN", {
50719
- hour: "numeric",
50720
- hour12: true
50721
- })}`;
50722
- const hourNumber = startTime.getHours();
50723
- const dataCollected = !isToday2 || hourNumber < currentHour;
51006
+ const interval = hourlyIntervals[index];
51007
+ const timeRange = interval ? `${formatIntervalLabel(interval.start)} - ${formatIntervalLabel(interval.end)}` : `${index + 1}`;
51008
+ const dataCollected = interval ? isHourlyIntervalComplete({
51009
+ reportDate: workspace.date,
51010
+ shiftStart: workspace.shift_start,
51011
+ shiftEnd: workspace.shift_end,
51012
+ interval,
51013
+ timeZone: reportTimezone
51014
+ }) : !isToday2 || currentHour >= 24;
50724
51015
  const outputValue = isUptimeMode ? entry.activeMinutes ?? 0 : entry;
50725
51016
  const idleValue = isUptimeMode ? entry.idleMinutes ?? 0 : 0;
50726
51017
  const uptimePercent = isUptimeMode ? entry.uptimePercent ?? 0 : 0;
50727
51018
  const outputStr = dataCollected ? outputValue.toString() : "TBD";
50728
- const targetStr = isUptimeMode ? dataCollected ? idleValue.toString() : "TBD" : hourlyTarget.toString();
51019
+ const effectiveTarget = outputTargetPlan?.targets[index] ?? hourlyTarget;
51020
+ const remarkText = outputTargetPlan?.breakRemarks[index] || "";
51021
+ const targetStr = isUptimeMode ? dataCollected ? idleValue.toString() : "TBD" : effectiveTarget.toString();
50729
51022
  if (index < totalRows - 1) {
50730
51023
  const rowBottomY = headerBottomY + (index + 1) * rowHeight;
50731
51024
  doc.setDrawColor(200, 200, 200);
@@ -50757,10 +51050,12 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50757
51050
  } else {
50758
51051
  doc.text(outputStr, 75, yPos);
50759
51052
  doc.text(targetStr, 105, yPos);
51053
+ const remarkDisplay = remarkText ? doc.splitTextToSize(remarkText, 26)[0] : "";
51054
+ doc.text(remarkDisplay, 160, yPos);
50760
51055
  if (!dataCollected) {
50761
51056
  doc.setTextColor(100, 100, 100);
50762
51057
  doc.text("-", 135, yPos);
50763
- } else if (outputValue >= hourlyTarget) {
51058
+ } else if (outputValue >= effectiveTarget) {
50764
51059
  doc.setTextColor(0, 171, 69);
50765
51060
  doc.setFont("ZapfDingbats", "normal");
50766
51061
  doc.text("4", 135, yPos);
@@ -50774,11 +51069,10 @@ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons, efficiency
50774
51069
  yPos += rowHeight;
50775
51070
  });
50776
51071
  const workspaceDisplayName = getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
50777
- const fileDate = new Date(workspace.date).toLocaleDateString("en-IN", {
51072
+ const fileDate = formatOperationalDateKey2(workspace.date, {
50778
51073
  day: "2-digit",
50779
51074
  month: "short",
50780
- year: "numeric",
50781
- timeZone: "Asia/Kolkata"
51075
+ year: "numeric"
50782
51076
  }).replace(/ /g, "_");
50783
51077
  const fileShift = shiftType.replace(/ /g, "_");
50784
51078
  const fileName = `${workspaceDisplayName}_${fileDate}_${fileShift}.pdf`;
@@ -50824,12 +51118,25 @@ var WorkspaceMonthlyPdfGenerator = ({
50824
51118
  availableShifts,
50825
51119
  shiftConfig,
50826
51120
  efficiencyLegend,
51121
+ trendSummary,
50827
51122
  className,
50828
51123
  compact = false,
50829
51124
  isAssemblyWorkspace = false
50830
51125
  }) => {
50831
51126
  const [isGenerating, setIsGenerating] = useState(false);
50832
51127
  const effectiveLegend = efficiencyLegend || DEFAULT_EFFICIENCY_LEGEND;
51128
+ const drawStatusMark = (doc, x, y, met) => {
51129
+ doc.setLineWidth(0.8);
51130
+ if (met) {
51131
+ doc.setDrawColor(0, 171, 69);
51132
+ doc.line(x - 1.8, y - 0.2, x - 0.5, y + 1.2);
51133
+ doc.line(x - 0.5, y + 1.2, x + 2.1, y - 1.6);
51134
+ return;
51135
+ }
51136
+ doc.setDrawColor(227, 67, 41);
51137
+ doc.line(x - 1.8, y - 1.6, x + 1.8, y + 1.6);
51138
+ doc.line(x - 1.8, y + 1.6, x + 1.8, y - 1.6);
51139
+ };
50833
51140
  const generatePDF = async () => {
50834
51141
  setIsGenerating(true);
50835
51142
  try {
@@ -51024,12 +51331,13 @@ var WorkspaceMonthlyPdfGenerator = ({
51024
51331
  doc.setFont("helvetica", "bold");
51025
51332
  doc.text(`${outputMetrics.underperformingDays} of ${outputMetrics.totalDays}`, 120, kpiStartY + kpiSpacing * 3);
51026
51333
  } else {
51334
+ const medianCycleTime = Number.isFinite(trendSummary?.avg_cycle_time?.current) ? Number(trendSummary?.avg_cycle_time?.current) : outputMetrics.avgCycleTime;
51027
51335
  createKPIBox(kpiStartY);
51028
51336
  doc.setFontSize(11);
51029
51337
  doc.setFont("helvetica", "normal");
51030
51338
  doc.text("Average Cycle Time:", 25, kpiStartY);
51031
51339
  doc.setFont("helvetica", "bold");
51032
- doc.text(`${Math.round(outputMetrics.avgCycleTime)}s`, 120, kpiStartY);
51340
+ doc.text(`${medianCycleTime.toFixed(1)}s`, 120, kpiStartY);
51033
51341
  createKPIBox(kpiStartY + kpiSpacing);
51034
51342
  doc.setFont("helvetica", "normal");
51035
51343
  doc.text("Average Idle Time:", 25, kpiStartY + kpiSpacing);
@@ -51064,8 +51372,8 @@ var WorkspaceMonthlyPdfGenerator = ({
51064
51372
  doc.roundedRect(20, tableHeaderY, 170, 7, 1, 1, "F");
51065
51373
  const textY = tableHeaderY + 5;
51066
51374
  doc.text("Date", 25, textY);
51067
- doc.text(isUptimeMode ? "Productive" : isAssemblyWorkspace ? "Cycle Time" : "Actual", 60, textY);
51068
- doc.text(isUptimeMode ? "Idle" : isAssemblyWorkspace ? "Target CT" : "Standard", 95, textY);
51375
+ doc.text(isUptimeMode ? "Productive" : isAssemblyWorkspace ? "Actual Cycle Time" : "Actual", 60, textY);
51376
+ doc.text(isUptimeMode ? "Idle" : isAssemblyWorkspace ? "Standard Cycle Time" : "Standard", 95, textY);
51069
51377
  doc.text(isUptimeMode ? "Utilization" : "Efficiency", 135, textY);
51070
51378
  doc.text("Status", 170, textY);
51071
51379
  doc.setLineWidth(0.2);
@@ -51097,31 +51405,18 @@ var WorkspaceMonthlyPdfGenerator = ({
51097
51405
  doc.text(formatIdleTime(productiveSeconds), 60, yPos);
51098
51406
  doc.text(formatIdleTime(clampedIdleSeconds), 95, yPos);
51099
51407
  doc.text(`${utilization}%`, 135, yPos);
51100
- if (utilization >= effectiveLegend.green_min) {
51101
- doc.setTextColor(0, 171, 69);
51102
- doc.text("\u2713", 170, yPos);
51103
- } else {
51104
- doc.setTextColor(227, 67, 41);
51105
- doc.text("\xD7", 170, yPos);
51106
- }
51107
- doc.setTextColor(0, 0, 0);
51408
+ drawStatusMark(doc, 171, yPos - 0.3, utilization >= effectiveLegend.green_min);
51108
51409
  } else {
51109
51410
  if (isAssemblyWorkspace) {
51411
+ const targetCycleTime = Number.isFinite(shift.idealCycleTime) && Number(shift.idealCycleTime) > 0 ? Number(shift.idealCycleTime) : shift.pphThreshold > 0 ? 3600 / shift.pphThreshold : null;
51110
51412
  doc.text(`${shift.cycleTime.toFixed(1)}`, 60, yPos);
51111
- doc.text(`${shift.pphThreshold > 0 ? (3600 / shift.pphThreshold).toFixed(1) : "-"}`, 95, yPos);
51413
+ doc.text(targetCycleTime !== null ? `${targetCycleTime.toFixed(1)}` : "-", 95, yPos);
51112
51414
  } else {
51113
51415
  doc.text(`${shift.output}`, 60, yPos);
51114
51416
  doc.text(`${shift.targetOutput}`, 95, yPos);
51115
51417
  }
51116
51418
  doc.text(`${shift.efficiency.toFixed(1)}%`, 135, yPos);
51117
- if (shift.efficiency >= effectiveLegend.green_min) {
51118
- doc.setTextColor(0, 171, 69);
51119
- doc.text("\u2713", 170, yPos);
51120
- } else {
51121
- doc.setTextColor(227, 67, 41);
51122
- doc.text("\xD7", 170, yPos);
51123
- }
51124
- doc.setTextColor(0, 0, 0);
51419
+ drawStatusMark(doc, 171, yPos - 0.3, shift.efficiency >= effectiveLegend.green_min);
51125
51420
  }
51126
51421
  yPos += 8;
51127
51422
  });
@@ -64287,7 +64582,17 @@ var KPIDetailView = ({
64287
64582
  )
64288
64583
  ] }),
64289
64584
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 ml-auto", children: [
64290
- resolvedLineInfo && activeTab === "overview" && /* @__PURE__ */ jsx(LinePdfGenerator, { lineInfo: resolvedLineInfo, workspaceData: resolvedWorkspaces || [], issueResolutionSummary, shiftName: getShiftName(resolvedLineInfo.shift_id) }),
64585
+ resolvedLineInfo && activeTab === "overview" && /* @__PURE__ */ jsx(
64586
+ LinePdfGenerator,
64587
+ {
64588
+ lineInfo: resolvedLineInfo,
64589
+ workspaceData: resolvedWorkspaces || [],
64590
+ issueResolutionSummary,
64591
+ shiftName: getShiftName(resolvedLineInfo.shift_id),
64592
+ shiftBreaks: shiftConfig?.shifts?.find((shift) => shift.shiftId === resolvedLineInfo.shift_id)?.breaks || [],
64593
+ reportTimezone: shiftConfig?.timezone || configuredTimezone
64594
+ }
64595
+ ),
64291
64596
  activeTab === "monthly_history" && !urlDate && !urlShift && /* @__PURE__ */ jsxs(Fragment, { children: [
64292
64597
  /* @__PURE__ */ jsx(
64293
64598
  MonthlyRangeFilter_default,
@@ -64316,6 +64621,8 @@ var KPIDetailView = ({
64316
64621
  rangeStart,
64317
64622
  rangeEnd,
64318
64623
  selectedShiftId,
64624
+ availableShifts: shiftConfig?.shifts?.map((shift) => ({ id: shift.shiftId, name: shift.shiftName })),
64625
+ lineAssembly: resolvedLineInfo?.assembly === true,
64319
64626
  compact: true
64320
64627
  }
64321
64628
  )
@@ -64494,7 +64801,7 @@ var KPIDetailViewWithDisplayNames = withSelectedLineDisplayNames(KPIDetailView);
64494
64801
  var KPIDetailView_default = KPIDetailViewWithDisplayNames;
64495
64802
  var isNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0;
64496
64803
  var resolveCompanyId = (...candidates) => candidates.find(isNonEmptyString);
64497
- var parseTimeToMinutes2 = (value) => {
64804
+ var parseTimeToMinutes3 = (value) => {
64498
64805
  if (!value) return null;
64499
64806
  const [hourStr, minuteStr] = value.split(":");
64500
64807
  const hour = Number.parseInt(hourStr ?? "", 10);
@@ -64506,8 +64813,8 @@ var getShiftEndDate = (shift, timezone) => {
64506
64813
  if (!shift?.date) return null;
64507
64814
  const startTime = shift.startTime || "06:00";
64508
64815
  const endTime = shift.endTime || "18:00";
64509
- const startMinutes = parseTimeToMinutes2(startTime);
64510
- const endMinutes = parseTimeToMinutes2(endTime);
64816
+ const startMinutes = parseTimeToMinutes3(startTime);
64817
+ const endMinutes = parseTimeToMinutes3(endTime);
64511
64818
  if (startMinutes === null || endMinutes === null) return null;
64512
64819
  const shiftStartDate = fromZonedTime(`${shift.date}T${startTime}:00`, timezone);
64513
64820
  let durationMinutes = endMinutes - startMinutes;
@@ -64545,7 +64852,7 @@ var createKpisOverviewUrl = ({
64545
64852
  return queryString ? `/kpis?${queryString}` : "/kpis";
64546
64853
  };
64547
64854
  var getZonedDateAtMidday = (dateKey, timezone) => fromZonedTime(`${dateKey}T12:00:00`, timezone);
64548
- var formatDateKey = (dateKey, timezone, options) => {
64855
+ var formatDateKey2 = (dateKey, timezone, options) => {
64549
64856
  try {
64550
64857
  return new Intl.DateTimeFormat("en-US", {
64551
64858
  ...options,
@@ -65613,7 +65920,7 @@ var KPIsOverviewView = ({
65613
65920
  setActiveTab(newTab);
65614
65921
  }, [activeTab, leaderboardLines.length, lines.length]);
65615
65922
  const formatLocalDate2 = useCallback((dateKey) => {
65616
- return formatDateKey(dateKey, configuredTimezone, {
65923
+ return formatDateKey2(dateKey, configuredTimezone, {
65617
65924
  year: "numeric",
65618
65925
  month: "long",
65619
65926
  day: "numeric"
@@ -65627,8 +65934,8 @@ var KPIsOverviewView = ({
65627
65934
  zonedNow.getMonth(),
65628
65935
  new Date(zonedNow.getFullYear(), zonedNow.getMonth() + 1, 0).getDate()
65629
65936
  );
65630
- const startLabel = formatDateKey(startOfMonthKey, configuredTimezone, { month: "short", day: "numeric" });
65631
- const endLabel = formatDateKey(endOfMonthKey, configuredTimezone, { month: "short", day: "numeric" });
65937
+ const startLabel = formatDateKey2(startOfMonthKey, configuredTimezone, { month: "short", day: "numeric" });
65938
+ const endLabel = formatDateKey2(endOfMonthKey, configuredTimezone, { month: "short", day: "numeric" });
65632
65939
  return `${startLabel} - ${endLabel}, ${zonedNow.getFullYear()}`;
65633
65940
  };
65634
65941
  const isMonthlyMode = activeTab === "leaderboard" && timeRange === "monthly";
@@ -71263,6 +71570,7 @@ var WorkspaceDetailView = ({
71263
71570
  efficiency: monitoringMode === "uptime" ? Number.isFinite(metric.avg_efficiency) ? Number(metric.avg_efficiency) : computedUptimeEfficiency : Number.isFinite(metric.avg_efficiency) ? Number(metric.avg_efficiency) : 0,
71264
71571
  output: metric.total_output || 0,
71265
71572
  cycleTime: metric.avg_cycle_time || 0,
71573
+ idealCycleTime: Number(metric.ideal_cycle_time || 0),
71266
71574
  pph: metric.avg_pph || 0,
71267
71575
  pphThreshold: metric.pph_threshold || 0,
71268
71576
  idealOutput: Number(metric.ideal_output || 0),
@@ -71818,7 +72126,9 @@ var WorkspaceDetailView = ({
71818
72126
  workspace,
71819
72127
  idleTimeReasons: idleTimeChartData,
71820
72128
  efficiencyLegend,
71821
- hourlyCycleTimes: cycleTimeChartData
72129
+ hourlyCycleTimes: cycleTimeChartData,
72130
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
72131
+ reportTimezone: shiftConfig?.timezone || timezone
71822
72132
  }
71823
72133
  ) }),
71824
72134
  activeTab === "monthly_history" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -71853,6 +72163,7 @@ var WorkspaceDetailView = ({
71853
72163
  workspaceId,
71854
72164
  workspaceName: formattedWorkspaceName,
71855
72165
  monthlyData,
72166
+ analysisData: analysisMonthlyData,
71856
72167
  selectedMonth,
71857
72168
  selectedYear,
71858
72169
  monitoringMode: workspace?.monitoring_mode,
@@ -71861,7 +72172,10 @@ var WorkspaceDetailView = ({
71861
72172
  selectedShiftId: selectedShift,
71862
72173
  availableShifts: shiftConfig?.shifts?.map((s) => ({ id: s.shiftId, name: s.shiftName })),
71863
72174
  shiftConfig,
71864
- compact: true
72175
+ efficiencyLegend,
72176
+ trendSummary: workspaceMonthlyTrend,
72177
+ compact: true,
72178
+ isAssemblyWorkspace
71865
72179
  }
71866
72180
  )
71867
72181
  ] })
@@ -80076,7 +80390,7 @@ var useOperationsOverviewRefresh = ({
80076
80390
  };
80077
80391
  }, [companyId, enabled, getIsPageActive, isLiveScope, lineIds.length, refreshFromResume, startPolling, stopPolling, supabase]);
80078
80392
  };
80079
- var parseTimeToMinutes3 = (value) => {
80393
+ var parseTimeToMinutes4 = (value) => {
80080
80394
  if (!value) return null;
80081
80395
  const parts = value.split(":");
80082
80396
  if (parts.length < 2) return null;
@@ -80111,8 +80425,8 @@ var classifyShiftBucket = ({
80111
80425
  return "day";
80112
80426
  }
80113
80427
  }
80114
- const startMinutes = parseTimeToMinutes3(startTime);
80115
- const endMinutes = parseTimeToMinutes3(endTime);
80428
+ const startMinutes = parseTimeToMinutes4(startTime);
80429
+ const endMinutes = parseTimeToMinutes4(endTime);
80116
80430
  if (startMinutes !== null) {
80117
80431
  if (startMinutes >= 4 * 60 && startMinutes < 18 * 60) return "day";
80118
80432
  return "night";
@@ -80171,8 +80485,8 @@ var getShiftWindowsForConfig = (shiftConfig, timezone) => {
80171
80485
  ];
80172
80486
  };
80173
80487
  var normalizeShiftWindowMinutes = (startTime, endTime) => {
80174
- const startMinutes = parseTimeToMinutes3(startTime);
80175
- const endMinutesRaw = parseTimeToMinutes3(endTime);
80488
+ const startMinutes = parseTimeToMinutes4(startTime);
80489
+ const endMinutesRaw = parseTimeToMinutes4(endTime);
80176
80490
  if (startMinutes === null || endMinutesRaw === null) {
80177
80491
  return null;
80178
80492
  }
@@ -80338,7 +80652,7 @@ var PlantHeadView = () => {
80338
80652
  startTime: shift.startTime,
80339
80653
  endTime: shift.endTime
80340
80654
  });
80341
- const startMinutes = parseTimeToMinutes3(shift.startTime);
80655
+ const startMinutes = parseTimeToMinutes4(shift.startTime);
80342
80656
  if (bucket === "day" && startMinutes !== null) {
80343
80657
  candidateStarts.push(startMinutes);
80344
80658
  }
@@ -80348,7 +80662,7 @@ var PlantHeadView = () => {
80348
80662
  scopedLineIds.forEach((lineId) => {
80349
80663
  const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
80350
80664
  getShiftWindowsForConfig(shiftConfig).forEach((shift) => {
80351
- const startMinutes = parseTimeToMinutes3(shift.startTime);
80665
+ const startMinutes = parseTimeToMinutes4(shift.startTime);
80352
80666
  if (startMinutes !== null) {
80353
80667
  candidateStarts.push(startMinutes);
80354
80668
  }
@@ -80439,7 +80753,7 @@ var PlantHeadView = () => {
80439
80753
  startTime: shift.startTime,
80440
80754
  endTime: shift.endTime
80441
80755
  });
80442
- return bucket === "day" ? parseTimeToMinutes3(shift.startTime) : null;
80756
+ return bucket === "day" ? parseTimeToMinutes4(shift.startTime) : null;
80443
80757
  }).filter((value) => value !== null);
80444
80758
  }) : [];
80445
80759
  if (dayStartCandidates.length > 0) {