@optifye/dashboard-core 6.12.0 → 6.12.2

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
@@ -4234,6 +4234,22 @@ var isDummyRow = (row, dummySet) => {
4234
4234
  if (typeof skuId !== "string" || skuId.length === 0) return false;
4235
4235
  return dummySet.has(skuId);
4236
4236
  };
4237
+ var isActiveOutputRow = (row) => {
4238
+ const currentOutput = coerceOptionalNumber(row.current_output) ?? 0;
4239
+ const idealOutput = coerceOptionalNumber(row.ideal_output) ?? 0;
4240
+ return currentOutput > 0 || idealOutput > 0;
4241
+ };
4242
+ var roundHalfUpInt = (value) => Math.floor(value + 0.5);
4243
+ var dedupeStringsPreserveOrder = (values) => {
4244
+ const seen = /* @__PURE__ */ new Set();
4245
+ const ordered = [];
4246
+ for (const value of values) {
4247
+ if (!value || seen.has(value)) continue;
4248
+ seen.add(value);
4249
+ ordered.push(value);
4250
+ }
4251
+ return ordered;
4252
+ };
4237
4253
  var emptyAggregate = () => ({
4238
4254
  current_output: 0,
4239
4255
  ideal_output: 0,
@@ -4352,10 +4368,7 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4352
4368
  );
4353
4369
  const dummyLineMetricsRow = rows.find((row) => isDummyRow(row, dummySet));
4354
4370
  const lineThresholdValue = dummyLineMetricsRow ? safeFloat(dummyLineMetricsRow.line_threshold) : safeFloat(rowsForAggregation[0]?.line_threshold ?? 0);
4355
- const underperformingWorkspacesSum = rowsForAggregation.reduce(
4356
- (acc, row) => acc + safeInt(row.underperforming_workspaces),
4357
- 0
4358
- );
4371
+ const activeRealRows = rowsForAggregation.filter(isActiveOutputRow);
4359
4372
  const weighted = (field) => {
4360
4373
  const pairs = [];
4361
4374
  rowsForAggregation.forEach((row, idx) => {
@@ -4377,6 +4390,12 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4377
4390
  const avgEfficiency = weighted("avg_efficiency");
4378
4391
  const avgCycleTime = weighted("avg_cycle_time");
4379
4392
  const thresholdPph = weighted("threshold_pph");
4393
+ const underperformingWorkspaces = activeRealRows.length > 0 ? roundHalfUpInt(
4394
+ activeRealRows.reduce(
4395
+ (acc, row) => acc + safeInt(row.underperforming_workspaces),
4396
+ 0
4397
+ ) / activeRealRows.length
4398
+ ) : 0;
4380
4399
  const merged = mergeHourlyFields(rows);
4381
4400
  const outputArrays = [];
4382
4401
  for (const row of rowsForAggregation) {
@@ -4399,7 +4418,7 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4399
4418
  }
4400
4419
  const underperformingNames = [];
4401
4420
  const underperformingUuids = [];
4402
- for (const row of rowsForAggregation) {
4421
+ for (const row of activeRealRows) {
4403
4422
  const names = row.underperforming_workspace_names;
4404
4423
  if (Array.isArray(names)) {
4405
4424
  for (const n of names) {
@@ -4413,6 +4432,8 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4413
4432
  }
4414
4433
  }
4415
4434
  }
4435
+ const dedupedUnderperformingNames = dedupeStringsPreserveOrder(underperformingNames);
4436
+ const dedupedUnderperformingUuids = dedupeStringsPreserveOrder(underperformingUuids);
4416
4437
  let totalWorkspacesValue = null;
4417
4438
  const primaryTotal = coerceOptionalNumber(primary.total_workspaces);
4418
4439
  if (primaryTotal !== null) {
@@ -4434,9 +4455,9 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4434
4455
  line_threshold: lineThresholdValue,
4435
4456
  threshold_pph: thresholdPph,
4436
4457
  total_workspaces: safeInt(totalWorkspacesValue ?? 0),
4437
- underperforming_workspaces: underperformingWorkspacesSum,
4438
- underperforming_workspace_names: underperformingNames,
4439
- underperforming_workspace_uuids: underperformingUuids,
4458
+ underperforming_workspaces: underperformingWorkspaces,
4459
+ underperforming_workspace_names: dedupedUnderperformingNames,
4460
+ underperforming_workspace_uuids: dedupedUnderperformingUuids,
4440
4461
  output_array: outputArray,
4441
4462
  output_hourly: merged.output_hourly,
4442
4463
  idle_time_hourly: merged.idle_time_hourly,
@@ -5561,11 +5582,11 @@ var getDaysDifferenceInZone = (compareDate, timezone) => {
5561
5582
  todayInZone.setHours(0, 0, 0, 0);
5562
5583
  compareDateInZone.setHours(0, 0, 0, 0);
5563
5584
  const diffTime = todayInZone.getTime() - compareDateInZone.getTime();
5564
- const diffDays = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
5565
- if (diffDays < 0) return "In the future";
5566
- if (diffDays === 0) return "Today";
5567
- if (diffDays === 1) return "Yesterday";
5568
- return `${diffDays} days ago`;
5585
+ const diffDays2 = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
5586
+ if (diffDays2 < 0) return "In the future";
5587
+ if (diffDays2 === 0) return "Today";
5588
+ if (diffDays2 === 1) return "Yesterday";
5589
+ return `${diffDays2} days ago`;
5569
5590
  };
5570
5591
  var getDashboardHeaderTimeInZone = (date = /* @__PURE__ */ new Date(), timezone, timeOptions, locale = DEFAULT_LOCALE) => {
5571
5592
  const defaultOptions = {
@@ -13012,6 +13033,8 @@ var toWorkspaceDetailedMetrics = ({
13012
13033
  const targetOutput = coerceNumber(data.target_output ?? data.total_day_output, 0);
13013
13034
  const idealOutput = coerceNumber(data.ideal_output ?? data.ideal_output_until_now, 0);
13014
13035
  const outputDifference = totalActions - idealOutput;
13036
+ const hasHourlyTargetOutputField = Object.prototype.hasOwnProperty.call(data, "hourly_target_output");
13037
+ const hourlyTargetOutput = Array.isArray(data.hourly_target_output) ? data.hourly_target_output.map((value) => value === null || value === void 0 ? null : coerceNumber(value, 0)) : hasHourlyTargetOutputField ? null : void 0;
13015
13038
  const hourlyCycleTimes = Array.isArray(data.hourly_cycle_times) ? data.hourly_cycle_times.map((value) => coerceNumber(value, 0)) : [];
13016
13039
  const cycleCompletionClipCount = data.cycle_completion_clip_count === null || data.cycle_completion_clip_count === void 0 ? null : coerceNumber(data.cycle_completion_clip_count, 0);
13017
13040
  const cycleTimeDataStatus = data.cycle_time_data_status === "missing_clips" ? "missing_clips" : data.cycle_time_data_status === "available" ? "available" : null;
@@ -13063,6 +13086,7 @@ var toWorkspaceDetailedMetrics = ({
13063
13086
  avg_efficiency: coerceNumber(data.efficiency ?? data.avg_efficiency, 0),
13064
13087
  total_actions: totalActions,
13065
13088
  hourly_action_counts: hourlyActionCounts,
13089
+ hourly_target_output: hourlyTargetOutput,
13066
13090
  hourly_cycle_times: hourlyCycleTimes,
13067
13091
  cycle_completion_clip_count: cycleCompletionClipCount,
13068
13092
  cycle_time_data_status: cycleTimeDataStatus,
@@ -19679,7 +19703,7 @@ function formatRelativeTime(timestamp) {
19679
19703
  const diffSeconds = Math.floor(diffMs / 1e3);
19680
19704
  const diffMinutes = Math.floor(diffSeconds / 60);
19681
19705
  const diffHours = Math.floor(diffMinutes / 60);
19682
- const diffDays = Math.floor(diffHours / 24);
19706
+ const diffDays2 = Math.floor(diffHours / 24);
19683
19707
  if (diffSeconds < 60) {
19684
19708
  return "Less than a minute ago";
19685
19709
  }
@@ -19691,8 +19715,8 @@ function formatRelativeTime(timestamp) {
19691
19715
  const hourLabel = diffHours === 1 ? "hour" : "hours";
19692
19716
  return `${diffHours} ${hourLabel} ago`;
19693
19717
  }
19694
- const dayLabel = diffDays === 1 ? "day" : "days";
19695
- return `${diffDays} ${dayLabel} ago`;
19718
+ const dayLabel = diffDays2 === 1 ? "day" : "days";
19719
+ return `${diffDays2} ${dayLabel} ago`;
19696
19720
  } catch (error) {
19697
19721
  console.error("[formatRelativeTime] Error formatting timestamp:", error);
19698
19722
  return "Unknown";
@@ -35192,6 +35216,478 @@ var Button = React144__namespace.forwardRef(
35192
35216
  );
35193
35217
  Button.displayName = "Button";
35194
35218
 
35219
+ // src/lib/utils/hourlyTargets.ts
35220
+ var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
35221
+ var MINUTES_PER_DAY = 24 * 60;
35222
+ var parseTimeToMinutes2 = (timeString) => {
35223
+ const normalized = stripSeconds2(timeString || "");
35224
+ if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
35225
+ const [hours, minutes] = normalized.split(":").map(Number);
35226
+ return hours * 60 + minutes;
35227
+ };
35228
+ var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
35229
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35230
+ if (!Number.isFinite(shiftStartMinutes)) return [];
35231
+ const normalizedBreaks = [];
35232
+ for (const entry of breaks) {
35233
+ const startRaw = parseTimeToMinutes2(entry.startTime);
35234
+ const endRaw = parseTimeToMinutes2(entry.endTime);
35235
+ if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
35236
+ let start = startRaw;
35237
+ let end = endRaw;
35238
+ if (end <= start) {
35239
+ end += 24 * 60;
35240
+ }
35241
+ if (start < shiftStartMinutes) {
35242
+ start += 24 * 60;
35243
+ end += 24 * 60;
35244
+ }
35245
+ const label = entry.remarks?.trim() || "Break";
35246
+ normalizedBreaks.push({ start, end, label });
35247
+ }
35248
+ return normalizedBreaks;
35249
+ };
35250
+ var roundTarget = (value, mode) => {
35251
+ if (!Number.isFinite(value)) return 0;
35252
+ switch (mode) {
35253
+ case "floor":
35254
+ return Math.floor(value);
35255
+ case "ceil":
35256
+ return Math.ceil(value);
35257
+ case "round":
35258
+ default:
35259
+ return Math.round(value);
35260
+ }
35261
+ };
35262
+ var formatDateKey = (date) => {
35263
+ const year = date.getUTCFullYear();
35264
+ const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
35265
+ const day = `${date.getUTCDate()}`.padStart(2, "0");
35266
+ return `${year}-${month}-${day}`;
35267
+ };
35268
+ var shiftDateKey = (dateKey, deltaDays) => {
35269
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
35270
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
35271
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
35272
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
35273
+ const date = new Date(Date.UTC(year, month - 1, day));
35274
+ date.setUTCDate(date.getUTCDate() + deltaDays);
35275
+ return formatDateKey(date);
35276
+ };
35277
+ var getZonedNowSnapshot = (timeZone, now4) => {
35278
+ const formatter = new Intl.DateTimeFormat("en-US", {
35279
+ timeZone,
35280
+ year: "numeric",
35281
+ month: "2-digit",
35282
+ day: "2-digit",
35283
+ hour: "2-digit",
35284
+ minute: "2-digit",
35285
+ hourCycle: "h23"
35286
+ });
35287
+ const parts = formatter.formatToParts(now4).reduce((acc, part) => {
35288
+ if (part.type !== "literal") {
35289
+ acc[part.type] = part.value;
35290
+ }
35291
+ return acc;
35292
+ }, {});
35293
+ const year = Number(parts.year);
35294
+ const month = Number(parts.month);
35295
+ const day = Number(parts.day);
35296
+ const hour = Number(parts.hour);
35297
+ const minute = Number(parts.minute);
35298
+ return {
35299
+ dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
35300
+ minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
35301
+ };
35302
+ };
35303
+ var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
35304
+ var buildHourlyIntervals = ({
35305
+ shiftStart,
35306
+ shiftEnd,
35307
+ bucketMinutes = 60,
35308
+ fallbackHours = 11
35309
+ }) => {
35310
+ const startMinutes = parseTimeToMinutes2(shiftStart);
35311
+ if (!Number.isFinite(startMinutes)) return [];
35312
+ const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
35313
+ let totalMinutes;
35314
+ const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
35315
+ if (!Number.isFinite(endRaw)) {
35316
+ totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
35317
+ } else {
35318
+ let endMinutes = endRaw;
35319
+ if (endMinutes <= startMinutes) {
35320
+ endMinutes += 24 * 60;
35321
+ }
35322
+ totalMinutes = endMinutes - startMinutes;
35323
+ }
35324
+ if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
35325
+ const count = Math.ceil(totalMinutes / bucket);
35326
+ const shiftEndMinutes = startMinutes + totalMinutes;
35327
+ const intervals = [];
35328
+ for (let i = 0; i < count; i += 1) {
35329
+ const start = startMinutes + i * bucket;
35330
+ const end = Math.min(start + bucket, shiftEndMinutes);
35331
+ const minutes = Math.max(0, end - start);
35332
+ if (minutes <= 0) continue;
35333
+ intervals.push({ start, end, minutes });
35334
+ }
35335
+ return intervals;
35336
+ };
35337
+ var computeBreakMinutesByInterval = ({
35338
+ intervals,
35339
+ shiftStart,
35340
+ breaks
35341
+ }) => {
35342
+ if (!intervals.length || !breaks.length) return intervals.map(() => 0);
35343
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
35344
+ return intervals.map((interval) => {
35345
+ if (!normalizedBreaks.length) return 0;
35346
+ let total = 0;
35347
+ for (const brk of normalizedBreaks) {
35348
+ const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
35349
+ total += overlap;
35350
+ if (total >= interval.minutes) return interval.minutes;
35351
+ }
35352
+ return Math.min(interval.minutes, total);
35353
+ });
35354
+ };
35355
+ var computeBreakRemarksByInterval = ({
35356
+ intervals,
35357
+ shiftStart,
35358
+ breaks
35359
+ }) => {
35360
+ if (!intervals.length || !breaks.length) return intervals.map(() => "");
35361
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
35362
+ return intervals.map((interval) => {
35363
+ 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);
35364
+ return labels.join(", ");
35365
+ });
35366
+ };
35367
+ var computeEffectiveTargets = ({
35368
+ intervals,
35369
+ breakMinutes,
35370
+ pphThreshold,
35371
+ rounding = "round"
35372
+ }) => {
35373
+ return intervals.map((interval, idx) => {
35374
+ const intervalMinutes = Number(interval?.minutes) || 0;
35375
+ const breakMins = Number(breakMinutes?.[idx]) || 0;
35376
+ const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
35377
+ if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
35378
+ if (plannedWorkMinutes <= 0) return 0;
35379
+ return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
35380
+ });
35381
+ };
35382
+ var buildHourlyTargetPlan = ({
35383
+ shiftStart,
35384
+ shiftEnd,
35385
+ breaks = [],
35386
+ pphThreshold,
35387
+ bucketMinutes = 60,
35388
+ fallbackHours = 11,
35389
+ rounding = "round"
35390
+ }) => {
35391
+ const intervals = buildHourlyIntervals({
35392
+ shiftStart,
35393
+ shiftEnd,
35394
+ bucketMinutes,
35395
+ fallbackHours
35396
+ });
35397
+ const breakMinutes = computeBreakMinutesByInterval({
35398
+ intervals,
35399
+ shiftStart,
35400
+ breaks
35401
+ });
35402
+ const breakRemarks = computeBreakRemarksByInterval({
35403
+ intervals,
35404
+ shiftStart,
35405
+ breaks
35406
+ });
35407
+ const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
35408
+ const targets = computeEffectiveTargets({
35409
+ intervals,
35410
+ breakMinutes,
35411
+ pphThreshold,
35412
+ rounding
35413
+ });
35414
+ return {
35415
+ intervals,
35416
+ breakMinutes,
35417
+ breakRemarks,
35418
+ productiveMinutes,
35419
+ targets
35420
+ };
35421
+ };
35422
+ var isHourlyIntervalComplete = ({
35423
+ reportDate,
35424
+ shiftStart,
35425
+ shiftEnd,
35426
+ interval,
35427
+ timeZone = "Asia/Kolkata",
35428
+ now: now4 = /* @__PURE__ */ new Date()
35429
+ }) => {
35430
+ if (!reportDate) return true;
35431
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
35432
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35433
+ const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
35434
+ const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
35435
+ if (reportDate === snapshot.dateKey) {
35436
+ return interval.end <= snapshot.minutesOfDay;
35437
+ }
35438
+ if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
35439
+ return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
35440
+ }
35441
+ return reportDate < snapshot.dateKey;
35442
+ };
35443
+ var isShiftInProgressForReportDate = ({
35444
+ reportDate,
35445
+ shiftStart,
35446
+ shiftEnd,
35447
+ timeZone = "Asia/Kolkata",
35448
+ now: now4 = /* @__PURE__ */ new Date()
35449
+ }) => {
35450
+ if (!reportDate || !shiftStart || !shiftEnd) return false;
35451
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35452
+ const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
35453
+ if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
35454
+ return false;
35455
+ }
35456
+ let shiftEndMinutes = shiftEndMinutesRaw;
35457
+ const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
35458
+ if (wrapsMidnight) {
35459
+ shiftEndMinutes += MINUTES_PER_DAY;
35460
+ }
35461
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
35462
+ let currentMinutes = null;
35463
+ if (reportDate === snapshot.dateKey) {
35464
+ currentMinutes = snapshot.minutesOfDay;
35465
+ } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
35466
+ currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
35467
+ }
35468
+ if (currentMinutes === null) {
35469
+ return false;
35470
+ }
35471
+ return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
35472
+ };
35473
+ var padTime = (value) => value.toString().padStart(2, "0");
35474
+ var parseTime = (timeValue) => {
35475
+ if (!timeValue) return null;
35476
+ const [hourPart, minutePart] = timeValue.split(":");
35477
+ const hour = Number.parseInt(hourPart, 10);
35478
+ const minute = Number.parseInt(minutePart ?? "0", 10);
35479
+ if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null;
35480
+ return { hour, minute };
35481
+ };
35482
+ var normalizeIdleTimeHourly = (idleTimeHourly) => {
35483
+ if (!idleTimeHourly || typeof idleTimeHourly !== "object") {
35484
+ return {};
35485
+ }
35486
+ return Object.fromEntries(
35487
+ Object.entries(idleTimeHourly).map(([key, value]) => {
35488
+ if (Array.isArray(value)) return [key, value];
35489
+ if (value && Array.isArray(value.values)) {
35490
+ return [key, value.values];
35491
+ }
35492
+ return [key, []];
35493
+ })
35494
+ );
35495
+ };
35496
+ var interpretIdleValue = (value) => {
35497
+ if (value === 1 || value === "1") return "idle";
35498
+ if (value === 0 || value === "0") return "active";
35499
+ if (value === "x" || value === null || value === void 0) return "unknown";
35500
+ return "unknown";
35501
+ };
35502
+ var getShiftDurationMinutes = (shiftStart, shiftEnd) => {
35503
+ const start = parseTime(shiftStart);
35504
+ const end = parseTime(shiftEnd);
35505
+ if (!start || !end) return null;
35506
+ let duration = end.hour * 60 + end.minute - (start.hour * 60 + start.minute);
35507
+ if (duration <= 0) {
35508
+ duration += 24 * 60;
35509
+ }
35510
+ return duration > 0 ? duration : null;
35511
+ };
35512
+ var getShiftElapsedMinutes = ({
35513
+ shiftStart,
35514
+ shiftEnd,
35515
+ shiftDate,
35516
+ timezone,
35517
+ now: now4 = /* @__PURE__ */ new Date()
35518
+ }) => {
35519
+ if (!shiftDate || !timezone) return null;
35520
+ const startTime = parseTime(shiftStart);
35521
+ const endTime = parseTime(shiftEnd);
35522
+ if (!startTime || !endTime) return null;
35523
+ const shiftStartDate = dateFnsTz.fromZonedTime(
35524
+ `${shiftDate}T${padTime(startTime.hour)}:${padTime(startTime.minute)}:00`,
35525
+ timezone
35526
+ );
35527
+ let shiftEndDate = dateFnsTz.fromZonedTime(
35528
+ `${shiftDate}T${padTime(endTime.hour)}:${padTime(endTime.minute)}:00`,
35529
+ timezone
35530
+ );
35531
+ if (shiftEndDate <= shiftStartDate) {
35532
+ shiftEndDate = dateFns.addDays(shiftEndDate, 1);
35533
+ }
35534
+ const shiftMinutes = Math.max(dateFns.differenceInMinutes(shiftEndDate, shiftStartDate), 0);
35535
+ if (shiftMinutes <= 0) return null;
35536
+ const elapsed = dateFns.differenceInMinutes(now4, shiftStartDate);
35537
+ return Math.min(Math.max(elapsed, 0), shiftMinutes);
35538
+ };
35539
+ var maskFutureHourlySeries = ({
35540
+ data,
35541
+ shiftStart,
35542
+ shiftEnd,
35543
+ shiftDate,
35544
+ timezone,
35545
+ now: now4 = /* @__PURE__ */ new Date()
35546
+ }) => {
35547
+ if (!Array.isArray(data)) {
35548
+ return [];
35549
+ }
35550
+ const normalizedData = data.map((value) => typeof value === "number" && Number.isFinite(value) ? value : null);
35551
+ if (!normalizedData.length) {
35552
+ return normalizedData;
35553
+ }
35554
+ const shiftMinutes = getShiftDurationMinutes(shiftStart, shiftEnd);
35555
+ const elapsedMinutes = getShiftElapsedMinutes({
35556
+ shiftStart,
35557
+ shiftEnd,
35558
+ shiftDate,
35559
+ timezone,
35560
+ now: now4
35561
+ });
35562
+ if (shiftMinutes === null || elapsedMinutes === null || elapsedMinutes >= shiftMinutes) {
35563
+ return normalizedData;
35564
+ }
35565
+ return normalizedData.map((value, index) => {
35566
+ const slotStartMinutes = index * 60;
35567
+ return slotStartMinutes > elapsedMinutes ? null : value;
35568
+ });
35569
+ };
35570
+ var buildUptimeSeries = ({
35571
+ idleTimeHourly,
35572
+ shiftStart,
35573
+ shiftEnd,
35574
+ shiftDate,
35575
+ timezone,
35576
+ elapsedMinutes
35577
+ }) => {
35578
+ const normalizedIdle = normalizeIdleTimeHourly(idleTimeHourly || {});
35579
+ const hasIdleData = Object.keys(normalizedIdle).length > 0;
35580
+ if (!hasIdleData || !shiftDate || !timezone) {
35581
+ return {
35582
+ points: [],
35583
+ activeMinutes: 0,
35584
+ idleMinutes: 0,
35585
+ availableMinutes: 0,
35586
+ shiftMinutes: 0,
35587
+ elapsedMinutes: 0,
35588
+ hasData: false
35589
+ };
35590
+ }
35591
+ const startTime = parseTime(shiftStart);
35592
+ const endTime = parseTime(shiftEnd);
35593
+ if (!startTime || !endTime) {
35594
+ return {
35595
+ points: [],
35596
+ activeMinutes: 0,
35597
+ idleMinutes: 0,
35598
+ availableMinutes: 0,
35599
+ shiftMinutes: 0,
35600
+ elapsedMinutes: 0,
35601
+ hasData: false
35602
+ };
35603
+ }
35604
+ const shiftStartDate = dateFnsTz.fromZonedTime(
35605
+ `${shiftDate}T${padTime(startTime.hour)}:${padTime(startTime.minute)}:00`,
35606
+ timezone
35607
+ );
35608
+ let shiftEndDate = dateFnsTz.fromZonedTime(
35609
+ `${shiftDate}T${padTime(endTime.hour)}:${padTime(endTime.minute)}:00`,
35610
+ timezone
35611
+ );
35612
+ if (shiftEndDate <= shiftStartDate) {
35613
+ shiftEndDate = dateFns.addDays(shiftEndDate, 1);
35614
+ }
35615
+ const shiftMinutes = Math.max(dateFns.differenceInMinutes(shiftEndDate, shiftStartDate), 0);
35616
+ if (shiftMinutes <= 0) {
35617
+ return {
35618
+ points: [],
35619
+ activeMinutes: 0,
35620
+ idleMinutes: 0,
35621
+ availableMinutes: 0,
35622
+ shiftMinutes: 0,
35623
+ elapsedMinutes: 0,
35624
+ hasData: false
35625
+ };
35626
+ }
35627
+ const elapsedMinutesClamped = Number.isFinite(elapsedMinutes) ? Math.min(Math.max(Math.floor(elapsedMinutes ?? 0), 0), shiftMinutes) : shiftMinutes;
35628
+ const points = [];
35629
+ let activeMinutes = 0;
35630
+ let idleMinutes = 0;
35631
+ for (let minuteIndex = 0; minuteIndex < shiftMinutes; minuteIndex += 1) {
35632
+ const minuteDate = dateFns.addMinutes(shiftStartDate, minuteIndex);
35633
+ const timeLabel = dateFnsTz.formatInTimeZone(minuteDate, timezone, "h:mm a");
35634
+ if (minuteIndex >= elapsedMinutesClamped) {
35635
+ points.push({
35636
+ minuteIndex,
35637
+ timeLabel,
35638
+ uptime: null,
35639
+ status: "unknown"
35640
+ });
35641
+ continue;
35642
+ }
35643
+ const hourKey = dateFnsTz.formatInTimeZone(minuteDate, timezone, "H");
35644
+ const minuteKey = Number.parseInt(dateFnsTz.formatInTimeZone(minuteDate, timezone, "m"), 10);
35645
+ const hourBucket = normalizedIdle[hourKey] || [];
35646
+ const value = Array.isArray(hourBucket) ? hourBucket[minuteKey] : void 0;
35647
+ const status = interpretIdleValue(value);
35648
+ if (status === "active") activeMinutes += 1;
35649
+ if (status === "idle") idleMinutes += 1;
35650
+ points.push({
35651
+ minuteIndex,
35652
+ timeLabel,
35653
+ uptime: status === "active" ? 1 : status === "idle" ? 0 : null,
35654
+ status
35655
+ });
35656
+ }
35657
+ return {
35658
+ points,
35659
+ activeMinutes,
35660
+ idleMinutes,
35661
+ availableMinutes: activeMinutes + idleMinutes,
35662
+ shiftMinutes,
35663
+ elapsedMinutes: elapsedMinutesClamped,
35664
+ hasData: activeMinutes + idleMinutes > 0
35665
+ };
35666
+ };
35667
+ var getUptimeUtilizationPercent = (shift) => {
35668
+ const efficiency = shift.efficiency;
35669
+ if (Number.isFinite(efficiency)) {
35670
+ return Math.round(Math.max(0, Math.min(100, Number(efficiency))));
35671
+ }
35672
+ const idleSeconds = Number.isFinite(shift.idleTime) ? Number(shift.idleTime) : 0;
35673
+ const activeSeconds = Number.isFinite(shift.activeTimeSeconds) ? Number(shift.activeTimeSeconds) : null;
35674
+ let availableSeconds = Number.isFinite(shift.availableTimeSeconds) ? Number(shift.availableTimeSeconds) : null;
35675
+ if (availableSeconds === null) {
35676
+ if ((activeSeconds ?? 0) > 0 || idleSeconds > 0) {
35677
+ availableSeconds = (activeSeconds ?? 0) + idleSeconds;
35678
+ } else {
35679
+ return 0;
35680
+ }
35681
+ }
35682
+ if (availableSeconds <= 0) return 0;
35683
+ const clampedIdleSeconds = Math.min(Math.max(idleSeconds, 0), availableSeconds);
35684
+ const productiveSeconds = Math.max(
35685
+ activeSeconds ?? availableSeconds - clampedIdleSeconds,
35686
+ 0
35687
+ );
35688
+ return Math.round(productiveSeconds / availableSeconds * 100);
35689
+ };
35690
+
35195
35691
  // src/components/charts/skuDividerUtils.ts
35196
35692
  var HOURLY_TIME_RE = /^(\d{1,2}):(\d{2})/;
35197
35693
  var parseTimeOfDay = (timeValue) => {
@@ -35203,37 +35699,163 @@ var parseTimeOfDay = (timeValue) => {
35203
35699
  if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
35204
35700
  return { hour, minute };
35205
35701
  };
35206
- var computeSegmentOffset = (segment, shift) => {
35207
- if (!segment?.start_time) return null;
35702
+ var parseDateKey = (value) => {
35703
+ const [yearPart, monthPart, dayPart] = value.split("-").map(Number);
35704
+ if (!Number.isFinite(yearPart) || !Number.isFinite(monthPart) || !Number.isFinite(dayPart)) {
35705
+ return null;
35706
+ }
35707
+ return Date.UTC(yearPart, monthPart - 1, dayPart);
35708
+ };
35709
+ var diffDays = (left, right) => {
35710
+ const leftUtc = parseDateKey(left);
35711
+ const rightUtc = parseDateKey(right);
35712
+ if (leftUtc === null || rightUtc === null) return null;
35713
+ return Math.round((leftUtc - rightUtc) / (24 * 60 * 60 * 1e3));
35714
+ };
35715
+ var getSegmentMinutes = (isoString, timeZone, reportDate) => {
35716
+ const date = new Date(isoString);
35717
+ if (Number.isNaN(date.getTime())) return Number.NaN;
35718
+ const formatter = new Intl.DateTimeFormat("en-US", {
35719
+ timeZone,
35720
+ year: "numeric",
35721
+ month: "2-digit",
35722
+ day: "2-digit",
35723
+ hour: "2-digit",
35724
+ minute: "2-digit",
35725
+ hourCycle: "h23"
35726
+ });
35727
+ const parts = formatter.formatToParts(date).reduce((acc, part) => {
35728
+ if (part.type !== "literal") {
35729
+ acc[part.type] = part.value;
35730
+ }
35731
+ return acc;
35732
+ }, {});
35733
+ const dateKey = `${parts.year}-${parts.month}-${parts.day}`;
35734
+ let minutes = Number(parts.hour) * 60 + Number(parts.minute);
35735
+ const dayDelta = diffDays(dateKey, reportDate);
35736
+ if (dayDelta === null) return Number.NaN;
35737
+ minutes += dayDelta * 24 * 60;
35738
+ return minutes;
35739
+ };
35740
+ var computeSegmentOffsetUtc = (segment, shift) => {
35208
35741
  const parsedMs = Date.parse(segment.start_time);
35209
35742
  if (Number.isNaN(parsedMs)) return null;
35210
35743
  const date = new Date(parsedMs);
35211
35744
  const segHour = date.getUTCHours();
35212
35745
  const segMinute = date.getUTCMinutes();
35213
35746
  const segMinutes = segHour * 60 + segMinute;
35214
- let shiftStartMinutes = shift.startHour * 60 + shift.startMinute;
35747
+ const shiftStartMinutes = shift.startHour * 60 + shift.startMinute;
35215
35748
  let deltaMinutes = segMinutes - shiftStartMinutes;
35216
35749
  if (deltaMinutes < 0) deltaMinutes += 24 * 60;
35217
35750
  const offsetHours = deltaMinutes / 60;
35218
35751
  if (offsetHours < 0 || offsetHours > shift.slotCount) return null;
35219
35752
  return offsetHours;
35220
35753
  };
35754
+ var computeSegmentOffset = (segment, shift) => {
35755
+ if (!segment?.start_time) return null;
35756
+ if (shift.shiftDate && shift.timezone) {
35757
+ const segmentMinutes = getSegmentMinutes(
35758
+ segment.start_time,
35759
+ shift.timezone,
35760
+ shift.shiftDate
35761
+ );
35762
+ if (!Number.isFinite(segmentMinutes)) return null;
35763
+ const shiftStartMinutes = shift.startHour * 60 + shift.startMinute;
35764
+ const deltaMinutes = segmentMinutes - shiftStartMinutes;
35765
+ const offsetHours = deltaMinutes / 60;
35766
+ if (offsetHours < 0 || offsetHours > shift.slotCount) return null;
35767
+ return offsetHours;
35768
+ }
35769
+ return computeSegmentOffsetUtc(segment, shift);
35770
+ };
35221
35771
  var resolveShiftWindow2 = (params) => {
35222
35772
  const startTime = parseTimeOfDay(params.shiftStart);
35223
35773
  if (!startTime) return null;
35224
35774
  return {
35225
35775
  startHour: startTime.hour,
35226
35776
  startMinute: startTime.minute,
35227
- slotCount: params.slotCount
35777
+ slotCount: params.slotCount,
35778
+ ...params.shiftDate ? { shiftDate: params.shiftDate } : {},
35779
+ ...params.timezone ? { timezone: params.timezone } : {}
35228
35780
  };
35229
35781
  };
35782
+ var resolveTimelineEndOffset = ({
35783
+ shiftStart,
35784
+ shiftEnd,
35785
+ slotCount,
35786
+ shiftDate,
35787
+ timezone,
35788
+ now: now4
35789
+ }) => {
35790
+ const normalizedSlotCount = Number.isFinite(slotCount) && slotCount > 0 ? slotCount : 0;
35791
+ if (!shiftDate || !timezone) {
35792
+ return normalizedSlotCount;
35793
+ }
35794
+ const shiftMinutes = getShiftDurationMinutes(shiftStart, shiftEnd);
35795
+ const elapsedMinutes = getShiftElapsedMinutes({
35796
+ shiftStart,
35797
+ shiftEnd,
35798
+ shiftDate,
35799
+ timezone,
35800
+ now: now4
35801
+ });
35802
+ if (shiftMinutes === null || elapsedMinutes === null) {
35803
+ return normalizedSlotCount;
35804
+ }
35805
+ if (elapsedMinutes >= shiftMinutes) {
35806
+ return normalizedSlotCount;
35807
+ }
35808
+ return Math.max(0, Math.min(elapsedMinutes / 60, normalizedSlotCount));
35809
+ };
35810
+ var formatSkuRailLabel = (label, segmentWidth, options = {}) => {
35811
+ const horizontalPadding = options.horizontalPadding ?? 8;
35812
+ const minVisibleChars = options.minVisibleChars ?? 4;
35813
+ const averageCharacterWidth = options.averageCharacterWidth ?? 6.6;
35814
+ const compactAverageCharacterWidth = options.compactAverageCharacterWidth ?? 5;
35815
+ const cssTruncation = options.cssTruncation ?? false;
35816
+ const fullLabelThreshold = 6;
35817
+ if (!label) return null;
35818
+ if (!Number.isFinite(segmentWidth) || segmentWidth <= horizontalPadding * 2) return null;
35819
+ const usableWidth = Math.max(segmentWidth - horizontalPadding * 2, 0);
35820
+ const maxChars = Math.floor(usableWidth / averageCharacterWidth);
35821
+ if (maxChars >= Math.max(minVisibleChars, fullLabelThreshold)) {
35822
+ if (label.length <= maxChars || cssTruncation) return label;
35823
+ if (maxChars <= 1) return null;
35824
+ return `${label.slice(0, Math.max(maxChars - 1, 1)).trimEnd()}\u2026`;
35825
+ }
35826
+ const compactLabel = buildCompactSkuRailLabel(label, Math.max(maxChars, 1));
35827
+ const compactMaxChars = Math.floor(usableWidth / compactAverageCharacterWidth);
35828
+ if (compactMaxChars < 1) return null;
35829
+ if (compactLabel.length <= compactMaxChars || cssTruncation) return compactLabel;
35830
+ if (compactMaxChars <= 2) {
35831
+ return compactLabel.slice(0, compactMaxChars);
35832
+ }
35833
+ return `${compactLabel.slice(0, Math.max(compactMaxChars - 1, 1)).trimEnd()}\u2026`;
35834
+ };
35835
+ var buildCompactSkuRailLabel = (label, maxChars) => {
35836
+ const words = label.trim().split(/\s+/).filter(Boolean);
35837
+ if (words.length === 0) return label;
35838
+ const [firstWord] = words;
35839
+ const acronym = words.map((word) => word[0]).join("").toUpperCase();
35840
+ if (maxChars <= 3 && acronym) {
35841
+ return acronym;
35842
+ }
35843
+ if (firstWord.length >= 4) {
35844
+ return firstWord;
35845
+ }
35846
+ return acronym || firstWord;
35847
+ };
35230
35848
  var HourlyOutputChartComponent = ({
35231
35849
  data,
35232
35850
  pphThreshold,
35851
+ hourlyTargetOutput,
35233
35852
  shiftStart,
35234
35853
  shiftEnd,
35854
+ shiftBreaks = [],
35235
35855
  showIdleTime = false,
35236
35856
  idleTimeHourly,
35857
+ shiftDate,
35858
+ timezone,
35237
35859
  skuSegments,
35238
35860
  activeSkuId,
35239
35861
  className = ""
@@ -35241,6 +35863,7 @@ var HourlyOutputChartComponent = ({
35241
35863
  const containerRef = React144__namespace.default.useRef(null);
35242
35864
  const [containerReady, setContainerReady] = React144__namespace.default.useState(false);
35243
35865
  const [containerWidth, setContainerWidth] = React144__namespace.default.useState(0);
35866
+ const [hoveredSkuRailLabel, setHoveredSkuRailLabel] = React144__namespace.default.useState(null);
35244
35867
  const idleSlots = React144__namespace.default.useMemo(
35245
35868
  () => buildHourlyIdleSlots({
35246
35869
  idleTimeHourly,
@@ -35386,14 +36009,54 @@ var HourlyOutputChartComponent = ({
35386
36009
  }, [containerWidth]);
35387
36010
  const shiftWindow = React144__namespace.default.useMemo(() => resolveShiftWindow2({
35388
36011
  shiftStart,
35389
- slotCount: SHIFT_DURATION
35390
- }), [shiftStart, shiftEnd, SHIFT_DURATION]);
36012
+ slotCount: SHIFT_DURATION,
36013
+ shiftDate,
36014
+ timezone
36015
+ }), [shiftStart, shiftEnd, SHIFT_DURATION, shiftDate, timezone]);
36016
+ const fallbackTimelineEndOffset = React144__namespace.default.useMemo(
36017
+ () => resolveTimelineEndOffset({
36018
+ shiftStart,
36019
+ shiftEnd,
36020
+ slotCount: SHIFT_DURATION,
36021
+ shiftDate,
36022
+ timezone
36023
+ }),
36024
+ [shiftStart, shiftEnd, SHIFT_DURATION, shiftDate, timezone]
36025
+ );
36026
+ const observedTimelineEndOffset = React144__namespace.default.useMemo(() => {
36027
+ let lastObservedMinute = -1;
36028
+ idleSlots.forEach((slot) => {
36029
+ slot.idleArray.forEach((value, minuteIndex) => {
36030
+ if (value !== "x" && value !== null && value !== void 0) {
36031
+ lastObservedMinute = Math.max(
36032
+ lastObservedMinute,
36033
+ slot.hourIndex * 60 + minuteIndex
36034
+ );
36035
+ }
36036
+ });
36037
+ });
36038
+ if (lastObservedMinute < 0) return null;
36039
+ return Math.min((lastObservedMinute + 1) / 60, SHIFT_DURATION);
36040
+ }, [idleSlots, SHIFT_DURATION]);
36041
+ const timelineEndOffset = observedTimelineEndOffset ?? fallbackTimelineEndOffset;
36042
+ const targetLineEndOffset = React144__namespace.default.useMemo(() => {
36043
+ if (timelineEndOffset >= SHIFT_DURATION) {
36044
+ return SHIFT_DURATION;
36045
+ }
36046
+ if (Number.isInteger(timelineEndOffset)) {
36047
+ return timelineEndOffset;
36048
+ }
36049
+ return Math.min(Math.floor(timelineEndOffset) + 1, SHIFT_DURATION);
36050
+ }, [timelineEndOffset, SHIFT_DURATION]);
35391
36051
  const skuTimelineSegments = React144__namespace.default.useMemo(() => {
35392
- if (!skuSegments || skuSegments.length === 0 || !shiftWindow) return [];
35393
- const withOffsets = skuSegments.map((segment) => ({
35394
- segment,
35395
- offset: computeSegmentOffset(segment, shiftWindow) ?? 0
35396
- })).sort((a, b) => a.offset - b.offset);
36052
+ if (!skuSegments || skuSegments.length === 0 || !shiftWindow || timelineEndOffset <= 0) {
36053
+ return [];
36054
+ }
36055
+ const withOffsets = skuSegments.flatMap((segment) => {
36056
+ const offset = computeSegmentOffset(segment, shiftWindow);
36057
+ if (offset === null) return [];
36058
+ return [{ segment, offset }];
36059
+ }).sort((a, b) => a.offset - b.offset);
35397
36060
  if (withOffsets.length === 0) return [];
35398
36061
  const deduped = [];
35399
36062
  const DUPLICATE_OFFSET_THRESHOLD = 1 / 60;
@@ -35407,9 +36070,9 @@ var HourlyOutputChartComponent = ({
35407
36070
  deduped[0] = { ...deduped[0], offset: 0 };
35408
36071
  }
35409
36072
  return deduped.map((entry, index) => {
35410
- const nextOffset = index < deduped.length - 1 ? deduped[index + 1].offset : SHIFT_DURATION;
35411
- const start = Math.max(0, Math.min(entry.offset, SHIFT_DURATION));
35412
- const end = Math.max(start, Math.min(nextOffset, SHIFT_DURATION));
36073
+ const nextOffset = index < deduped.length - 1 ? deduped[index + 1].offset : timelineEndOffset;
36074
+ const start = Math.max(0, Math.min(entry.offset, timelineEndOffset));
36075
+ const end = Math.max(start, Math.min(nextOffset, timelineEndOffset));
35413
36076
  return {
35414
36077
  skuId: entry.segment.sku_id,
35415
36078
  label: entry.segment.sku_code,
@@ -35418,15 +36081,94 @@ var HourlyOutputChartComponent = ({
35418
36081
  pphThreshold: entry.segment.pph_threshold ?? pphThreshold
35419
36082
  };
35420
36083
  }).filter((segment) => segment.end > segment.start);
35421
- }, [skuSegments, shiftWindow, SHIFT_DURATION, pphThreshold]);
36084
+ }, [skuSegments, shiftWindow, timelineEndOffset, pphThreshold]);
36085
+ const targetTimelineSegments = React144__namespace.default.useMemo(() => {
36086
+ if (skuTimelineSegments.length === 0) return [];
36087
+ return skuTimelineSegments.map((segment, index) => ({
36088
+ ...segment,
36089
+ end: index === skuTimelineSegments.length - 1 ? Math.max(segment.start, targetLineEndOffset) : segment.end
36090
+ })).filter((segment) => segment.end > segment.start);
36091
+ }, [skuTimelineSegments, targetLineEndOffset]);
36092
+ const hasExplicitHourlyTargetOutputProp = React144__namespace.default.useMemo(
36093
+ () => hourlyTargetOutput !== void 0,
36094
+ [hourlyTargetOutput]
36095
+ );
36096
+ const fallbackHourlyTargetOutput = React144__namespace.default.useMemo(() => {
36097
+ if (hasExplicitHourlyTargetOutputProp) return void 0;
36098
+ if (skuTimelineSegments.length > 0) return void 0;
36099
+ const plan = buildHourlyTargetPlan({
36100
+ shiftStart,
36101
+ shiftEnd,
36102
+ breaks: shiftBreaks,
36103
+ pphThreshold,
36104
+ rounding: "floor"
36105
+ });
36106
+ if (!plan.targets.length) return void 0;
36107
+ return plan.targets.map((value) => Number.isFinite(value) ? value : null);
36108
+ }, [
36109
+ hasExplicitHourlyTargetOutputProp,
36110
+ skuTimelineSegments.length,
36111
+ shiftStart,
36112
+ shiftEnd,
36113
+ shiftBreaks,
36114
+ pphThreshold
36115
+ ]);
36116
+ const effectiveHourlyTargetOutput = React144__namespace.default.useMemo(
36117
+ () => hasExplicitHourlyTargetOutputProp ? hourlyTargetOutput : fallbackHourlyTargetOutput,
36118
+ [hasExplicitHourlyTargetOutputProp, hourlyTargetOutput, fallbackHourlyTargetOutput]
36119
+ );
36120
+ const hasHourlyTargetOutputProp = React144__namespace.default.useMemo(
36121
+ () => effectiveHourlyTargetOutput !== void 0,
36122
+ [effectiveHourlyTargetOutput]
36123
+ );
36124
+ const hasExplicitHourlyTargets = React144__namespace.default.useMemo(
36125
+ () => Array.isArray(effectiveHourlyTargetOutput) && effectiveHourlyTargetOutput.some((value) => value !== null && value !== void 0),
36126
+ [effectiveHourlyTargetOutput]
36127
+ );
36128
+ const hourlyTargetSegments = React144__namespace.default.useMemo(() => {
36129
+ if (!hasExplicitHourlyTargets) return [];
36130
+ const segments = [];
36131
+ let runStart = null;
36132
+ let runValue = null;
36133
+ const flush = (endIndex) => {
36134
+ if (runStart === null || runValue === null) return;
36135
+ segments.push({ start: runStart, end: endIndex, value: runValue });
36136
+ runStart = null;
36137
+ runValue = null;
36138
+ };
36139
+ for (let i = 0; i < SHIFT_DURATION; i += 1) {
36140
+ const rawValue = Array.isArray(effectiveHourlyTargetOutput) ? effectiveHourlyTargetOutput[i] : null;
36141
+ const value = rawValue === null || rawValue === void 0 ? null : Number(rawValue);
36142
+ if (value === null || !Number.isFinite(value)) {
36143
+ flush(i);
36144
+ continue;
36145
+ }
36146
+ if (runStart === null || runValue === null) {
36147
+ runStart = i;
36148
+ runValue = value;
36149
+ continue;
36150
+ }
36151
+ if (Math.abs(runValue - value) > 1e-6) {
36152
+ flush(i);
36153
+ runStart = i;
36154
+ runValue = value;
36155
+ }
36156
+ }
36157
+ flush(SHIFT_DURATION);
36158
+ return segments.filter((segment) => segment.end > segment.start);
36159
+ }, [SHIFT_DURATION, hasExplicitHourlyTargets, effectiveHourlyTargetOutput]);
35422
36160
  const activeSkuHourIndices = React144__namespace.default.useMemo(() => {
35423
36161
  const indices = /* @__PURE__ */ new Set();
35424
36162
  const targets = Array(SHIFT_DURATION).fill(pphThreshold);
35425
- if (!skuSegments || !shiftWindow) return { indices, targets };
35426
- const segmentsWithOffsets = skuSegments.map((seg) => ({
35427
- ...seg,
35428
- offset: computeSegmentOffset(seg, shiftWindow) || 0
35429
- })).sort((a, b) => a.offset - b.offset);
36163
+ if (!skuSegments || !shiftWindow) return { indices, targets, hasTimeline: false };
36164
+ const segmentsWithOffsets = skuSegments.flatMap((segment) => {
36165
+ const offset = computeSegmentOffset(segment, shiftWindow);
36166
+ if (offset === null) return [];
36167
+ return [{ ...segment, offset }];
36168
+ }).sort((a, b) => a.offset - b.offset);
36169
+ if (segmentsWithOffsets.length === 0) {
36170
+ return { indices, targets, hasTimeline: false };
36171
+ }
35430
36172
  for (let i = 0; i < SHIFT_DURATION; i++) {
35431
36173
  const midpoint = i + 0.5;
35432
36174
  let activeSeg = segmentsWithOffsets[0];
@@ -35446,13 +36188,15 @@ var HourlyOutputChartComponent = ({
35446
36188
  }
35447
36189
  }
35448
36190
  }
35449
- return { indices, targets };
36191
+ return { indices, targets, hasTimeline: true };
35450
36192
  }, [skuSegments, activeSkuId, shiftWindow, SHIFT_DURATION, pphThreshold]);
35451
36193
  const chartData = React144__namespace.default.useMemo(() => {
35452
- const { indices, targets } = activeSkuHourIndices;
36194
+ const { indices, targets, hasTimeline } = activeSkuHourIndices;
35453
36195
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
35454
36196
  const idleSlot = idleSlots[i];
35455
- const currentTarget = targets[i] || pphThreshold;
36197
+ const explicitTarget = hasHourlyTargetOutputProp ? effectiveHourlyTargetOutput?.[i] ?? null : void 0;
36198
+ const currentTarget = hasHourlyTargetOutputProp ? explicitTarget : targets[i] || pphThreshold;
36199
+ const comparisonTarget = currentTarget === null || currentTarget === void 0 ? targets[i] || pphThreshold : currentTarget;
35456
36200
  return {
35457
36201
  hourIndex: idleSlot?.hourIndex ?? i,
35458
36202
  hour: idleSlot?.hour || "",
@@ -35460,16 +36204,16 @@ var HourlyOutputChartComponent = ({
35460
36204
  output: animatedData[i] || 0,
35461
36205
  originalOutput: data[i] || 0,
35462
36206
  // Keep original data for labels
35463
- target: currentTarget,
35464
- color: (animatedData[i] || 0) >= Math.round(currentTarget) ? "#00AB45" : "#E34329",
36207
+ target: currentTarget ?? null,
36208
+ color: (animatedData[i] || 0) >= comparisonTarget ? "#00AB45" : "#E34329",
35465
36209
  idleMinutes: idleSlot?.idleMinutes || 0,
35466
36210
  idleArray: idleSlot?.idleArray || [],
35467
36211
  skuIndex: i,
35468
36212
  isHighlighted: indices.has(i),
35469
- isDimmed: !!activeSkuId && !indices.has(i)
36213
+ isDimmed: hasTimeline && !!activeSkuId && !indices.has(i)
35470
36214
  };
35471
36215
  });
35472
- }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId]);
36216
+ }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId, effectiveHourlyTargetOutput, hasHourlyTargetOutputProp]);
35473
36217
  const renderSkuTimelineRail = React144__namespace.default.useCallback((props) => {
35474
36218
  if (!skuTimelineSegments.length || SHIFT_DURATION <= 0) return null;
35475
36219
  const offset = props?.offset;
@@ -35481,8 +36225,9 @@ var HourlyOutputChartComponent = ({
35481
36225
  const railHeight = 3;
35482
36226
  const railY = top - 10;
35483
36227
  const baselineY = railY + railHeight / 2;
35484
- const labelYHigh = railY - 20;
35485
- const labelYLow = railY - 6;
36228
+ const showHoverLabel = (label, centerX, rY) => {
36229
+ setHoveredSkuRailLabel({ label, centerX, railY: rY });
36230
+ };
35486
36231
  return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
35487
36232
  /* @__PURE__ */ jsxRuntime.jsx(
35488
36233
  "line",
@@ -35500,9 +36245,43 @@ var HourlyOutputChartComponent = ({
35500
36245
  const xStart = left + segment.start / SHIFT_DURATION * width;
35501
36246
  const xEnd = left + segment.end / SHIFT_DURATION * width;
35502
36247
  const segmentWidth = Math.max(1, xEnd - xStart);
36248
+ const labelPadding = segmentWidth < 48 ? 4 : 8;
36249
+ const inlineLabelText = formatSkuRailLabel(segment.label, segmentWidth, {
36250
+ horizontalPadding: labelPadding,
36251
+ cssTruncation: true
36252
+ });
36253
+ const badgeLabelText = formatSkuRailLabel(segment.label, 28, {
36254
+ horizontalPadding: 4,
36255
+ cssTruncation: true
36256
+ });
36257
+ const isMicroSegment = segmentWidth < 18;
36258
+ const labelText = isMicroSegment ? badgeLabelText : inlineLabelText;
36259
+ const labelClipWidth = Math.max(segmentWidth - labelPadding * 2, 0);
35503
36260
  const isActive = !!activeSkuId && segment.skuId === activeSkuId;
35504
36261
  const isDimmed = !!activeSkuId && segment.skuId !== activeSkuId;
36262
+ const hoverHitWidth = Math.max(segmentWidth + 12, isMicroSegment ? 20 : segmentWidth);
36263
+ const hoverHitX = Math.max(
36264
+ left,
36265
+ Math.min(xStart - (hoverHitWidth - segmentWidth) / 2, left + width - hoverHitWidth)
36266
+ );
36267
+ const hoverHitHeight = isMicroSegment ? 34 : 28;
36268
+ const hoverHitY = railY - (isMicroSegment ? 30 : 24);
36269
+ const microBadgeWidth = labelText ? Math.max(labelText.length * 6.2 + 10, 18) : 0;
36270
+ const microBadgeX = xStart + segmentWidth / 2 - microBadgeWidth / 2;
36271
+ const microBadgeY = railY - 24;
35505
36272
  return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
36273
+ /* @__PURE__ */ jsxRuntime.jsx(
36274
+ "rect",
36275
+ {
36276
+ x: hoverHitX,
36277
+ y: hoverHitY,
36278
+ width: hoverHitWidth,
36279
+ height: hoverHitHeight,
36280
+ fill: "transparent",
36281
+ onMouseEnter: () => showHoverLabel(segment.label, xStart + segmentWidth / 2, railY),
36282
+ onMouseLeave: () => setHoveredSkuRailLabel((current) => current?.label === segment.label ? null : current)
36283
+ }
36284
+ ),
35506
36285
  /* @__PURE__ */ jsxRuntime.jsx(
35507
36286
  "rect",
35508
36287
  {
@@ -35513,7 +36292,7 @@ var HourlyOutputChartComponent = ({
35513
36292
  rx: 1.5,
35514
36293
  fill: isActive ? "#3b82f6" : "#cbd5e1",
35515
36294
  opacity: isDimmed ? 0.3 : 1,
35516
- style: { transition: "all 0.3s ease" }
36295
+ style: { transition: "all 0.3s ease", pointerEvents: "none" }
35517
36296
  }
35518
36297
  ),
35519
36298
  index > 0 && /* @__PURE__ */ jsxRuntime.jsx(
@@ -35525,22 +36304,59 @@ var HourlyOutputChartComponent = ({
35525
36304
  y2: railY + railHeight + 3,
35526
36305
  stroke: "#94a3b8",
35527
36306
  strokeWidth: 1,
35528
- opacity: 0.6
36307
+ opacity: 0.6,
36308
+ style: { pointerEvents: "none" }
35529
36309
  }
35530
36310
  ),
35531
- segmentWidth >= 36 && /* @__PURE__ */ jsxRuntime.jsx(
35532
- "text",
36311
+ isMicroSegment && labelText && /* @__PURE__ */ jsxRuntime.jsxs("g", { style: { pointerEvents: "none" }, children: [
36312
+ /* @__PURE__ */ jsxRuntime.jsx(
36313
+ "rect",
36314
+ {
36315
+ x: microBadgeX,
36316
+ y: microBadgeY,
36317
+ width: microBadgeWidth,
36318
+ height: 14,
36319
+ rx: 7,
36320
+ fill: "white",
36321
+ stroke: isActive ? "#3b82f6" : "#cbd5e1",
36322
+ strokeWidth: 1,
36323
+ opacity: isDimmed ? 0.55 : 0.98
36324
+ }
36325
+ ),
36326
+ /* @__PURE__ */ jsxRuntime.jsx(
36327
+ "text",
36328
+ {
36329
+ x: xStart + segmentWidth / 2,
36330
+ y: microBadgeY + 10,
36331
+ textAnchor: "middle",
36332
+ fontSize: 8,
36333
+ fontWeight: 700,
36334
+ fill: isActive ? "#2563eb" : "#64748b",
36335
+ opacity: isDimmed ? 0.55 : 1,
36336
+ style: { transition: "all 0.3s ease" },
36337
+ children: labelText
36338
+ }
36339
+ )
36340
+ ] }),
36341
+ !isMicroSegment && labelText && labelClipWidth > 0 && /* @__PURE__ */ jsxRuntime.jsx(
36342
+ "foreignObject",
35533
36343
  {
35534
- x: xStart + segmentWidth / 2,
35535
- y: index % 2 === 0 ? labelYHigh : labelYLow,
35536
- textAnchor: "middle",
35537
- fontSize: 10,
35538
- fontWeight: 700,
35539
- letterSpacing: "0.02em",
35540
- fill: isActive ? "#2563eb" : "#64748b",
35541
- opacity: isDimmed ? 0.4 : 1,
35542
- style: { transition: "all 0.3s ease", pointerEvents: "none" },
35543
- children: segment.label
36344
+ x: xStart + labelPadding,
36345
+ y: railY - 24,
36346
+ width: labelClipWidth,
36347
+ height: 18,
36348
+ style: { pointerEvents: "none", overflow: "visible" },
36349
+ children: /* @__PURE__ */ jsxRuntime.jsx(
36350
+ "div",
36351
+ {
36352
+ className: `w-full h-full flex items-center justify-center text-[10px] font-bold tracking-[0.02em] truncate pt-0.5 ${isActive ? "text-blue-600" : "text-slate-500"}`,
36353
+ style: {
36354
+ opacity: isDimmed ? 0.4 : 1,
36355
+ transition: "all 0.3s ease"
36356
+ },
36357
+ children: labelText
36358
+ }
36359
+ )
35544
36360
  }
35545
36361
  )
35546
36362
  ] }, `sku-rail-${segment.skuId}-${segment.start}-${index}`);
@@ -35598,14 +36414,17 @@ var HourlyOutputChartComponent = ({
35598
36414
  );
35599
36415
  }, [idleBarState.visible, idleBarState.key, idleBarState.shouldAnimate]);
35600
36416
  const maxDataValue = Math.max(...data, 0);
35601
- const maxTargetValue = Math.max(...chartData.map((d) => d.target), pphThreshold, 0);
36417
+ const numericChartTargets = chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target));
36418
+ const maxTargetValue = Math.max(...numericChartTargets, pphThreshold, 0);
35602
36419
  const maxYValue = Math.max(
35603
36420
  Math.ceil(maxTargetValue * 1.5),
35604
36421
  Math.ceil(maxDataValue * 1.15)
35605
36422
  // Add 15% headroom above max value
35606
36423
  );
35607
36424
  const generateYAxisTicks = () => {
35608
- const uniqueTargets = [...new Set(chartData.map((d) => Math.round(d.target)))].sort((a, b) => a - b);
36425
+ const uniqueTargets = [...new Set(
36426
+ chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target)).map((target) => Math.round(target))
36427
+ )].sort((a, b) => a - b);
35609
36428
  const rawTicks = [0];
35610
36429
  uniqueTargets.forEach((target) => {
35611
36430
  if (target > 0) {
@@ -35640,14 +36459,54 @@ var HourlyOutputChartComponent = ({
35640
36459
  };
35641
36460
  const renderTargetLine = React144__namespace.default.useCallback((props) => {
35642
36461
  const { offset, yAxisMap } = props;
35643
- if (!offset || !yAxisMap || SHIFT_DURATION <= 0) return null;
36462
+ if (!offset || !yAxisMap || SHIFT_DURATION <= 0 || targetLineEndOffset <= 0) return null;
35644
36463
  const { left, width } = offset;
35645
36464
  const yAxis = yAxisMap["default"] || yAxisMap[0];
35646
36465
  if (!yAxis || !yAxis.scale) return null;
35647
36466
  const lines = [];
35648
36467
  const offsetToX = (o) => left + o / SHIFT_DURATION * width;
35649
- if (skuTimelineSegments.length > 0) {
35650
- skuTimelineSegments.forEach((segment, index) => {
36468
+ if (hasHourlyTargetOutputProp && hourlyTargetSegments.length > 0) {
36469
+ hourlyTargetSegments.forEach((segment, index) => {
36470
+ const y = yAxis.scale(segment.value);
36471
+ const xStart = offsetToX(segment.start);
36472
+ const xEnd = offsetToX(segment.end);
36473
+ lines.push(
36474
+ /* @__PURE__ */ jsxRuntime.jsx(
36475
+ "line",
36476
+ {
36477
+ x1: xStart,
36478
+ y1: y,
36479
+ x2: xEnd,
36480
+ y2: y,
36481
+ stroke: "#E34329",
36482
+ strokeDasharray: "3 3",
36483
+ strokeWidth: 2
36484
+ },
36485
+ `target-hourly-h-${index}`
36486
+ )
36487
+ );
36488
+ const next = hourlyTargetSegments[index + 1];
36489
+ if (next && Math.abs(next.value - segment.value) > 1e-6) {
36490
+ const nextY = yAxis.scale(next.value);
36491
+ lines.push(
36492
+ /* @__PURE__ */ jsxRuntime.jsx(
36493
+ "line",
36494
+ {
36495
+ x1: xEnd,
36496
+ y1: y,
36497
+ x2: xEnd,
36498
+ y2: nextY,
36499
+ stroke: "#E34329",
36500
+ strokeDasharray: "3 3",
36501
+ strokeWidth: 2
36502
+ },
36503
+ `target-hourly-v-${index}`
36504
+ )
36505
+ );
36506
+ }
36507
+ });
36508
+ } else if (!hasHourlyTargetOutputProp && targetTimelineSegments.length > 0) {
36509
+ targetTimelineSegments.forEach((segment, index) => {
35651
36510
  const target = segment.pphThreshold || pphThreshold;
35652
36511
  const y = yAxis.scale(target);
35653
36512
  const xStart = offsetToX(segment.start);
@@ -35667,7 +36526,7 @@ var HourlyOutputChartComponent = ({
35667
36526
  `target-h-${index}`
35668
36527
  )
35669
36528
  );
35670
- const next = skuTimelineSegments[index + 1];
36529
+ const next = targetTimelineSegments[index + 1];
35671
36530
  if (next) {
35672
36531
  const nextTarget = next.pphThreshold || pphThreshold;
35673
36532
  if (nextTarget !== target) {
@@ -35690,7 +36549,7 @@ var HourlyOutputChartComponent = ({
35690
36549
  }
35691
36550
  }
35692
36551
  });
35693
- } else {
36552
+ } else if (!hasHourlyTargetOutputProp) {
35694
36553
  const y = yAxis.scale(pphThreshold);
35695
36554
  lines.push(
35696
36555
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -35709,10 +36568,13 @@ var HourlyOutputChartComponent = ({
35709
36568
  );
35710
36569
  }
35711
36570
  return /* @__PURE__ */ jsxRuntime.jsx("g", { children: lines });
35712
- }, [skuTimelineSegments, SHIFT_DURATION, pphThreshold]);
36571
+ }, [hourlyTargetSegments, targetTimelineSegments, SHIFT_DURATION, pphThreshold, targetLineEndOffset, hasHourlyTargetOutputProp]);
35713
36572
  const renderLegend = () => {
35714
- const uniqueTargets = [...new Set(chartData.map((d) => Math.round(d.target)))].sort((a, b) => a - b);
35715
- const targetText = uniqueTargets.length === 1 ? `Target: ${uniqueTargets[0]} units/hr` : `Target: ${uniqueTargets[0]} - ${uniqueTargets[uniqueTargets.length - 1]} units/hr`;
36573
+ const uniqueTargets = [...new Set(
36574
+ chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target)).map((target) => Math.round(target))
36575
+ )].sort((a, b) => a - b);
36576
+ const unitLabel = hasHourlyTargetOutputProp ? "units" : "units/hr";
36577
+ const targetText = uniqueTargets.length === 0 ? `Target` : uniqueTargets.length === 1 ? `Target: ${uniqueTargets[0]} ${unitLabel}` : `Target: ${uniqueTargets[0]} - ${uniqueTargets[uniqueTargets.length - 1]} ${unitLabel}`;
35716
36578
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center text-sm text-gray-600 bg-white py-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
35717
36579
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full border-t-2 border-[#E34329] border-dashed" }) }),
35718
36580
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: targetText })
@@ -35725,254 +36587,274 @@ var HourlyOutputChartComponent = ({
35725
36587
  className: `w-full h-full min-w-0 flex flex-col ${className}`,
35726
36588
  style: { minHeight: "200px", minWidth: 0 },
35727
36589
  children: [
35728
- containerReady ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
35729
- recharts.BarChart,
35730
- {
35731
- data: chartData,
35732
- margin: {
35733
- // Reserve headroom for the SKU timeline rail + staggered
35734
- // labels only when SKU segments are rendered. Non-SKU charts
35735
- // keep the original 10px top so recharts has enough vertical
35736
- // space to show the target (pph) tick label on the Y-axis.
35737
- top: skuTimelineSegments.length > 0 ? 40 : 10,
35738
- right: 10,
35739
- bottom: 10,
35740
- left: 6
35741
- },
35742
- barCategoryGap: "25%",
35743
- children: [
35744
- /* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
35745
- /* @__PURE__ */ jsxRuntime.jsx(
35746
- recharts.XAxis,
35747
- {
35748
- xAxisId: "default",
35749
- dataKey: "hour",
35750
- tick: { fontSize: xAxisConfig.tickFont },
35751
- interval: xAxisConfig.interval,
35752
- angle: xAxisConfig.angle,
35753
- textAnchor: "end",
35754
- tickMargin: xAxisConfig.tickMargin,
35755
- height: xAxisConfig.height
35756
- }
35757
- ),
35758
- /* @__PURE__ */ jsxRuntime.jsx(
35759
- recharts.XAxis,
35760
- {
35761
- xAxisId: "sku",
35762
- type: "number",
35763
- dataKey: "skuIndex",
35764
- domain: [0, Math.max(SHIFT_DURATION, 0)],
35765
- hide: true,
35766
- allowDataOverflow: true
35767
- }
35768
- ),
35769
- /* @__PURE__ */ jsxRuntime.jsx(
35770
- recharts.YAxis,
35771
- {
35772
- yAxisId: "default",
35773
- tickMargin: 8,
35774
- width: 48,
35775
- domain: [0, maxYValue],
35776
- ticks: generateYAxisTicks(),
35777
- tickFormatter: (value) => value,
35778
- tick: (props) => {
35779
- const { x, y, payload } = props;
35780
- return /* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${x},${y})`, children: /* @__PURE__ */ jsxRuntime.jsx(
35781
- "text",
35782
- {
35783
- x: -2,
35784
- y: 0,
35785
- dy: 4,
35786
- textAnchor: "end",
35787
- fill: "#666",
35788
- fontSize: 12,
35789
- children: payload.value
35790
- },
35791
- `tick-${payload.value}-${x}-${y}`
35792
- ) });
36590
+ containerReady ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-h-0 relative", children: [
36591
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
36592
+ recharts.BarChart,
36593
+ {
36594
+ data: chartData,
36595
+ margin: {
36596
+ // Reserve headroom for the SKU timeline rail + staggered
36597
+ // labels only when SKU segments are rendered. Non-SKU charts
36598
+ // keep the original 10px top so recharts has enough vertical
36599
+ // space to show the target (pph) tick label on the Y-axis.
36600
+ top: skuTimelineSegments.length > 0 ? 40 : 10,
36601
+ right: 10,
36602
+ bottom: 10,
36603
+ left: 6
36604
+ },
36605
+ barCategoryGap: "25%",
36606
+ children: [
36607
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
36608
+ /* @__PURE__ */ jsxRuntime.jsx(
36609
+ recharts.XAxis,
36610
+ {
36611
+ xAxisId: "default",
36612
+ dataKey: "hour",
36613
+ tick: { fontSize: xAxisConfig.tickFont },
36614
+ interval: xAxisConfig.interval,
36615
+ angle: xAxisConfig.angle,
36616
+ textAnchor: "end",
36617
+ tickMargin: xAxisConfig.tickMargin,
36618
+ height: xAxisConfig.height
35793
36619
  }
35794
- }
35795
- ),
35796
- /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { yAxisId: "idle", domain: [0, 60], hide: true }),
35797
- /* @__PURE__ */ jsxRuntime.jsx(
35798
- recharts.Tooltip,
35799
- {
35800
- cursor: false,
35801
- contentStyle: {
35802
- backgroundColor: "white",
35803
- border: "none",
35804
- borderRadius: "12px",
35805
- boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
35806
- padding: "0",
35807
- fontSize: "14px"
35808
- },
35809
- content: (props) => {
35810
- if (!props.active || !props.payload || props.payload.length === 0)
35811
- return null;
35812
- const data2 = props.payload[0].payload;
35813
- const idlePeriods = showIdleTime ? getHourlyIdlePeriods({
35814
- idleArray: data2.idleArray,
35815
- shiftStart,
35816
- hourIndex: Number.isFinite(data2.hourIndex) ? data2.hourIndex : 0
35817
- }) : [];
35818
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-xl shadow-xl border border-gray-100 p-4 min-w-[220px]", children: [
35819
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold text-gray-900 text-sm", children: data2.timeRange }) }),
35820
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
35821
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
35822
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: "Output" }),
35823
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-gray-900 text-sm", children: [
35824
- Math.round(data2.output),
35825
- " units"
35826
- ] })
35827
- ] }),
35828
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
35829
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: "Target" }),
35830
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-gray-600 text-sm", children: [
35831
- Math.round(data2.target),
35832
- " units"
35833
- ] })
35834
- ] }),
35835
- showIdleTime && data2.idleMinutes > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
35836
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-100 pt-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
35837
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: "Idle Time" }),
35838
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-orange-600 text-sm", children: [
35839
- data2.idleMinutes,
35840
- " minutes"
36620
+ ),
36621
+ /* @__PURE__ */ jsxRuntime.jsx(
36622
+ recharts.XAxis,
36623
+ {
36624
+ xAxisId: "sku",
36625
+ type: "number",
36626
+ dataKey: "skuIndex",
36627
+ domain: [0, Math.max(SHIFT_DURATION, 0)],
36628
+ hide: true,
36629
+ allowDataOverflow: true
36630
+ }
36631
+ ),
36632
+ /* @__PURE__ */ jsxRuntime.jsx(
36633
+ recharts.YAxis,
36634
+ {
36635
+ yAxisId: "default",
36636
+ tickMargin: 8,
36637
+ width: 48,
36638
+ domain: [0, maxYValue],
36639
+ ticks: generateYAxisTicks(),
36640
+ tickFormatter: (value) => value,
36641
+ tick: (props) => {
36642
+ const { x, y, payload } = props;
36643
+ return /* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${x},${y})`, children: /* @__PURE__ */ jsxRuntime.jsx(
36644
+ "text",
36645
+ {
36646
+ x: -2,
36647
+ y: 0,
36648
+ dy: 4,
36649
+ textAnchor: "end",
36650
+ fill: "#666",
36651
+ fontSize: 12,
36652
+ children: payload.value
36653
+ },
36654
+ `tick-${payload.value}-${x}-${y}`
36655
+ ) });
36656
+ }
36657
+ }
36658
+ ),
36659
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { yAxisId: "idle", domain: [0, 60], hide: true }),
36660
+ /* @__PURE__ */ jsxRuntime.jsx(
36661
+ recharts.Tooltip,
36662
+ {
36663
+ cursor: { fill: "#f1f5f9" },
36664
+ contentStyle: { backgroundColor: "transparent", border: "none", padding: 0 },
36665
+ content: (props) => {
36666
+ if (!props.active || !props.payload || props.payload.length === 0)
36667
+ return null;
36668
+ const data2 = props.payload[0].payload;
36669
+ const idlePeriods = showIdleTime ? getHourlyIdlePeriods({
36670
+ idleArray: data2.idleArray,
36671
+ shiftStart,
36672
+ hourIndex: Number.isFinite(data2.hourIndex) ? data2.hourIndex : 0
36673
+ }) : [];
36674
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white/95 backdrop-blur-md border border-slate-200/60 shadow-xl shadow-slate-200/40 rounded-xl p-4 min-w-[240px] text-slate-700", children: [
36675
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between mb-4 pb-3 border-b border-slate-100", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold text-slate-900 text-sm tracking-tight", children: data2.timeRange }) }),
36676
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
36677
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
36678
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500 font-medium tracking-wide", children: "Output" }),
36679
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-bold text-slate-900 text-sm", children: [
36680
+ Math.round(data2.output),
36681
+ " ",
36682
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-400 font-normal text-xs ml-0.5", children: "units" })
36683
+ ] })
36684
+ ] }),
36685
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
36686
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500 font-medium tracking-wide", children: "Target" }),
36687
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-bold text-slate-700 text-sm", children: [
36688
+ Math.round(data2.target),
36689
+ " ",
36690
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-400 font-normal text-xs ml-0.5", children: "units" })
36691
+ ] })
36692
+ ] }),
36693
+ showIdleTime && data2.idleMinutes > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
36694
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-3 mt-3 border-t border-slate-100", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
36695
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-slate-500 font-medium tracking-wide", children: "Idle Time" }),
36696
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-bold text-orange-600 text-sm flex items-center gap-1.5", children: [
36697
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-orange-500 shadow-[0_0_6px_rgba(249,115,22,0.6)] animate-pulse" }),
36698
+ data2.idleMinutes,
36699
+ " ",
36700
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-orange-500/70 font-normal text-xs ml-0.5", children: "min" })
36701
+ ] })
36702
+ ] }) }),
36703
+ idlePeriods.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 bg-slate-50/80 rounded-lg p-3 border border-slate-100/50", children: [
36704
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold text-slate-400 text-[10px] mb-2.5 uppercase tracking-wider", children: "Idle Periods" }),
36705
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2.5 max-h-32 overflow-y-auto pr-1 custom-scrollbar", children: idlePeriods.map((period, index) => {
36706
+ return /* @__PURE__ */ jsxRuntime.jsxs(
36707
+ "div",
36708
+ {
36709
+ className: "flex items-start gap-2.5 text-xs",
36710
+ children: [
36711
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-[5px] w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0 shadow-[0_0_4px_rgba(251,146,60,0.5)]" }),
36712
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-700 font-medium tracking-tight", children: period.duration === 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
36713
+ period.startTime,
36714
+ " ",
36715
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-slate-400 font-normal ml-1", children: [
36716
+ "(",
36717
+ period.duration,
36718
+ "m)"
36719
+ ] })
36720
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
36721
+ period.startTime,
36722
+ " ",
36723
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-400 mx-0.5", children: "\u2192" }),
36724
+ " ",
36725
+ period.endTime,
36726
+ " ",
36727
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-slate-400 font-normal ml-1", children: [
36728
+ "(",
36729
+ period.duration,
36730
+ "m)"
36731
+ ] })
36732
+ ] }) })
36733
+ ]
36734
+ },
36735
+ index
36736
+ );
36737
+ }) })
35841
36738
  ] })
35842
- ] }) }),
35843
- idlePeriods.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 bg-gray-50 rounded-lg p-2.5", children: [
35844
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium text-gray-700 text-xs mb-2", children: "Idle periods:" }),
35845
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idlePeriods.map((period, index) => {
35846
- return /* @__PURE__ */ jsxRuntime.jsxs(
35847
- "div",
35848
- {
35849
- className: "text-gray-600 flex items-center gap-2 text-xs",
35850
- children: [
35851
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0" }),
35852
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: period.duration === 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
35853
- period.startTime,
35854
- " (",
35855
- period.duration,
35856
- " min)"
35857
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
35858
- period.startTime,
35859
- " -",
35860
- " ",
35861
- period.endTime,
35862
- " (",
35863
- period.duration,
35864
- " mins)"
35865
- ] }) })
35866
- ]
35867
- },
35868
- index
35869
- );
35870
- }) })
35871
36739
  ] })
35872
36740
  ] })
35873
- ] })
35874
- ] });
35875
- },
35876
- animationDuration: 200
35877
- }
35878
- ),
35879
- /* @__PURE__ */ jsxRuntime.jsx(recharts.Customized, { component: renderTargetLine }),
35880
- /* @__PURE__ */ jsxRuntime.jsx(recharts.Customized, { component: renderSkuTimelineRail }),
35881
- /* @__PURE__ */ jsxRuntime.jsxs(
35882
- recharts.Bar,
35883
- {
35884
- xAxisId: "default",
35885
- dataKey: "output",
35886
- yAxisId: "default",
35887
- maxBarSize: 35,
35888
- radius: [10, 10, 0, 0],
35889
- isAnimationActive: false,
35890
- children: [
35891
- chartData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(
35892
- recharts.Cell,
35893
- {
35894
- fill: entry.color,
35895
- stroke: "transparent",
35896
- strokeWidth: 0,
35897
- style: {
35898
- filter: entry.isHighlighted ? "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)" : "brightness(1)",
35899
- transform: entry.isHighlighted ? "translateY(-4px)" : "translateY(0)",
35900
- opacity: entry.isDimmed ? 0.4 : 1,
35901
- transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
35902
- cursor: "pointer"
35903
- },
35904
- onMouseEnter: (e) => {
35905
- const target = e.target;
35906
- target.style.filter = "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)";
35907
- target.style.transform = "translateY(-4px)";
35908
- target.style.opacity = "1";
36741
+ ] });
36742
+ },
36743
+ animationDuration: 200
36744
+ }
36745
+ ),
36746
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Customized, { component: renderTargetLine }),
36747
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Customized, { component: renderSkuTimelineRail }),
36748
+ /* @__PURE__ */ jsxRuntime.jsxs(
36749
+ recharts.Bar,
36750
+ {
36751
+ xAxisId: "default",
36752
+ dataKey: "output",
36753
+ yAxisId: "default",
36754
+ maxBarSize: 35,
36755
+ radius: [10, 10, 0, 0],
36756
+ isAnimationActive: false,
36757
+ children: [
36758
+ chartData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(
36759
+ recharts.Cell,
36760
+ {
36761
+ fill: entry.color,
36762
+ stroke: "transparent",
36763
+ strokeWidth: 0,
36764
+ style: {
36765
+ filter: entry.isHighlighted ? "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)" : "brightness(1)",
36766
+ transform: entry.isHighlighted ? "translateY(-4px)" : "translateY(0)",
36767
+ opacity: entry.isDimmed ? 0.4 : 1,
36768
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
36769
+ cursor: "pointer"
36770
+ },
36771
+ onMouseEnter: (e) => {
36772
+ const target = e.target;
36773
+ target.style.filter = "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)";
36774
+ target.style.transform = "translateY(-4px)";
36775
+ target.style.opacity = "1";
36776
+ },
36777
+ onMouseLeave: (e) => {
36778
+ const target = e.target;
36779
+ target.style.filter = entry.isHighlighted ? "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)" : "brightness(1)";
36780
+ target.style.transform = entry.isHighlighted ? "translateY(-4px)" : "translateY(0)";
36781
+ target.style.opacity = entry.isDimmed ? "0.4" : "1";
36782
+ }
35909
36783
  },
35910
- onMouseLeave: (e) => {
35911
- const target = e.target;
35912
- target.style.filter = entry.isHighlighted ? "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)" : "brightness(1)";
35913
- target.style.transform = entry.isHighlighted ? "translateY(-4px)" : "translateY(0)";
35914
- target.style.opacity = entry.isDimmed ? "0.4" : "1";
36784
+ `cell-${index}`
36785
+ )),
36786
+ /* @__PURE__ */ jsxRuntime.jsx(
36787
+ recharts.LabelList,
36788
+ {
36789
+ dataKey: "originalOutput",
36790
+ position: "top",
36791
+ content: (props) => {
36792
+ const { x, y, width, value, payload } = props;
36793
+ const actualValue = payload?.originalOutput || value;
36794
+ if (!actualValue || actualValue === 0) return null;
36795
+ return /* @__PURE__ */ jsxRuntime.jsx(
36796
+ "text",
36797
+ {
36798
+ x: x + width / 2,
36799
+ y: y - 8,
36800
+ textAnchor: "middle",
36801
+ fontSize: "12",
36802
+ fontWeight: "600",
36803
+ fill: "#374151",
36804
+ style: {
36805
+ opacity: 1,
36806
+ pointerEvents: "none",
36807
+ transition: "none"
36808
+ },
36809
+ children: Math.round(actualValue)
36810
+ }
36811
+ );
36812
+ }
35915
36813
  }
35916
- },
35917
- `cell-${index}`
35918
- )),
35919
- /* @__PURE__ */ jsxRuntime.jsx(
35920
- recharts.LabelList,
35921
- {
35922
- dataKey: "originalOutput",
35923
- position: "top",
35924
- content: (props) => {
35925
- const { x, y, width, value, payload } = props;
35926
- const actualValue = payload?.originalOutput || value;
35927
- if (!actualValue || actualValue === 0) return null;
35928
- return /* @__PURE__ */ jsxRuntime.jsx(
35929
- "text",
35930
- {
35931
- x: x + width / 2,
35932
- y: y - 8,
35933
- textAnchor: "middle",
35934
- fontSize: "12",
35935
- fontWeight: "600",
35936
- fill: "#374151",
35937
- style: {
35938
- opacity: 1,
35939
- pointerEvents: "none",
35940
- transition: "none"
35941
- },
35942
- children: Math.round(actualValue)
35943
- }
35944
- );
36814
+ )
36815
+ ]
36816
+ }
36817
+ ),
36818
+ IdleBar,
36819
+ /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
36820
+ "pattern",
36821
+ {
36822
+ id: "idlePattern",
36823
+ patternUnits: "userSpaceOnUse",
36824
+ width: "4",
36825
+ height: "4",
36826
+ children: [
36827
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "4", height: "4", fill: "#4b5563", opacity: "0.6" }),
36828
+ /* @__PURE__ */ jsxRuntime.jsx(
36829
+ "path",
36830
+ {
36831
+ d: "M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2",
36832
+ stroke: "#374151",
36833
+ strokeWidth: "0.8",
36834
+ opacity: "0.8"
35945
36835
  }
35946
- }
35947
- )
35948
- ]
35949
- }
35950
- ),
35951
- IdleBar,
35952
- /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
35953
- "pattern",
35954
- {
35955
- id: "idlePattern",
35956
- patternUnits: "userSpaceOnUse",
35957
- width: "4",
35958
- height: "4",
35959
- children: [
35960
- /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "4", height: "4", fill: "#4b5563", opacity: "0.6" }),
35961
- /* @__PURE__ */ jsxRuntime.jsx(
35962
- "path",
35963
- {
35964
- d: "M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2",
35965
- stroke: "#374151",
35966
- strokeWidth: "0.8",
35967
- opacity: "0.8"
35968
- }
35969
- )
35970
- ]
35971
- }
35972
- ) })
35973
- ]
35974
- }
35975
- ) }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center bg-gray-50 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500 text-sm", children: "Loading chart..." }) }),
36836
+ )
36837
+ ]
36838
+ }
36839
+ ) })
36840
+ ]
36841
+ }
36842
+ ) }),
36843
+ hoveredSkuRailLabel && /* @__PURE__ */ jsxRuntime.jsx(
36844
+ "div",
36845
+ {
36846
+ className: "absolute z-50 pointer-events-none transform -translate-x-1/2 -translate-y-full transition-opacity duration-200",
36847
+ style: {
36848
+ left: hoveredSkuRailLabel.centerX,
36849
+ top: hoveredSkuRailLabel.railY - 12
36850
+ },
36851
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white/95 backdrop-blur-md border border-slate-200/80 shadow-xl shadow-slate-200/50 rounded-lg px-3 py-2 flex flex-col min-w-[100px] max-w-[280px]", children: [
36852
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-bold tracking-wider text-blue-500 uppercase leading-none mb-1", children: "SKU" }),
36853
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[13px] font-semibold text-slate-800 whitespace-normal break-words leading-snug", children: hoveredSkuRailLabel.label })
36854
+ ] })
36855
+ }
36856
+ )
36857
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center bg-gray-50 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500 text-sm", children: "Loading chart..." }) }),
35976
36858
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-none pt-2", children: renderLegend() })
35977
36859
  ]
35978
36860
  }
@@ -35990,6 +36872,38 @@ var HourlyOutputChart = React144__namespace.default.memo(
35990
36872
  if (!prevProps.data.every((val, idx) => val === nextProps.data[idx])) {
35991
36873
  return false;
35992
36874
  }
36875
+ const prevHasHourlyTargetOutputProp = prevProps.hourlyTargetOutput !== void 0;
36876
+ const nextHasHourlyTargetOutputProp = nextProps.hourlyTargetOutput !== void 0;
36877
+ if (prevHasHourlyTargetOutputProp !== nextHasHourlyTargetOutputProp) {
36878
+ return false;
36879
+ }
36880
+ if (prevProps.hourlyTargetOutput === null || nextProps.hourlyTargetOutput === null) {
36881
+ if (prevProps.hourlyTargetOutput !== nextProps.hourlyTargetOutput) {
36882
+ return false;
36883
+ }
36884
+ }
36885
+ const prevHourlyTargets = prevProps.hourlyTargetOutput || [];
36886
+ const nextHourlyTargets = nextProps.hourlyTargetOutput || [];
36887
+ if (prevHourlyTargets.length !== nextHourlyTargets.length) {
36888
+ return false;
36889
+ }
36890
+ for (let i = 0; i < prevHourlyTargets.length; i += 1) {
36891
+ if (prevHourlyTargets[i] !== nextHourlyTargets[i]) {
36892
+ return false;
36893
+ }
36894
+ }
36895
+ const prevShiftBreaks = prevProps.shiftBreaks || [];
36896
+ const nextShiftBreaks = nextProps.shiftBreaks || [];
36897
+ if (prevShiftBreaks.length !== nextShiftBreaks.length) {
36898
+ return false;
36899
+ }
36900
+ for (let i = 0; i < prevShiftBreaks.length; i += 1) {
36901
+ const prevBreak = prevShiftBreaks[i] || {};
36902
+ const nextBreak = nextShiftBreaks[i] || {};
36903
+ if (prevBreak.startTime !== nextBreak.startTime || prevBreak.endTime !== nextBreak.endTime || prevBreak.duration !== nextBreak.duration || prevBreak.remarks !== nextBreak.remarks) {
36904
+ return false;
36905
+ }
36906
+ }
35993
36907
  const prevIdle = prevProps.idleTimeHourly || {};
35994
36908
  const nextIdle = nextProps.idleTimeHourly || {};
35995
36909
  const prevKeys = Object.keys(prevIdle);
@@ -36177,7 +37091,6 @@ var VideoCard = React144__namespace.default.memo(({
36177
37091
  shouldPlay,
36178
37092
  onClick,
36179
37093
  onFatalError,
36180
- isVeryLowEfficiency = false,
36181
37094
  legend,
36182
37095
  cropping,
36183
37096
  canvasFps = 30,
@@ -36249,11 +37162,6 @@ var VideoCard = React144__namespace.default.memo(({
36249
37162
  }
36250
37163
  },
36251
37164
  children: [
36252
- isVeryLowEfficiency && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute ${compact ? "top-0.5 left-1" : "top-1 left-2"} z-30`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
36253
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute -inset-1 bg-red-400/50 rounded-full blur-sm animate-pulse" }),
36254
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute -inset-0.5 bg-red-500/30 rounded-full blur-md animate-ping [animation-duration:1.5s]" }),
36255
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `bg-[#E34329] ${compact ? "w-4 h-4" : "w-5 h-5"} rounded-full flex items-center justify-center text-white font-bold ${compact ? "text-[10px]" : "text-xs"} shadow-lg ring-2 ring-red-400/40 border border-red-400/80 animate-pulse`, children: "!" })
36256
- ] }) }),
36257
37165
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full overflow-hidden bg-black", children: [
36258
37166
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black z-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "animate-pulse flex flex-col items-center", children: [
36259
37167
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Camera, { className: `w-5 h-5 sm:${compact ? "w-4 h-4" : "w-6 h-6"} text-gray-500` }),
@@ -36663,7 +37571,6 @@ var VideoGridView = React144__namespace.default.memo(({
36663
37571
  const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
36664
37572
  const workspaceKey = `${workspace.line_id || "unknown"}-${workspaceId}`;
36665
37573
  const isVisible = visibleWorkspaces.has(workspaceId);
36666
- const isVeryLowEfficiency = workspace.show_exclamation ?? (workspace.efficiency < 50 && workspace.efficiency >= 10);
36667
37574
  const workspaceCropping = getWorkspaceCropping(workspaceId, workspace.workspace_name);
36668
37575
  const workspaceStream = videoStreamsByWorkspaceId?.[workspaceId];
36669
37576
  const lastSeenLabel = workspace.workspace_uuid ? lastSeenByWorkspaceId[workspace.workspace_uuid]?.timeSinceLastUpdate : void 0;
@@ -36680,7 +37587,6 @@ var VideoGridView = React144__namespace.default.memo(({
36680
37587
  workspaceId,
36681
37588
  workspaceKey,
36682
37589
  isVisible,
36683
- isVeryLowEfficiency,
36684
37590
  workspaceCropping,
36685
37591
  fallbackUrl,
36686
37592
  hlsUrl,
@@ -36728,7 +37634,6 @@ var VideoGridView = React144__namespace.default.memo(({
36728
37634
  isR2Stream: card.isR2Stream,
36729
37635
  fallbackUrl: card.fallbackUrl
36730
37636
  }),
36731
- isVeryLowEfficiency: card.isVeryLowEfficiency,
36732
37637
  legend: effectiveLegend,
36733
37638
  cropping: card.workspaceCropping,
36734
37639
  canvasFps: effectiveCanvasFps,
@@ -37648,223 +38553,6 @@ var UptimeLineChartComponent = ({ points, className = "" }) => {
37648
38553
  ] }) }) });
37649
38554
  };
37650
38555
  var UptimeLineChart = React144__namespace.default.memo(UptimeLineChartComponent);
37651
- var padTime = (value) => value.toString().padStart(2, "0");
37652
- var parseTime = (timeValue) => {
37653
- if (!timeValue) return null;
37654
- const [hourPart, minutePart] = timeValue.split(":");
37655
- const hour = Number.parseInt(hourPart, 10);
37656
- const minute = Number.parseInt(minutePart ?? "0", 10);
37657
- if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null;
37658
- return { hour, minute };
37659
- };
37660
- var normalizeIdleTimeHourly = (idleTimeHourly) => {
37661
- if (!idleTimeHourly || typeof idleTimeHourly !== "object") {
37662
- return {};
37663
- }
37664
- return Object.fromEntries(
37665
- Object.entries(idleTimeHourly).map(([key, value]) => {
37666
- if (Array.isArray(value)) return [key, value];
37667
- if (value && Array.isArray(value.values)) {
37668
- return [key, value.values];
37669
- }
37670
- return [key, []];
37671
- })
37672
- );
37673
- };
37674
- var interpretIdleValue = (value) => {
37675
- if (value === 1 || value === "1") return "idle";
37676
- if (value === 0 || value === "0") return "active";
37677
- if (value === "x" || value === null || value === void 0) return "unknown";
37678
- return "unknown";
37679
- };
37680
- var getShiftDurationMinutes = (shiftStart, shiftEnd) => {
37681
- const start = parseTime(shiftStart);
37682
- const end = parseTime(shiftEnd);
37683
- if (!start || !end) return null;
37684
- let duration = end.hour * 60 + end.minute - (start.hour * 60 + start.minute);
37685
- if (duration <= 0) {
37686
- duration += 24 * 60;
37687
- }
37688
- return duration > 0 ? duration : null;
37689
- };
37690
- var getShiftElapsedMinutes = ({
37691
- shiftStart,
37692
- shiftEnd,
37693
- shiftDate,
37694
- timezone,
37695
- now: now4 = /* @__PURE__ */ new Date()
37696
- }) => {
37697
- if (!shiftDate || !timezone) return null;
37698
- const startTime = parseTime(shiftStart);
37699
- const endTime = parseTime(shiftEnd);
37700
- if (!startTime || !endTime) return null;
37701
- const shiftStartDate = dateFnsTz.fromZonedTime(
37702
- `${shiftDate}T${padTime(startTime.hour)}:${padTime(startTime.minute)}:00`,
37703
- timezone
37704
- );
37705
- let shiftEndDate = dateFnsTz.fromZonedTime(
37706
- `${shiftDate}T${padTime(endTime.hour)}:${padTime(endTime.minute)}:00`,
37707
- timezone
37708
- );
37709
- if (shiftEndDate <= shiftStartDate) {
37710
- shiftEndDate = dateFns.addDays(shiftEndDate, 1);
37711
- }
37712
- const shiftMinutes = Math.max(dateFns.differenceInMinutes(shiftEndDate, shiftStartDate), 0);
37713
- if (shiftMinutes <= 0) return null;
37714
- const elapsed = dateFns.differenceInMinutes(now4, shiftStartDate);
37715
- return Math.min(Math.max(elapsed, 0), shiftMinutes);
37716
- };
37717
- var maskFutureHourlySeries = ({
37718
- data,
37719
- shiftStart,
37720
- shiftEnd,
37721
- shiftDate,
37722
- timezone,
37723
- now: now4 = /* @__PURE__ */ new Date()
37724
- }) => {
37725
- if (!Array.isArray(data)) {
37726
- return [];
37727
- }
37728
- const normalizedData = data.map((value) => typeof value === "number" && Number.isFinite(value) ? value : null);
37729
- if (!normalizedData.length) {
37730
- return normalizedData;
37731
- }
37732
- const shiftMinutes = getShiftDurationMinutes(shiftStart, shiftEnd);
37733
- const elapsedMinutes = getShiftElapsedMinutes({
37734
- shiftStart,
37735
- shiftEnd,
37736
- shiftDate,
37737
- timezone,
37738
- now: now4
37739
- });
37740
- if (shiftMinutes === null || elapsedMinutes === null || elapsedMinutes >= shiftMinutes) {
37741
- return normalizedData;
37742
- }
37743
- return normalizedData.map((value, index) => {
37744
- const slotStartMinutes = index * 60;
37745
- return slotStartMinutes > elapsedMinutes ? null : value;
37746
- });
37747
- };
37748
- var buildUptimeSeries = ({
37749
- idleTimeHourly,
37750
- shiftStart,
37751
- shiftEnd,
37752
- shiftDate,
37753
- timezone,
37754
- elapsedMinutes
37755
- }) => {
37756
- const normalizedIdle = normalizeIdleTimeHourly(idleTimeHourly || {});
37757
- const hasIdleData = Object.keys(normalizedIdle).length > 0;
37758
- if (!hasIdleData || !shiftDate || !timezone) {
37759
- return {
37760
- points: [],
37761
- activeMinutes: 0,
37762
- idleMinutes: 0,
37763
- availableMinutes: 0,
37764
- shiftMinutes: 0,
37765
- elapsedMinutes: 0,
37766
- hasData: false
37767
- };
37768
- }
37769
- const startTime = parseTime(shiftStart);
37770
- const endTime = parseTime(shiftEnd);
37771
- if (!startTime || !endTime) {
37772
- return {
37773
- points: [],
37774
- activeMinutes: 0,
37775
- idleMinutes: 0,
37776
- availableMinutes: 0,
37777
- shiftMinutes: 0,
37778
- elapsedMinutes: 0,
37779
- hasData: false
37780
- };
37781
- }
37782
- const shiftStartDate = dateFnsTz.fromZonedTime(
37783
- `${shiftDate}T${padTime(startTime.hour)}:${padTime(startTime.minute)}:00`,
37784
- timezone
37785
- );
37786
- let shiftEndDate = dateFnsTz.fromZonedTime(
37787
- `${shiftDate}T${padTime(endTime.hour)}:${padTime(endTime.minute)}:00`,
37788
- timezone
37789
- );
37790
- if (shiftEndDate <= shiftStartDate) {
37791
- shiftEndDate = dateFns.addDays(shiftEndDate, 1);
37792
- }
37793
- const shiftMinutes = Math.max(dateFns.differenceInMinutes(shiftEndDate, shiftStartDate), 0);
37794
- if (shiftMinutes <= 0) {
37795
- return {
37796
- points: [],
37797
- activeMinutes: 0,
37798
- idleMinutes: 0,
37799
- availableMinutes: 0,
37800
- shiftMinutes: 0,
37801
- elapsedMinutes: 0,
37802
- hasData: false
37803
- };
37804
- }
37805
- const elapsedMinutesClamped = Number.isFinite(elapsedMinutes) ? Math.min(Math.max(Math.floor(elapsedMinutes ?? 0), 0), shiftMinutes) : shiftMinutes;
37806
- const points = [];
37807
- let activeMinutes = 0;
37808
- let idleMinutes = 0;
37809
- for (let minuteIndex = 0; minuteIndex < shiftMinutes; minuteIndex += 1) {
37810
- const minuteDate = dateFns.addMinutes(shiftStartDate, minuteIndex);
37811
- const timeLabel = dateFnsTz.formatInTimeZone(minuteDate, timezone, "h:mm a");
37812
- if (minuteIndex >= elapsedMinutesClamped) {
37813
- points.push({
37814
- minuteIndex,
37815
- timeLabel,
37816
- uptime: null,
37817
- status: "unknown"
37818
- });
37819
- continue;
37820
- }
37821
- const hourKey = dateFnsTz.formatInTimeZone(minuteDate, timezone, "H");
37822
- const minuteKey = Number.parseInt(dateFnsTz.formatInTimeZone(minuteDate, timezone, "m"), 10);
37823
- const hourBucket = normalizedIdle[hourKey] || [];
37824
- const value = Array.isArray(hourBucket) ? hourBucket[minuteKey] : void 0;
37825
- const status = interpretIdleValue(value);
37826
- if (status === "active") activeMinutes += 1;
37827
- if (status === "idle") idleMinutes += 1;
37828
- points.push({
37829
- minuteIndex,
37830
- timeLabel,
37831
- uptime: status === "active" ? 1 : status === "idle" ? 0 : null,
37832
- status
37833
- });
37834
- }
37835
- return {
37836
- points,
37837
- activeMinutes,
37838
- idleMinutes,
37839
- availableMinutes: activeMinutes + idleMinutes,
37840
- shiftMinutes,
37841
- elapsedMinutes: elapsedMinutesClamped,
37842
- hasData: activeMinutes + idleMinutes > 0
37843
- };
37844
- };
37845
- var getUptimeUtilizationPercent = (shift) => {
37846
- const efficiency = shift.efficiency;
37847
- if (Number.isFinite(efficiency)) {
37848
- return Math.round(Math.max(0, Math.min(100, Number(efficiency))));
37849
- }
37850
- const idleSeconds = Number.isFinite(shift.idleTime) ? Number(shift.idleTime) : 0;
37851
- const activeSeconds = Number.isFinite(shift.activeTimeSeconds) ? Number(shift.activeTimeSeconds) : null;
37852
- let availableSeconds = Number.isFinite(shift.availableTimeSeconds) ? Number(shift.availableTimeSeconds) : null;
37853
- if (availableSeconds === null) {
37854
- if ((activeSeconds ?? 0) > 0 || idleSeconds > 0) {
37855
- availableSeconds = (activeSeconds ?? 0) + idleSeconds;
37856
- } else {
37857
- return 0;
37858
- }
37859
- }
37860
- if (availableSeconds <= 0) return 0;
37861
- const clampedIdleSeconds = Math.min(Math.max(idleSeconds, 0), availableSeconds);
37862
- const productiveSeconds = Math.max(
37863
- activeSeconds ?? availableSeconds - clampedIdleSeconds,
37864
- 0
37865
- );
37866
- return Math.round(productiveSeconds / availableSeconds * 100);
37867
- };
37868
38556
  var getTimeFromTimeString = (timeStr) => {
37869
38557
  if (!timeStr) {
37870
38558
  return { hour: 0, minute: 0, decimalHour: 0 };
@@ -49930,261 +50618,6 @@ Underperforming Workspaces: ${lineInfo.metrics.underperforming_workspaces} / ${l
49930
50618
  }
49931
50619
  );
49932
50620
  };
49933
-
49934
- // src/lib/utils/hourlyTargets.ts
49935
- var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
49936
- var MINUTES_PER_DAY = 24 * 60;
49937
- var parseTimeToMinutes2 = (timeString) => {
49938
- const normalized = stripSeconds2(timeString || "");
49939
- if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
49940
- const [hours, minutes] = normalized.split(":").map(Number);
49941
- return hours * 60 + minutes;
49942
- };
49943
- var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
49944
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
49945
- if (!Number.isFinite(shiftStartMinutes)) return [];
49946
- const normalizedBreaks = [];
49947
- for (const entry of breaks) {
49948
- const startRaw = parseTimeToMinutes2(entry.startTime);
49949
- const endRaw = parseTimeToMinutes2(entry.endTime);
49950
- if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
49951
- let start = startRaw;
49952
- let end = endRaw;
49953
- if (end <= start) {
49954
- end += 24 * 60;
49955
- }
49956
- if (start < shiftStartMinutes) {
49957
- start += 24 * 60;
49958
- end += 24 * 60;
49959
- }
49960
- const label = entry.remarks?.trim() || "Break";
49961
- normalizedBreaks.push({ start, end, label });
49962
- }
49963
- return normalizedBreaks;
49964
- };
49965
- var roundTarget = (value, mode) => {
49966
- if (!Number.isFinite(value)) return 0;
49967
- switch (mode) {
49968
- case "floor":
49969
- return Math.floor(value);
49970
- case "ceil":
49971
- return Math.ceil(value);
49972
- case "round":
49973
- default:
49974
- return Math.round(value);
49975
- }
49976
- };
49977
- var formatDateKey = (date) => {
49978
- const year = date.getUTCFullYear();
49979
- const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
49980
- const day = `${date.getUTCDate()}`.padStart(2, "0");
49981
- return `${year}-${month}-${day}`;
49982
- };
49983
- var shiftDateKey = (dateKey, deltaDays) => {
49984
- const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
49985
- const year = Number.isFinite(yearPart) ? yearPart : 1970;
49986
- const month = Number.isFinite(monthPart) ? monthPart : 1;
49987
- const day = Number.isFinite(dayPart) ? dayPart : 1;
49988
- const date = new Date(Date.UTC(year, month - 1, day));
49989
- date.setUTCDate(date.getUTCDate() + deltaDays);
49990
- return formatDateKey(date);
49991
- };
49992
- var getZonedNowSnapshot = (timeZone, now4) => {
49993
- const formatter = new Intl.DateTimeFormat("en-US", {
49994
- timeZone,
49995
- year: "numeric",
49996
- month: "2-digit",
49997
- day: "2-digit",
49998
- hour: "2-digit",
49999
- minute: "2-digit",
50000
- hourCycle: "h23"
50001
- });
50002
- const parts = formatter.formatToParts(now4).reduce((acc, part) => {
50003
- if (part.type !== "literal") {
50004
- acc[part.type] = part.value;
50005
- }
50006
- return acc;
50007
- }, {});
50008
- const year = Number(parts.year);
50009
- const month = Number(parts.month);
50010
- const day = Number(parts.day);
50011
- const hour = Number(parts.hour);
50012
- const minute = Number(parts.minute);
50013
- return {
50014
- dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
50015
- minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
50016
- };
50017
- };
50018
- var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
50019
- var buildHourlyIntervals = ({
50020
- shiftStart,
50021
- shiftEnd,
50022
- bucketMinutes = 60,
50023
- fallbackHours = 11
50024
- }) => {
50025
- const startMinutes = parseTimeToMinutes2(shiftStart);
50026
- if (!Number.isFinite(startMinutes)) return [];
50027
- const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
50028
- let totalMinutes;
50029
- const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
50030
- if (!Number.isFinite(endRaw)) {
50031
- totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
50032
- } else {
50033
- let endMinutes = endRaw;
50034
- if (endMinutes <= startMinutes) {
50035
- endMinutes += 24 * 60;
50036
- }
50037
- totalMinutes = endMinutes - startMinutes;
50038
- }
50039
- if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
50040
- const count = Math.ceil(totalMinutes / bucket);
50041
- const shiftEndMinutes = startMinutes + totalMinutes;
50042
- const intervals = [];
50043
- for (let i = 0; i < count; i += 1) {
50044
- const start = startMinutes + i * bucket;
50045
- const end = Math.min(start + bucket, shiftEndMinutes);
50046
- const minutes = Math.max(0, end - start);
50047
- if (minutes <= 0) continue;
50048
- intervals.push({ start, end, minutes });
50049
- }
50050
- return intervals;
50051
- };
50052
- var computeBreakMinutesByInterval = ({
50053
- intervals,
50054
- shiftStart,
50055
- breaks
50056
- }) => {
50057
- if (!intervals.length || !breaks.length) return intervals.map(() => 0);
50058
- const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
50059
- return intervals.map((interval) => {
50060
- if (!normalizedBreaks.length) return 0;
50061
- let total = 0;
50062
- for (const brk of normalizedBreaks) {
50063
- const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
50064
- total += overlap;
50065
- if (total >= interval.minutes) return interval.minutes;
50066
- }
50067
- return Math.min(interval.minutes, total);
50068
- });
50069
- };
50070
- var computeBreakRemarksByInterval = ({
50071
- intervals,
50072
- shiftStart,
50073
- breaks
50074
- }) => {
50075
- if (!intervals.length || !breaks.length) return intervals.map(() => "");
50076
- const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
50077
- return intervals.map((interval) => {
50078
- 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);
50079
- return labels.join(", ");
50080
- });
50081
- };
50082
- var computeEffectiveTargets = ({
50083
- intervals,
50084
- breakMinutes,
50085
- pphThreshold,
50086
- rounding = "round"
50087
- }) => {
50088
- return intervals.map((interval, idx) => {
50089
- const intervalMinutes = Number(interval?.minutes) || 0;
50090
- const breakMins = Number(breakMinutes?.[idx]) || 0;
50091
- const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
50092
- if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
50093
- if (plannedWorkMinutes <= 0) return 0;
50094
- return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
50095
- });
50096
- };
50097
- var buildHourlyTargetPlan = ({
50098
- shiftStart,
50099
- shiftEnd,
50100
- breaks = [],
50101
- pphThreshold,
50102
- bucketMinutes = 60,
50103
- fallbackHours = 11,
50104
- rounding = "round"
50105
- }) => {
50106
- const intervals = buildHourlyIntervals({
50107
- shiftStart,
50108
- shiftEnd,
50109
- bucketMinutes,
50110
- fallbackHours
50111
- });
50112
- const breakMinutes = computeBreakMinutesByInterval({
50113
- intervals,
50114
- shiftStart,
50115
- breaks
50116
- });
50117
- const breakRemarks = computeBreakRemarksByInterval({
50118
- intervals,
50119
- shiftStart,
50120
- breaks
50121
- });
50122
- const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
50123
- const targets = computeEffectiveTargets({
50124
- intervals,
50125
- breakMinutes,
50126
- pphThreshold,
50127
- rounding
50128
- });
50129
- return {
50130
- intervals,
50131
- breakMinutes,
50132
- breakRemarks,
50133
- productiveMinutes,
50134
- targets
50135
- };
50136
- };
50137
- var isHourlyIntervalComplete = ({
50138
- reportDate,
50139
- shiftStart,
50140
- shiftEnd,
50141
- interval,
50142
- timeZone = "Asia/Kolkata",
50143
- now: now4 = /* @__PURE__ */ new Date()
50144
- }) => {
50145
- if (!reportDate) return true;
50146
- const snapshot = getZonedNowSnapshot(timeZone, now4);
50147
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50148
- const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
50149
- const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
50150
- if (reportDate === snapshot.dateKey) {
50151
- return interval.end <= snapshot.minutesOfDay;
50152
- }
50153
- if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
50154
- return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
50155
- }
50156
- return reportDate < snapshot.dateKey;
50157
- };
50158
- var isShiftInProgressForReportDate = ({
50159
- reportDate,
50160
- shiftStart,
50161
- shiftEnd,
50162
- timeZone = "Asia/Kolkata",
50163
- now: now4 = /* @__PURE__ */ new Date()
50164
- }) => {
50165
- if (!reportDate || !shiftStart || !shiftEnd) return false;
50166
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50167
- const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
50168
- if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
50169
- return false;
50170
- }
50171
- let shiftEndMinutes = shiftEndMinutesRaw;
50172
- const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
50173
- if (wrapsMidnight) {
50174
- shiftEndMinutes += MINUTES_PER_DAY;
50175
- }
50176
- const snapshot = getZonedNowSnapshot(timeZone, now4);
50177
- let currentMinutes = null;
50178
- if (reportDate === snapshot.dateKey) {
50179
- currentMinutes = snapshot.minutesOfDay;
50180
- } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
50181
- currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
50182
- }
50183
- if (currentMinutes === null) {
50184
- return false;
50185
- }
50186
- return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
50187
- };
50188
50621
  var formatOperationalDateKey = (dateKey, options) => {
50189
50622
  const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50190
50623
  const year = Number.isFinite(yearPart) ? yearPart : 1970;
@@ -50195,7 +50628,7 @@ var formatOperationalDateKey = (dateKey, options) => {
50195
50628
  timeZone: "UTC"
50196
50629
  });
50197
50630
  };
50198
- var getSegmentMinutes = (isoString, timeZone, reportDate) => {
50631
+ var getSegmentMinutes2 = (isoString, timeZone, reportDate) => {
50199
50632
  const date = new Date(isoString);
50200
50633
  if (isNaN(date.getTime())) return NaN;
50201
50634
  const formatter = new Intl.DateTimeFormat("en-US", {
@@ -50216,13 +50649,13 @@ var getSegmentMinutes = (isoString, timeZone, reportDate) => {
50216
50649
  if (dateKey > reportDate) {
50217
50650
  const d1 = new Date(dateKey);
50218
50651
  const d2 = new Date(reportDate);
50219
- const diffDays = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
50220
- minutes += diffDays * 24 * 60;
50652
+ const diffDays2 = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
50653
+ minutes += diffDays2 * 24 * 60;
50221
50654
  } else if (dateKey < reportDate) {
50222
50655
  const d1 = new Date(dateKey);
50223
50656
  const d2 = new Date(reportDate);
50224
- const diffDays = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
50225
- minutes -= diffDays * 24 * 60;
50657
+ const diffDays2 = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
50658
+ minutes -= diffDays2 * 24 * 60;
50226
50659
  }
50227
50660
  return minutes;
50228
50661
  };
@@ -50632,7 +51065,7 @@ var LinePdfGenerator = ({
50632
51065
  const skuRemarksByIndex = {};
50633
51066
  if (lineInfo.metrics.sku_segments && lineInfo.metrics.sku_segments.length > 0) {
50634
51067
  lineInfo.metrics.sku_segments.forEach((segment, segmentIndex) => {
50635
- const segmentMinutes = getSegmentMinutes(segment.start_time, reportTimezone, lineInfo.date);
51068
+ const segmentMinutes = getSegmentMinutes2(segment.start_time, reportTimezone, lineInfo.date);
50636
51069
  if (!isNaN(segmentMinutes)) {
50637
51070
  const intervalIndex = hourlyTimeRanges.findIndex(
50638
51071
  (inv) => segmentMinutes >= inv.start && segmentMinutes < inv.end
@@ -52367,7 +52800,7 @@ var formatOperationalDateKey2 = (dateKey, options) => {
52367
52800
  timeZone: "UTC"
52368
52801
  });
52369
52802
  };
52370
- var getSegmentMinutes2 = (isoString, timeZone, reportDate) => {
52803
+ var getSegmentMinutes3 = (isoString, timeZone, reportDate) => {
52371
52804
  const date = new Date(isoString);
52372
52805
  if (isNaN(date.getTime())) return NaN;
52373
52806
  const formatter = new Intl.DateTimeFormat("en-US", {
@@ -52388,13 +52821,13 @@ var getSegmentMinutes2 = (isoString, timeZone, reportDate) => {
52388
52821
  if (dateKey > reportDate) {
52389
52822
  const d1 = new Date(dateKey);
52390
52823
  const d2 = new Date(reportDate);
52391
- const diffDays = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
52392
- minutes += diffDays * 24 * 60;
52824
+ const diffDays2 = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
52825
+ minutes += diffDays2 * 24 * 60;
52393
52826
  } else if (dateKey < reportDate) {
52394
52827
  const d1 = new Date(dateKey);
52395
52828
  const d2 = new Date(reportDate);
52396
- const diffDays = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
52397
- minutes -= diffDays * 24 * 60;
52829
+ const diffDays2 = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
52830
+ minutes -= diffDays2 * 24 * 60;
52398
52831
  }
52399
52832
  return minutes;
52400
52833
  };
@@ -52626,7 +53059,7 @@ var WorkspacePdfGenerator = ({
52626
53059
  const skuRemarksByIndex = {};
52627
53060
  if (workspace.sku_segments && workspace.sku_segments.length > 0) {
52628
53061
  workspace.sku_segments.forEach((segment, segmentIndex) => {
52629
- const segmentMinutes = getSegmentMinutes2(segment.start_time, reportTimezone, workspace.date);
53062
+ const segmentMinutes = getSegmentMinutes3(segment.start_time, reportTimezone, workspace.date);
52630
53063
  if (!isNaN(segmentMinutes)) {
52631
53064
  const intervalIndex = hourlyIntervals.findIndex(
52632
53065
  (inv) => segmentMinutes >= inv.start && segmentMinutes < inv.end
@@ -53468,12 +53901,10 @@ var formatPercentRange = (min, max) => {
53468
53901
  return `${format10(min)}-${format10(max)}%`;
53469
53902
  };
53470
53903
  var Legend5 = ({
53471
- useBottleneckLabel = false,
53472
53904
  legend,
53473
53905
  metricLabel = "Efficiency"
53474
53906
  }) => {
53475
53907
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
53476
- const exclamationLabel = useBottleneckLabel ? "Bottleneck" : "<50% efficiency";
53477
53908
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 sm:gap-4 text-xs font-medium text-slate-600", children: [
53478
53909
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "font-medium text-gray-700 hidden sm:block", children: [
53479
53910
  metricLabel,
@@ -53492,11 +53923,6 @@ var Legend5 = ({
53492
53923
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full bg-[#E34329]" }),
53493
53924
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatPercentRange(effectiveLegend.red_min, effectiveLegend.red_max) })
53494
53925
  ] })
53495
- ] }),
53496
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-slate-200 mx-1" }),
53497
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
53498
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center w-3 h-3 sm:w-4 sm:h-4 bg-[#E34329] rounded-full text-white font-bold text-[8px] sm:text-[10px]", children: "!" }),
53499
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: exclamationLabel })
53500
53926
  ] })
53501
53927
  ] });
53502
53928
  };
@@ -53610,7 +54036,6 @@ var WorkspaceGrid = React144__namespace.default.memo(({
53610
54036
  factoryView = "factory",
53611
54037
  line2Uuid = "line-2",
53612
54038
  className = "",
53613
- hasFlowBuffers = false,
53614
54039
  legend = DEFAULT_EFFICIENCY_LEGEND,
53615
54040
  videoSources = {},
53616
54041
  videoStreamsByWorkspaceId = {},
@@ -53654,7 +54079,7 @@ var WorkspaceGrid = React144__namespace.default.memo(({
53654
54079
  );
53655
54080
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col w-full h-full overflow-hidden bg-slate-50/50 ${className}`, children: [
53656
54081
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-none px-4 py-3 z-20 flex flex-row items-center justify-between gap-4", children: [
53657
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex bg-white/95 rounded-lg shadow-sm px-4 py-2 border border-slate-200/60 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) }) }),
54082
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex bg-white/95 rounded-lg shadow-sm px-4 py-2 border border-slate-200/60 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, metricLabel: legendMetricLabel }) }) }),
53658
54083
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-3 shrink-0", children: [
53659
54084
  toolbarRightContent,
53660
54085
  mapViewEnabled && /* @__PURE__ */ jsxRuntime.jsx(
@@ -53674,7 +54099,7 @@ var WorkspaceGrid = React144__namespace.default.memo(({
53674
54099
  )
53675
54100
  ] })
53676
54101
  ] }),
53677
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden px-3 py-2 bg-white border-b border-slate-200/60 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) }),
54102
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden px-3 py-2 bg-white border-b border-slate-200/60 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, metricLabel: legendMetricLabel }) }),
53678
54103
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { mode: "wait", children: viewMode === "video" ? /* @__PURE__ */ jsxRuntime.jsx(
53679
54104
  motion.div,
53680
54105
  {
@@ -64335,6 +64760,7 @@ var buildLineInfoSnapshot = (lineDetails, metrics2) => {
64335
64760
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
64336
64761
  output_array: metrics2.output_array || [],
64337
64762
  output_hourly: metrics2.output_hourly,
64763
+ hourly_target_output: metrics2.hourly_target_output,
64338
64764
  line_threshold: metrics2.line_threshold ?? 0,
64339
64765
  threshold_pph: metrics2.threshold_pph ?? 0,
64340
64766
  shift_start: metrics2.shift_start || "06:00",
@@ -64421,6 +64847,7 @@ var transformLineMetrics = (lineId, detailResponse, queryDate, queryShiftId) =>
64421
64847
  underperforming_workspace_names: [],
64422
64848
  underperforming_workspace_uuids: [],
64423
64849
  output_array: [],
64850
+ hourly_target_output: void 0,
64424
64851
  line_threshold: 0,
64425
64852
  threshold_pph: 0,
64426
64853
  shift_start: "06:00",
@@ -65458,6 +65885,10 @@ var BottomSection = React144.memo(({
65458
65885
  workspaceDisplayNames,
65459
65886
  hourlyOutputData,
65460
65887
  hourlyThreshold,
65888
+ hourlyTargetOutput,
65889
+ shiftBreaks,
65890
+ idleTimeHourly,
65891
+ timezone,
65461
65892
  urlDate,
65462
65893
  urlShift,
65463
65894
  navigate,
@@ -65628,8 +66059,13 @@ var BottomSection = React144.memo(({
65628
66059
  {
65629
66060
  data: hourlyOutputData,
65630
66061
  pphThreshold: hourlyThreshold,
66062
+ hourlyTargetOutput,
65631
66063
  shiftStart: lineInfo.metrics.shift_start || "06:00",
65632
66064
  shiftEnd: lineInfo.metrics.shift_end,
66065
+ shiftBreaks,
66066
+ idleTimeHourly,
66067
+ shiftDate: lineInfo.date,
66068
+ timezone,
65633
66069
  skuSegments: skuAware ? skuSegments : void 0,
65634
66070
  activeSkuId
65635
66071
  }
@@ -65650,6 +66086,12 @@ var BottomSection = React144.memo(({
65650
66086
  if (prevProps.lineInfo.monitoring_mode !== nextProps.lineInfo.monitoring_mode) return false;
65651
66087
  if (prevProps.skuAware !== nextProps.skuAware) return false;
65652
66088
  if (prevProps.activeSkuId !== nextProps.activeSkuId) return false;
66089
+ if (JSON.stringify(prevProps.shiftBreaks || []) !== JSON.stringify(nextProps.shiftBreaks || [])) {
66090
+ return false;
66091
+ }
66092
+ if (JSON.stringify(prevProps.hourlyTargetOutput || []) !== JSON.stringify(nextProps.hourlyTargetOutput || [])) {
66093
+ return false;
66094
+ }
65653
66095
  const prevSkuSegmentsSignature = JSON.stringify(
65654
66096
  (prevProps.skuSegments || []).map((segment) => ({
65655
66097
  sku_id: segment.sku_id,
@@ -65722,7 +66164,9 @@ var BottomSection = React144.memo(({
65722
66164
  );
65723
66165
  if (prevWorkspaceSignature !== nextWorkspaceSignature) return false;
65724
66166
  if (prevProps.lineInfo.metrics.shift_start !== nextProps.lineInfo.metrics.shift_start) return false;
66167
+ if (prevProps.lineInfo.metrics.shift_end !== nextProps.lineInfo.metrics.shift_end) return false;
65725
66168
  if (prevProps.hourlyThreshold !== nextProps.hourlyThreshold) return false;
66169
+ if (JSON.stringify(prevProps.idleTimeHourly || {}) !== JSON.stringify(nextProps.idleTimeHourly || {})) return false;
65726
66170
  if (prevProps.urlDate !== nextProps.urlDate || prevProps.urlShift !== nextProps.urlShift) return false;
65727
66171
  if (prevProps.workspaceDisplayNames !== nextProps.workspaceDisplayNames) return false;
65728
66172
  return true;
@@ -65990,6 +66434,7 @@ var KPIDetailView = ({
65990
66434
  }
65991
66435
  }, [urlDate, urlShift, urlTab]);
65992
66436
  const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineId);
66437
+ const lineTimezone = shiftConfig?.timezone || configuredTimezone;
65993
66438
  const getShiftName = React144.useCallback((shiftId) => {
65994
66439
  return getShiftNameById(shiftId, configuredTimezone, shiftConfig);
65995
66440
  }, [configuredTimezone, shiftConfig]);
@@ -66030,12 +66475,12 @@ var KPIDetailView = ({
66030
66475
  operationalTodayDate.setHours(0, 0, 0, 0);
66031
66476
  compareDateInZone.setHours(0, 0, 0, 0);
66032
66477
  const diffTime = compareDateInZone.getTime() - operationalTodayDate.getTime();
66033
- const diffDays = Math.round(diffTime / (1e3 * 60 * 60 * 24));
66034
- if (diffDays === 0) return "Today";
66035
- if (diffDays === -1) return "Yesterday";
66036
- if (diffDays === 1) return "Tomorrow";
66037
- if (diffDays < -1) return `${Math.abs(diffDays)} days ago`;
66038
- if (diffDays > 1) return `${diffDays} days ahead`;
66478
+ const diffDays2 = Math.round(diffTime / (1e3 * 60 * 60 * 24));
66479
+ if (diffDays2 === 0) return "Today";
66480
+ if (diffDays2 === -1) return "Yesterday";
66481
+ if (diffDays2 === 1) return "Tomorrow";
66482
+ if (diffDays2 < -1) return `${Math.abs(diffDays2)} days ago`;
66483
+ if (diffDays2 > 1) return `${diffDays2} days ahead`;
66039
66484
  return "Today";
66040
66485
  }, [configuredTimezone, shiftConfig]);
66041
66486
  const {
@@ -66245,6 +66690,7 @@ var KPIDetailView = ({
66245
66690
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
66246
66691
  output_array: metrics2.output_array || [],
66247
66692
  output_hourly: metrics2.output_hourly,
66693
+ hourly_target_output: metrics2.hourly_target_output,
66248
66694
  line_threshold: metrics2.line_threshold ?? 0,
66249
66695
  threshold_pph: metrics2.threshold_pph ?? 0,
66250
66696
  shift_start: metrics2.shift_start || "06:00",
@@ -66254,7 +66700,7 @@ var KPIDetailView = ({
66254
66700
  idle_time_hourly: metrics2.idle_time_hourly || null,
66255
66701
  // Multi-SKU additive fields (Phase 6) — propagated from
66256
66702
  // `useLineDetailPageData`. Backend authoritative; we never recompute.
66257
- // The selector below uses these to swap header KPIs per selected SKU.
66703
+ // The output-card selection below uses these to swap header KPIs per selected SKU.
66258
66704
  sku_aware: Boolean(metrics2.sku_aware),
66259
66705
  real_sku_count: metrics2.real_sku_count ?? 0,
66260
66706
  sku_breakdown: Array.isArray(metrics2.sku_breakdown) ? metrics2.sku_breakdown : [],
@@ -66351,7 +66797,7 @@ var KPIDetailView = ({
66351
66797
  }, [lineSkuSegments, outputChartSkuBreakdown, hasUrlDate, hasUrlShift]);
66352
66798
  const normalizedSelectedSkuId = selectedSkuId !== "all" ? selectedSkuId : null;
66353
66799
  const isLineSkuAware = Boolean(resolvedLineInfo?.metrics.sku_aware);
66354
- const showSkuSelector = isLineSkuAware && realSkuOptions.length > 0;
66800
+ const showSkuSelector = isLineSkuAware && realSkuOptions.length > 0 && !isUptimeMode;
66355
66801
  React144.useEffect(() => {
66356
66802
  if (selectedSkuId === "all") return;
66357
66803
  const stillPresent = realSkuOptions.some((item) => item.sku_id === selectedSkuId);
@@ -66378,15 +66824,11 @@ var KPIDetailView = ({
66378
66824
  ...resolvedLineInfo.metrics,
66379
66825
  current_output: selectedSkuRow.current_output ?? 0,
66380
66826
  ideal_output: selectedSkuRow.ideal_output ?? 0,
66381
- avg_efficiency: selectedSkuRow.avg_efficiency ?? resolvedLineInfo.metrics.avg_efficiency,
66382
66827
  total_workspaces: selectedSkuRow.total_workspaces ?? resolvedLineInfo.metrics.total_workspaces,
66383
66828
  underperforming_workspaces: selectedSkuRow.underperforming_workspaces ?? resolvedLineInfo.metrics.underperforming_workspaces,
66384
66829
  underperforming_workspace_names: selectedSkuRow.underperforming_workspace_names ?? resolvedLineInfo.metrics.underperforming_workspace_names,
66385
66830
  underperforming_workspace_uuids: selectedSkuRow.underperforming_workspace_uuids ?? resolvedLineInfo.metrics.underperforming_workspace_uuids,
66386
- output_array: selectedSkuRow.output_array ?? resolvedLineInfo.metrics.output_array,
66387
- output_hourly: selectedSkuRow.output_hourly ?? resolvedLineInfo.metrics.output_hourly,
66388
- line_threshold: selectedSkuRow.line_threshold ?? resolvedLineInfo.metrics.line_threshold,
66389
- poorest_performing_workspaces: selectedSkuRow.poorest_performing_workspaces ?? resolvedLineInfo.metrics.poorest_performing_workspaces
66831
+ line_threshold: selectedSkuRow.line_threshold ?? resolvedLineInfo.metrics.line_threshold
66390
66832
  }
66391
66833
  };
66392
66834
  }, [resolvedLineInfo, selectedSkuRow]);
@@ -67165,11 +67607,9 @@ var KPIDetailView = ({
67165
67607
  showIdleTime: idleTimeVlmEnabled
67166
67608
  }
67167
67609
  ) : (
67168
- // Phase 6: pass `displayLineInfo` so the SKU selector
67169
- // swaps `current_output`, `ideal_output`,
67170
- // `avg_efficiency`, `line_threshold` on the header KPI
67171
- // cards. When `selectedSkuId === 'all'`, this is exactly
67172
- // `resolvedLineInfo` (aggregate path preserved).
67610
+ // Keep the line output + underperforming cards SKU-aware,
67611
+ // while Average Efficiency stays on the aggregate line
67612
+ // metrics even when a specific SKU is selected.
67173
67613
  /* @__PURE__ */ jsxRuntime.jsx(
67174
67614
  MetricCards,
67175
67615
  {
@@ -67214,6 +67654,10 @@ var KPIDetailView = ({
67214
67654
  workspaceDisplayNames,
67215
67655
  hourlyOutputData,
67216
67656
  hourlyThreshold,
67657
+ hourlyTargetOutput: chartMetrics?.hourly_target_output,
67658
+ shiftBreaks: shiftConfig?.shifts?.find((shift) => shift.shiftId === resolvedLineInfo.shift_id)?.breaks || [],
67659
+ idleTimeHourly: chartMetrics?.idle_time_hourly,
67660
+ timezone: lineTimezone,
67217
67661
  urlDate,
67218
67662
  urlShift,
67219
67663
  navigate,
@@ -67279,8 +67723,8 @@ var KPIDetailView = ({
67279
67723
  showIdleTime: idleTimeVlmEnabled
67280
67724
  }
67281
67725
  ) : (
67282
- // Phase 6: pass `displayLineInfo` so the SKU selector
67283
- // swaps the four SKU-specific fields on the header.
67726
+ // Keep the line output + underperforming cards SKU-aware,
67727
+ // while Average Efficiency stays aggregate.
67284
67728
  /* @__PURE__ */ jsxRuntime.jsx(
67285
67729
  MetricCards,
67286
67730
  {
@@ -67325,6 +67769,8 @@ var KPIDetailView = ({
67325
67769
  workspaceDisplayNames,
67326
67770
  hourlyOutputData,
67327
67771
  hourlyThreshold,
67772
+ idleTimeHourly: chartMetrics?.idle_time_hourly,
67773
+ timezone: lineTimezone,
67328
67774
  urlDate,
67329
67775
  urlShift,
67330
67776
  navigate,
@@ -73561,12 +74007,12 @@ var getDaysDifference = (date, timezone = "UTC", shiftStartTime = "06:00") => {
73561
74007
  operationalTodayDate.setHours(0, 0, 0, 0);
73562
74008
  compareDateInTz.setHours(0, 0, 0, 0);
73563
74009
  const diffTime = compareDateInTz.getTime() - operationalTodayDate.getTime();
73564
- const diffDays = Math.round(diffTime / (1e3 * 60 * 60 * 24));
73565
- if (diffDays === 0) return "Today";
73566
- if (diffDays === -1) return "Yesterday";
73567
- if (diffDays === 1) return "Tomorrow";
73568
- if (diffDays < -1) return `${Math.abs(diffDays)} days ago`;
73569
- if (diffDays > 1) return `${diffDays} days ahead`;
74010
+ const diffDays2 = Math.round(diffTime / (1e3 * 60 * 60 * 24));
74011
+ if (diffDays2 === 0) return "Today";
74012
+ if (diffDays2 === -1) return "Yesterday";
74013
+ if (diffDays2 === 1) return "Tomorrow";
74014
+ if (diffDays2 < -1) return `${Math.abs(diffDays2)} days ago`;
74015
+ if (diffDays2 > 1) return `${diffDays2} days ahead`;
73570
74016
  return "Today";
73571
74017
  };
73572
74018
  var getInitialTab = (sourceType, defaultTab, fromMonthly, urlDate) => {
@@ -74002,7 +74448,7 @@ var WorkspaceDetailView = ({
74002
74448
  () => resolveLiveSkuId(workspace?.sku_segments),
74003
74449
  [workspace?.sku_segments]
74004
74450
  );
74005
- const activeSkuId = selectedSkuId || liveSkuId;
74451
+ const activeSkuId = selectedSkuId;
74006
74452
  const resolvedLineId = effectiveLineId || workspace?.line_id || cachedDetailedMetrics?.line_id || cachedOverviewMetrics?.line_id || overviewFallback?.line_id;
74007
74453
  const { timezone: cycleTimeTimezone } = useTimezone({
74008
74454
  lineId: resolvedLineId || void 0,
@@ -74970,14 +75416,16 @@ var WorkspaceDetailView = ({
74970
75416
  {
74971
75417
  data: workspace.hourly_action_counts || [],
74972
75418
  pphThreshold: workspace.pph_threshold || 0,
75419
+ hourlyTargetOutput: workspace.hourly_target_output,
74973
75420
  shiftStart: workspace.shift_start || "06:00",
74974
75421
  shiftEnd: workspace.shift_end,
75422
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
74975
75423
  showIdleTime: showChartIdleTime,
74976
75424
  idleTimeHourly: workspace.idle_time_hourly,
74977
75425
  idleTimeClips,
74978
75426
  idleTimeClipClassifications,
74979
75427
  shiftDate: idleClipDate,
74980
- timezone,
75428
+ timezone: effectiveCycleTimeTimezone,
74981
75429
  skuSegments: isSkuAware ? skuSegments : void 0,
74982
75430
  activeSkuId
74983
75431
  }
@@ -75116,14 +75564,16 @@ var WorkspaceDetailView = ({
75116
75564
  {
75117
75565
  data: workspace.hourly_action_counts || [],
75118
75566
  pphThreshold: workspace.pph_threshold || 0,
75567
+ hourlyTargetOutput: workspace.hourly_target_output,
75119
75568
  shiftStart: workspace.shift_start || "06:00",
75120
75569
  shiftEnd: workspace.shift_end,
75570
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
75121
75571
  showIdleTime: showChartIdleTime,
75122
75572
  idleTimeHourly: workspace.idle_time_hourly,
75123
75573
  idleTimeClips,
75124
75574
  idleTimeClipClassifications,
75125
75575
  shiftDate: idleClipDate,
75126
- timezone,
75576
+ timezone: effectiveCycleTimeTimezone,
75127
75577
  skuSegments: isSkuAware ? skuSegments : void 0,
75128
75578
  activeSkuId
75129
75579
  }
@@ -78400,8 +78850,8 @@ var ImprovementCenterView = () => {
78400
78850
  const firstSeen = new Date(new Date(openedAt).toLocaleString("en-US", { timeZone: timezone }));
78401
78851
  if (Number.isNaN(firstSeen.getTime())) return void 0;
78402
78852
  const now4 = new Date((/* @__PURE__ */ new Date()).toLocaleString("en-US", { timeZone: timezone }));
78403
- const diffDays = Math.max(0, Math.floor((now4.getTime() - firstSeen.getTime()) / (1e3 * 60 * 60 * 24)));
78404
- return Math.max(1, Math.ceil(diffDays / 7));
78853
+ const diffDays2 = Math.max(0, Math.floor((now4.getTime() - firstSeen.getTime()) / (1e3 * 60 * 60 * 24)));
78854
+ return Math.max(1, Math.ceil(diffDays2 / 7));
78405
78855
  };
78406
78856
  const toZonedDate = (value) => {
78407
78857
  if (!value) return void 0;
@@ -79082,12 +79532,12 @@ var ThreadSidebar = ({
79082
79532
  const date = new Date(dateString);
79083
79533
  const now4 = /* @__PURE__ */ new Date();
79084
79534
  const diffMs = now4.getTime() - date.getTime();
79085
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
79086
- if (diffDays === 0) {
79535
+ const diffDays2 = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
79536
+ if (diffDays2 === 0) {
79087
79537
  return "Today";
79088
- } else if (diffDays === 1) {
79538
+ } else if (diffDays2 === 1) {
79089
79539
  return "Yesterday";
79090
- } else if (diffDays < 7) {
79540
+ } else if (diffDays2 < 7) {
79091
79541
  return date.toLocaleDateString("en-US", { weekday: "short" });
79092
79542
  } else {
79093
79543
  return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });