@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.mjs CHANGED
@@ -4205,6 +4205,22 @@ var isDummyRow = (row, dummySet) => {
4205
4205
  if (typeof skuId !== "string" || skuId.length === 0) return false;
4206
4206
  return dummySet.has(skuId);
4207
4207
  };
4208
+ var isActiveOutputRow = (row) => {
4209
+ const currentOutput = coerceOptionalNumber(row.current_output) ?? 0;
4210
+ const idealOutput = coerceOptionalNumber(row.ideal_output) ?? 0;
4211
+ return currentOutput > 0 || idealOutput > 0;
4212
+ };
4213
+ var roundHalfUpInt = (value) => Math.floor(value + 0.5);
4214
+ var dedupeStringsPreserveOrder = (values) => {
4215
+ const seen = /* @__PURE__ */ new Set();
4216
+ const ordered = [];
4217
+ for (const value of values) {
4218
+ if (!value || seen.has(value)) continue;
4219
+ seen.add(value);
4220
+ ordered.push(value);
4221
+ }
4222
+ return ordered;
4223
+ };
4208
4224
  var emptyAggregate = () => ({
4209
4225
  current_output: 0,
4210
4226
  ideal_output: 0,
@@ -4323,10 +4339,7 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4323
4339
  );
4324
4340
  const dummyLineMetricsRow = rows.find((row) => isDummyRow(row, dummySet));
4325
4341
  const lineThresholdValue = dummyLineMetricsRow ? safeFloat(dummyLineMetricsRow.line_threshold) : safeFloat(rowsForAggregation[0]?.line_threshold ?? 0);
4326
- const underperformingWorkspacesSum = rowsForAggregation.reduce(
4327
- (acc, row) => acc + safeInt(row.underperforming_workspaces),
4328
- 0
4329
- );
4342
+ const activeRealRows = rowsForAggregation.filter(isActiveOutputRow);
4330
4343
  const weighted = (field) => {
4331
4344
  const pairs = [];
4332
4345
  rowsForAggregation.forEach((row, idx) => {
@@ -4348,6 +4361,12 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4348
4361
  const avgEfficiency = weighted("avg_efficiency");
4349
4362
  const avgCycleTime = weighted("avg_cycle_time");
4350
4363
  const thresholdPph = weighted("threshold_pph");
4364
+ const underperformingWorkspaces = activeRealRows.length > 0 ? roundHalfUpInt(
4365
+ activeRealRows.reduce(
4366
+ (acc, row) => acc + safeInt(row.underperforming_workspaces),
4367
+ 0
4368
+ ) / activeRealRows.length
4369
+ ) : 0;
4351
4370
  const merged = mergeHourlyFields(rows);
4352
4371
  const outputArrays = [];
4353
4372
  for (const row of rowsForAggregation) {
@@ -4370,7 +4389,7 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4370
4389
  }
4371
4390
  const underperformingNames = [];
4372
4391
  const underperformingUuids = [];
4373
- for (const row of rowsForAggregation) {
4392
+ for (const row of activeRealRows) {
4374
4393
  const names = row.underperforming_workspace_names;
4375
4394
  if (Array.isArray(names)) {
4376
4395
  for (const n of names) {
@@ -4384,6 +4403,8 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4384
4403
  }
4385
4404
  }
4386
4405
  }
4406
+ const dedupedUnderperformingNames = dedupeStringsPreserveOrder(underperformingNames);
4407
+ const dedupedUnderperformingUuids = dedupeStringsPreserveOrder(underperformingUuids);
4387
4408
  let totalWorkspacesValue = null;
4388
4409
  const primaryTotal = coerceOptionalNumber(primary.total_workspaces);
4389
4410
  if (primaryTotal !== null) {
@@ -4405,9 +4426,9 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4405
4426
  line_threshold: lineThresholdValue,
4406
4427
  threshold_pph: thresholdPph,
4407
4428
  total_workspaces: safeInt(totalWorkspacesValue ?? 0),
4408
- underperforming_workspaces: underperformingWorkspacesSum,
4409
- underperforming_workspace_names: underperformingNames,
4410
- underperforming_workspace_uuids: underperformingUuids,
4429
+ underperforming_workspaces: underperformingWorkspaces,
4430
+ underperforming_workspace_names: dedupedUnderperformingNames,
4431
+ underperforming_workspace_uuids: dedupedUnderperformingUuids,
4411
4432
  output_array: outputArray,
4412
4433
  output_hourly: merged.output_hourly,
4413
4434
  idle_time_hourly: merged.idle_time_hourly,
@@ -5532,11 +5553,11 @@ var getDaysDifferenceInZone = (compareDate, timezone) => {
5532
5553
  todayInZone.setHours(0, 0, 0, 0);
5533
5554
  compareDateInZone.setHours(0, 0, 0, 0);
5534
5555
  const diffTime = todayInZone.getTime() - compareDateInZone.getTime();
5535
- const diffDays = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
5536
- if (diffDays < 0) return "In the future";
5537
- if (diffDays === 0) return "Today";
5538
- if (diffDays === 1) return "Yesterday";
5539
- return `${diffDays} days ago`;
5556
+ const diffDays2 = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
5557
+ if (diffDays2 < 0) return "In the future";
5558
+ if (diffDays2 === 0) return "Today";
5559
+ if (diffDays2 === 1) return "Yesterday";
5560
+ return `${diffDays2} days ago`;
5540
5561
  };
5541
5562
  var getDashboardHeaderTimeInZone = (date = /* @__PURE__ */ new Date(), timezone, timeOptions, locale = DEFAULT_LOCALE) => {
5542
5563
  const defaultOptions = {
@@ -12983,6 +13004,8 @@ var toWorkspaceDetailedMetrics = ({
12983
13004
  const targetOutput = coerceNumber(data.target_output ?? data.total_day_output, 0);
12984
13005
  const idealOutput = coerceNumber(data.ideal_output ?? data.ideal_output_until_now, 0);
12985
13006
  const outputDifference = totalActions - idealOutput;
13007
+ const hasHourlyTargetOutputField = Object.prototype.hasOwnProperty.call(data, "hourly_target_output");
13008
+ 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;
12986
13009
  const hourlyCycleTimes = Array.isArray(data.hourly_cycle_times) ? data.hourly_cycle_times.map((value) => coerceNumber(value, 0)) : [];
12987
13010
  const cycleCompletionClipCount = data.cycle_completion_clip_count === null || data.cycle_completion_clip_count === void 0 ? null : coerceNumber(data.cycle_completion_clip_count, 0);
12988
13011
  const cycleTimeDataStatus = data.cycle_time_data_status === "missing_clips" ? "missing_clips" : data.cycle_time_data_status === "available" ? "available" : null;
@@ -13034,6 +13057,7 @@ var toWorkspaceDetailedMetrics = ({
13034
13057
  avg_efficiency: coerceNumber(data.efficiency ?? data.avg_efficiency, 0),
13035
13058
  total_actions: totalActions,
13036
13059
  hourly_action_counts: hourlyActionCounts,
13060
+ hourly_target_output: hourlyTargetOutput,
13037
13061
  hourly_cycle_times: hourlyCycleTimes,
13038
13062
  cycle_completion_clip_count: cycleCompletionClipCount,
13039
13063
  cycle_time_data_status: cycleTimeDataStatus,
@@ -19650,7 +19674,7 @@ function formatRelativeTime(timestamp) {
19650
19674
  const diffSeconds = Math.floor(diffMs / 1e3);
19651
19675
  const diffMinutes = Math.floor(diffSeconds / 60);
19652
19676
  const diffHours = Math.floor(diffMinutes / 60);
19653
- const diffDays = Math.floor(diffHours / 24);
19677
+ const diffDays2 = Math.floor(diffHours / 24);
19654
19678
  if (diffSeconds < 60) {
19655
19679
  return "Less than a minute ago";
19656
19680
  }
@@ -19662,8 +19686,8 @@ function formatRelativeTime(timestamp) {
19662
19686
  const hourLabel = diffHours === 1 ? "hour" : "hours";
19663
19687
  return `${diffHours} ${hourLabel} ago`;
19664
19688
  }
19665
- const dayLabel = diffDays === 1 ? "day" : "days";
19666
- return `${diffDays} ${dayLabel} ago`;
19689
+ const dayLabel = diffDays2 === 1 ? "day" : "days";
19690
+ return `${diffDays2} ${dayLabel} ago`;
19667
19691
  } catch (error) {
19668
19692
  console.error("[formatRelativeTime] Error formatting timestamp:", error);
19669
19693
  return "Unknown";
@@ -35163,6 +35187,478 @@ var Button = React144.forwardRef(
35163
35187
  );
35164
35188
  Button.displayName = "Button";
35165
35189
 
35190
+ // src/lib/utils/hourlyTargets.ts
35191
+ var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
35192
+ var MINUTES_PER_DAY = 24 * 60;
35193
+ var parseTimeToMinutes2 = (timeString) => {
35194
+ const normalized = stripSeconds2(timeString || "");
35195
+ if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
35196
+ const [hours, minutes] = normalized.split(":").map(Number);
35197
+ return hours * 60 + minutes;
35198
+ };
35199
+ var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
35200
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35201
+ if (!Number.isFinite(shiftStartMinutes)) return [];
35202
+ const normalizedBreaks = [];
35203
+ for (const entry of breaks) {
35204
+ const startRaw = parseTimeToMinutes2(entry.startTime);
35205
+ const endRaw = parseTimeToMinutes2(entry.endTime);
35206
+ if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
35207
+ let start = startRaw;
35208
+ let end = endRaw;
35209
+ if (end <= start) {
35210
+ end += 24 * 60;
35211
+ }
35212
+ if (start < shiftStartMinutes) {
35213
+ start += 24 * 60;
35214
+ end += 24 * 60;
35215
+ }
35216
+ const label = entry.remarks?.trim() || "Break";
35217
+ normalizedBreaks.push({ start, end, label });
35218
+ }
35219
+ return normalizedBreaks;
35220
+ };
35221
+ var roundTarget = (value, mode) => {
35222
+ if (!Number.isFinite(value)) return 0;
35223
+ switch (mode) {
35224
+ case "floor":
35225
+ return Math.floor(value);
35226
+ case "ceil":
35227
+ return Math.ceil(value);
35228
+ case "round":
35229
+ default:
35230
+ return Math.round(value);
35231
+ }
35232
+ };
35233
+ var formatDateKey = (date) => {
35234
+ const year = date.getUTCFullYear();
35235
+ const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
35236
+ const day = `${date.getUTCDate()}`.padStart(2, "0");
35237
+ return `${year}-${month}-${day}`;
35238
+ };
35239
+ var shiftDateKey = (dateKey, deltaDays) => {
35240
+ const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
35241
+ const year = Number.isFinite(yearPart) ? yearPart : 1970;
35242
+ const month = Number.isFinite(monthPart) ? monthPart : 1;
35243
+ const day = Number.isFinite(dayPart) ? dayPart : 1;
35244
+ const date = new Date(Date.UTC(year, month - 1, day));
35245
+ date.setUTCDate(date.getUTCDate() + deltaDays);
35246
+ return formatDateKey(date);
35247
+ };
35248
+ var getZonedNowSnapshot = (timeZone, now4) => {
35249
+ const formatter = new Intl.DateTimeFormat("en-US", {
35250
+ timeZone,
35251
+ year: "numeric",
35252
+ month: "2-digit",
35253
+ day: "2-digit",
35254
+ hour: "2-digit",
35255
+ minute: "2-digit",
35256
+ hourCycle: "h23"
35257
+ });
35258
+ const parts = formatter.formatToParts(now4).reduce((acc, part) => {
35259
+ if (part.type !== "literal") {
35260
+ acc[part.type] = part.value;
35261
+ }
35262
+ return acc;
35263
+ }, {});
35264
+ const year = Number(parts.year);
35265
+ const month = Number(parts.month);
35266
+ const day = Number(parts.day);
35267
+ const hour = Number(parts.hour);
35268
+ const minute = Number(parts.minute);
35269
+ return {
35270
+ dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
35271
+ minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
35272
+ };
35273
+ };
35274
+ var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
35275
+ var buildHourlyIntervals = ({
35276
+ shiftStart,
35277
+ shiftEnd,
35278
+ bucketMinutes = 60,
35279
+ fallbackHours = 11
35280
+ }) => {
35281
+ const startMinutes = parseTimeToMinutes2(shiftStart);
35282
+ if (!Number.isFinite(startMinutes)) return [];
35283
+ const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
35284
+ let totalMinutes;
35285
+ const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
35286
+ if (!Number.isFinite(endRaw)) {
35287
+ totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
35288
+ } else {
35289
+ let endMinutes = endRaw;
35290
+ if (endMinutes <= startMinutes) {
35291
+ endMinutes += 24 * 60;
35292
+ }
35293
+ totalMinutes = endMinutes - startMinutes;
35294
+ }
35295
+ if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
35296
+ const count = Math.ceil(totalMinutes / bucket);
35297
+ const shiftEndMinutes = startMinutes + totalMinutes;
35298
+ const intervals = [];
35299
+ for (let i = 0; i < count; i += 1) {
35300
+ const start = startMinutes + i * bucket;
35301
+ const end = Math.min(start + bucket, shiftEndMinutes);
35302
+ const minutes = Math.max(0, end - start);
35303
+ if (minutes <= 0) continue;
35304
+ intervals.push({ start, end, minutes });
35305
+ }
35306
+ return intervals;
35307
+ };
35308
+ var computeBreakMinutesByInterval = ({
35309
+ intervals,
35310
+ shiftStart,
35311
+ breaks
35312
+ }) => {
35313
+ if (!intervals.length || !breaks.length) return intervals.map(() => 0);
35314
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
35315
+ return intervals.map((interval) => {
35316
+ if (!normalizedBreaks.length) return 0;
35317
+ let total = 0;
35318
+ for (const brk of normalizedBreaks) {
35319
+ const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
35320
+ total += overlap;
35321
+ if (total >= interval.minutes) return interval.minutes;
35322
+ }
35323
+ return Math.min(interval.minutes, total);
35324
+ });
35325
+ };
35326
+ var computeBreakRemarksByInterval = ({
35327
+ intervals,
35328
+ shiftStart,
35329
+ breaks
35330
+ }) => {
35331
+ if (!intervals.length || !breaks.length) return intervals.map(() => "");
35332
+ const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
35333
+ return intervals.map((interval) => {
35334
+ 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);
35335
+ return labels.join(", ");
35336
+ });
35337
+ };
35338
+ var computeEffectiveTargets = ({
35339
+ intervals,
35340
+ breakMinutes,
35341
+ pphThreshold,
35342
+ rounding = "round"
35343
+ }) => {
35344
+ return intervals.map((interval, idx) => {
35345
+ const intervalMinutes = Number(interval?.minutes) || 0;
35346
+ const breakMins = Number(breakMinutes?.[idx]) || 0;
35347
+ const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
35348
+ if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
35349
+ if (plannedWorkMinutes <= 0) return 0;
35350
+ return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
35351
+ });
35352
+ };
35353
+ var buildHourlyTargetPlan = ({
35354
+ shiftStart,
35355
+ shiftEnd,
35356
+ breaks = [],
35357
+ pphThreshold,
35358
+ bucketMinutes = 60,
35359
+ fallbackHours = 11,
35360
+ rounding = "round"
35361
+ }) => {
35362
+ const intervals = buildHourlyIntervals({
35363
+ shiftStart,
35364
+ shiftEnd,
35365
+ bucketMinutes,
35366
+ fallbackHours
35367
+ });
35368
+ const breakMinutes = computeBreakMinutesByInterval({
35369
+ intervals,
35370
+ shiftStart,
35371
+ breaks
35372
+ });
35373
+ const breakRemarks = computeBreakRemarksByInterval({
35374
+ intervals,
35375
+ shiftStart,
35376
+ breaks
35377
+ });
35378
+ const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
35379
+ const targets = computeEffectiveTargets({
35380
+ intervals,
35381
+ breakMinutes,
35382
+ pphThreshold,
35383
+ rounding
35384
+ });
35385
+ return {
35386
+ intervals,
35387
+ breakMinutes,
35388
+ breakRemarks,
35389
+ productiveMinutes,
35390
+ targets
35391
+ };
35392
+ };
35393
+ var isHourlyIntervalComplete = ({
35394
+ reportDate,
35395
+ shiftStart,
35396
+ shiftEnd,
35397
+ interval,
35398
+ timeZone = "Asia/Kolkata",
35399
+ now: now4 = /* @__PURE__ */ new Date()
35400
+ }) => {
35401
+ if (!reportDate) return true;
35402
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
35403
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35404
+ const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
35405
+ const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
35406
+ if (reportDate === snapshot.dateKey) {
35407
+ return interval.end <= snapshot.minutesOfDay;
35408
+ }
35409
+ if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
35410
+ return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
35411
+ }
35412
+ return reportDate < snapshot.dateKey;
35413
+ };
35414
+ var isShiftInProgressForReportDate = ({
35415
+ reportDate,
35416
+ shiftStart,
35417
+ shiftEnd,
35418
+ timeZone = "Asia/Kolkata",
35419
+ now: now4 = /* @__PURE__ */ new Date()
35420
+ }) => {
35421
+ if (!reportDate || !shiftStart || !shiftEnd) return false;
35422
+ const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
35423
+ const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
35424
+ if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
35425
+ return false;
35426
+ }
35427
+ let shiftEndMinutes = shiftEndMinutesRaw;
35428
+ const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
35429
+ if (wrapsMidnight) {
35430
+ shiftEndMinutes += MINUTES_PER_DAY;
35431
+ }
35432
+ const snapshot = getZonedNowSnapshot(timeZone, now4);
35433
+ let currentMinutes = null;
35434
+ if (reportDate === snapshot.dateKey) {
35435
+ currentMinutes = snapshot.minutesOfDay;
35436
+ } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
35437
+ currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
35438
+ }
35439
+ if (currentMinutes === null) {
35440
+ return false;
35441
+ }
35442
+ return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
35443
+ };
35444
+ var padTime = (value) => value.toString().padStart(2, "0");
35445
+ var parseTime = (timeValue) => {
35446
+ if (!timeValue) return null;
35447
+ const [hourPart, minutePart] = timeValue.split(":");
35448
+ const hour = Number.parseInt(hourPart, 10);
35449
+ const minute = Number.parseInt(minutePart ?? "0", 10);
35450
+ if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null;
35451
+ return { hour, minute };
35452
+ };
35453
+ var normalizeIdleTimeHourly = (idleTimeHourly) => {
35454
+ if (!idleTimeHourly || typeof idleTimeHourly !== "object") {
35455
+ return {};
35456
+ }
35457
+ return Object.fromEntries(
35458
+ Object.entries(idleTimeHourly).map(([key, value]) => {
35459
+ if (Array.isArray(value)) return [key, value];
35460
+ if (value && Array.isArray(value.values)) {
35461
+ return [key, value.values];
35462
+ }
35463
+ return [key, []];
35464
+ })
35465
+ );
35466
+ };
35467
+ var interpretIdleValue = (value) => {
35468
+ if (value === 1 || value === "1") return "idle";
35469
+ if (value === 0 || value === "0") return "active";
35470
+ if (value === "x" || value === null || value === void 0) return "unknown";
35471
+ return "unknown";
35472
+ };
35473
+ var getShiftDurationMinutes = (shiftStart, shiftEnd) => {
35474
+ const start = parseTime(shiftStart);
35475
+ const end = parseTime(shiftEnd);
35476
+ if (!start || !end) return null;
35477
+ let duration = end.hour * 60 + end.minute - (start.hour * 60 + start.minute);
35478
+ if (duration <= 0) {
35479
+ duration += 24 * 60;
35480
+ }
35481
+ return duration > 0 ? duration : null;
35482
+ };
35483
+ var getShiftElapsedMinutes = ({
35484
+ shiftStart,
35485
+ shiftEnd,
35486
+ shiftDate,
35487
+ timezone,
35488
+ now: now4 = /* @__PURE__ */ new Date()
35489
+ }) => {
35490
+ if (!shiftDate || !timezone) return null;
35491
+ const startTime = parseTime(shiftStart);
35492
+ const endTime = parseTime(shiftEnd);
35493
+ if (!startTime || !endTime) return null;
35494
+ const shiftStartDate = fromZonedTime(
35495
+ `${shiftDate}T${padTime(startTime.hour)}:${padTime(startTime.minute)}:00`,
35496
+ timezone
35497
+ );
35498
+ let shiftEndDate = fromZonedTime(
35499
+ `${shiftDate}T${padTime(endTime.hour)}:${padTime(endTime.minute)}:00`,
35500
+ timezone
35501
+ );
35502
+ if (shiftEndDate <= shiftStartDate) {
35503
+ shiftEndDate = addDays(shiftEndDate, 1);
35504
+ }
35505
+ const shiftMinutes = Math.max(differenceInMinutes(shiftEndDate, shiftStartDate), 0);
35506
+ if (shiftMinutes <= 0) return null;
35507
+ const elapsed = differenceInMinutes(now4, shiftStartDate);
35508
+ return Math.min(Math.max(elapsed, 0), shiftMinutes);
35509
+ };
35510
+ var maskFutureHourlySeries = ({
35511
+ data,
35512
+ shiftStart,
35513
+ shiftEnd,
35514
+ shiftDate,
35515
+ timezone,
35516
+ now: now4 = /* @__PURE__ */ new Date()
35517
+ }) => {
35518
+ if (!Array.isArray(data)) {
35519
+ return [];
35520
+ }
35521
+ const normalizedData = data.map((value) => typeof value === "number" && Number.isFinite(value) ? value : null);
35522
+ if (!normalizedData.length) {
35523
+ return normalizedData;
35524
+ }
35525
+ const shiftMinutes = getShiftDurationMinutes(shiftStart, shiftEnd);
35526
+ const elapsedMinutes = getShiftElapsedMinutes({
35527
+ shiftStart,
35528
+ shiftEnd,
35529
+ shiftDate,
35530
+ timezone,
35531
+ now: now4
35532
+ });
35533
+ if (shiftMinutes === null || elapsedMinutes === null || elapsedMinutes >= shiftMinutes) {
35534
+ return normalizedData;
35535
+ }
35536
+ return normalizedData.map((value, index) => {
35537
+ const slotStartMinutes = index * 60;
35538
+ return slotStartMinutes > elapsedMinutes ? null : value;
35539
+ });
35540
+ };
35541
+ var buildUptimeSeries = ({
35542
+ idleTimeHourly,
35543
+ shiftStart,
35544
+ shiftEnd,
35545
+ shiftDate,
35546
+ timezone,
35547
+ elapsedMinutes
35548
+ }) => {
35549
+ const normalizedIdle = normalizeIdleTimeHourly(idleTimeHourly || {});
35550
+ const hasIdleData = Object.keys(normalizedIdle).length > 0;
35551
+ if (!hasIdleData || !shiftDate || !timezone) {
35552
+ return {
35553
+ points: [],
35554
+ activeMinutes: 0,
35555
+ idleMinutes: 0,
35556
+ availableMinutes: 0,
35557
+ shiftMinutes: 0,
35558
+ elapsedMinutes: 0,
35559
+ hasData: false
35560
+ };
35561
+ }
35562
+ const startTime = parseTime(shiftStart);
35563
+ const endTime = parseTime(shiftEnd);
35564
+ if (!startTime || !endTime) {
35565
+ return {
35566
+ points: [],
35567
+ activeMinutes: 0,
35568
+ idleMinutes: 0,
35569
+ availableMinutes: 0,
35570
+ shiftMinutes: 0,
35571
+ elapsedMinutes: 0,
35572
+ hasData: false
35573
+ };
35574
+ }
35575
+ const shiftStartDate = fromZonedTime(
35576
+ `${shiftDate}T${padTime(startTime.hour)}:${padTime(startTime.minute)}:00`,
35577
+ timezone
35578
+ );
35579
+ let shiftEndDate = fromZonedTime(
35580
+ `${shiftDate}T${padTime(endTime.hour)}:${padTime(endTime.minute)}:00`,
35581
+ timezone
35582
+ );
35583
+ if (shiftEndDate <= shiftStartDate) {
35584
+ shiftEndDate = addDays(shiftEndDate, 1);
35585
+ }
35586
+ const shiftMinutes = Math.max(differenceInMinutes(shiftEndDate, shiftStartDate), 0);
35587
+ if (shiftMinutes <= 0) {
35588
+ return {
35589
+ points: [],
35590
+ activeMinutes: 0,
35591
+ idleMinutes: 0,
35592
+ availableMinutes: 0,
35593
+ shiftMinutes: 0,
35594
+ elapsedMinutes: 0,
35595
+ hasData: false
35596
+ };
35597
+ }
35598
+ const elapsedMinutesClamped = Number.isFinite(elapsedMinutes) ? Math.min(Math.max(Math.floor(elapsedMinutes ?? 0), 0), shiftMinutes) : shiftMinutes;
35599
+ const points = [];
35600
+ let activeMinutes = 0;
35601
+ let idleMinutes = 0;
35602
+ for (let minuteIndex = 0; minuteIndex < shiftMinutes; minuteIndex += 1) {
35603
+ const minuteDate = addMinutes(shiftStartDate, minuteIndex);
35604
+ const timeLabel = formatInTimeZone(minuteDate, timezone, "h:mm a");
35605
+ if (minuteIndex >= elapsedMinutesClamped) {
35606
+ points.push({
35607
+ minuteIndex,
35608
+ timeLabel,
35609
+ uptime: null,
35610
+ status: "unknown"
35611
+ });
35612
+ continue;
35613
+ }
35614
+ const hourKey = formatInTimeZone(minuteDate, timezone, "H");
35615
+ const minuteKey = Number.parseInt(formatInTimeZone(minuteDate, timezone, "m"), 10);
35616
+ const hourBucket = normalizedIdle[hourKey] || [];
35617
+ const value = Array.isArray(hourBucket) ? hourBucket[minuteKey] : void 0;
35618
+ const status = interpretIdleValue(value);
35619
+ if (status === "active") activeMinutes += 1;
35620
+ if (status === "idle") idleMinutes += 1;
35621
+ points.push({
35622
+ minuteIndex,
35623
+ timeLabel,
35624
+ uptime: status === "active" ? 1 : status === "idle" ? 0 : null,
35625
+ status
35626
+ });
35627
+ }
35628
+ return {
35629
+ points,
35630
+ activeMinutes,
35631
+ idleMinutes,
35632
+ availableMinutes: activeMinutes + idleMinutes,
35633
+ shiftMinutes,
35634
+ elapsedMinutes: elapsedMinutesClamped,
35635
+ hasData: activeMinutes + idleMinutes > 0
35636
+ };
35637
+ };
35638
+ var getUptimeUtilizationPercent = (shift) => {
35639
+ const efficiency = shift.efficiency;
35640
+ if (Number.isFinite(efficiency)) {
35641
+ return Math.round(Math.max(0, Math.min(100, Number(efficiency))));
35642
+ }
35643
+ const idleSeconds = Number.isFinite(shift.idleTime) ? Number(shift.idleTime) : 0;
35644
+ const activeSeconds = Number.isFinite(shift.activeTimeSeconds) ? Number(shift.activeTimeSeconds) : null;
35645
+ let availableSeconds = Number.isFinite(shift.availableTimeSeconds) ? Number(shift.availableTimeSeconds) : null;
35646
+ if (availableSeconds === null) {
35647
+ if ((activeSeconds ?? 0) > 0 || idleSeconds > 0) {
35648
+ availableSeconds = (activeSeconds ?? 0) + idleSeconds;
35649
+ } else {
35650
+ return 0;
35651
+ }
35652
+ }
35653
+ if (availableSeconds <= 0) return 0;
35654
+ const clampedIdleSeconds = Math.min(Math.max(idleSeconds, 0), availableSeconds);
35655
+ const productiveSeconds = Math.max(
35656
+ activeSeconds ?? availableSeconds - clampedIdleSeconds,
35657
+ 0
35658
+ );
35659
+ return Math.round(productiveSeconds / availableSeconds * 100);
35660
+ };
35661
+
35166
35662
  // src/components/charts/skuDividerUtils.ts
35167
35663
  var HOURLY_TIME_RE = /^(\d{1,2}):(\d{2})/;
35168
35664
  var parseTimeOfDay = (timeValue) => {
@@ -35174,37 +35670,163 @@ var parseTimeOfDay = (timeValue) => {
35174
35670
  if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
35175
35671
  return { hour, minute };
35176
35672
  };
35177
- var computeSegmentOffset = (segment, shift) => {
35178
- if (!segment?.start_time) return null;
35673
+ var parseDateKey = (value) => {
35674
+ const [yearPart, monthPart, dayPart] = value.split("-").map(Number);
35675
+ if (!Number.isFinite(yearPart) || !Number.isFinite(monthPart) || !Number.isFinite(dayPart)) {
35676
+ return null;
35677
+ }
35678
+ return Date.UTC(yearPart, monthPart - 1, dayPart);
35679
+ };
35680
+ var diffDays = (left, right) => {
35681
+ const leftUtc = parseDateKey(left);
35682
+ const rightUtc = parseDateKey(right);
35683
+ if (leftUtc === null || rightUtc === null) return null;
35684
+ return Math.round((leftUtc - rightUtc) / (24 * 60 * 60 * 1e3));
35685
+ };
35686
+ var getSegmentMinutes = (isoString, timeZone, reportDate) => {
35687
+ const date = new Date(isoString);
35688
+ if (Number.isNaN(date.getTime())) return Number.NaN;
35689
+ const formatter = new Intl.DateTimeFormat("en-US", {
35690
+ timeZone,
35691
+ year: "numeric",
35692
+ month: "2-digit",
35693
+ day: "2-digit",
35694
+ hour: "2-digit",
35695
+ minute: "2-digit",
35696
+ hourCycle: "h23"
35697
+ });
35698
+ const parts = formatter.formatToParts(date).reduce((acc, part) => {
35699
+ if (part.type !== "literal") {
35700
+ acc[part.type] = part.value;
35701
+ }
35702
+ return acc;
35703
+ }, {});
35704
+ const dateKey = `${parts.year}-${parts.month}-${parts.day}`;
35705
+ let minutes = Number(parts.hour) * 60 + Number(parts.minute);
35706
+ const dayDelta = diffDays(dateKey, reportDate);
35707
+ if (dayDelta === null) return Number.NaN;
35708
+ minutes += dayDelta * 24 * 60;
35709
+ return minutes;
35710
+ };
35711
+ var computeSegmentOffsetUtc = (segment, shift) => {
35179
35712
  const parsedMs = Date.parse(segment.start_time);
35180
35713
  if (Number.isNaN(parsedMs)) return null;
35181
35714
  const date = new Date(parsedMs);
35182
35715
  const segHour = date.getUTCHours();
35183
35716
  const segMinute = date.getUTCMinutes();
35184
35717
  const segMinutes = segHour * 60 + segMinute;
35185
- let shiftStartMinutes = shift.startHour * 60 + shift.startMinute;
35718
+ const shiftStartMinutes = shift.startHour * 60 + shift.startMinute;
35186
35719
  let deltaMinutes = segMinutes - shiftStartMinutes;
35187
35720
  if (deltaMinutes < 0) deltaMinutes += 24 * 60;
35188
35721
  const offsetHours = deltaMinutes / 60;
35189
35722
  if (offsetHours < 0 || offsetHours > shift.slotCount) return null;
35190
35723
  return offsetHours;
35191
35724
  };
35725
+ var computeSegmentOffset = (segment, shift) => {
35726
+ if (!segment?.start_time) return null;
35727
+ if (shift.shiftDate && shift.timezone) {
35728
+ const segmentMinutes = getSegmentMinutes(
35729
+ segment.start_time,
35730
+ shift.timezone,
35731
+ shift.shiftDate
35732
+ );
35733
+ if (!Number.isFinite(segmentMinutes)) return null;
35734
+ const shiftStartMinutes = shift.startHour * 60 + shift.startMinute;
35735
+ const deltaMinutes = segmentMinutes - shiftStartMinutes;
35736
+ const offsetHours = deltaMinutes / 60;
35737
+ if (offsetHours < 0 || offsetHours > shift.slotCount) return null;
35738
+ return offsetHours;
35739
+ }
35740
+ return computeSegmentOffsetUtc(segment, shift);
35741
+ };
35192
35742
  var resolveShiftWindow2 = (params) => {
35193
35743
  const startTime = parseTimeOfDay(params.shiftStart);
35194
35744
  if (!startTime) return null;
35195
35745
  return {
35196
35746
  startHour: startTime.hour,
35197
35747
  startMinute: startTime.minute,
35198
- slotCount: params.slotCount
35748
+ slotCount: params.slotCount,
35749
+ ...params.shiftDate ? { shiftDate: params.shiftDate } : {},
35750
+ ...params.timezone ? { timezone: params.timezone } : {}
35199
35751
  };
35200
35752
  };
35753
+ var resolveTimelineEndOffset = ({
35754
+ shiftStart,
35755
+ shiftEnd,
35756
+ slotCount,
35757
+ shiftDate,
35758
+ timezone,
35759
+ now: now4
35760
+ }) => {
35761
+ const normalizedSlotCount = Number.isFinite(slotCount) && slotCount > 0 ? slotCount : 0;
35762
+ if (!shiftDate || !timezone) {
35763
+ return normalizedSlotCount;
35764
+ }
35765
+ const shiftMinutes = getShiftDurationMinutes(shiftStart, shiftEnd);
35766
+ const elapsedMinutes = getShiftElapsedMinutes({
35767
+ shiftStart,
35768
+ shiftEnd,
35769
+ shiftDate,
35770
+ timezone,
35771
+ now: now4
35772
+ });
35773
+ if (shiftMinutes === null || elapsedMinutes === null) {
35774
+ return normalizedSlotCount;
35775
+ }
35776
+ if (elapsedMinutes >= shiftMinutes) {
35777
+ return normalizedSlotCount;
35778
+ }
35779
+ return Math.max(0, Math.min(elapsedMinutes / 60, normalizedSlotCount));
35780
+ };
35781
+ var formatSkuRailLabel = (label, segmentWidth, options = {}) => {
35782
+ const horizontalPadding = options.horizontalPadding ?? 8;
35783
+ const minVisibleChars = options.minVisibleChars ?? 4;
35784
+ const averageCharacterWidth = options.averageCharacterWidth ?? 6.6;
35785
+ const compactAverageCharacterWidth = options.compactAverageCharacterWidth ?? 5;
35786
+ const cssTruncation = options.cssTruncation ?? false;
35787
+ const fullLabelThreshold = 6;
35788
+ if (!label) return null;
35789
+ if (!Number.isFinite(segmentWidth) || segmentWidth <= horizontalPadding * 2) return null;
35790
+ const usableWidth = Math.max(segmentWidth - horizontalPadding * 2, 0);
35791
+ const maxChars = Math.floor(usableWidth / averageCharacterWidth);
35792
+ if (maxChars >= Math.max(minVisibleChars, fullLabelThreshold)) {
35793
+ if (label.length <= maxChars || cssTruncation) return label;
35794
+ if (maxChars <= 1) return null;
35795
+ return `${label.slice(0, Math.max(maxChars - 1, 1)).trimEnd()}\u2026`;
35796
+ }
35797
+ const compactLabel = buildCompactSkuRailLabel(label, Math.max(maxChars, 1));
35798
+ const compactMaxChars = Math.floor(usableWidth / compactAverageCharacterWidth);
35799
+ if (compactMaxChars < 1) return null;
35800
+ if (compactLabel.length <= compactMaxChars || cssTruncation) return compactLabel;
35801
+ if (compactMaxChars <= 2) {
35802
+ return compactLabel.slice(0, compactMaxChars);
35803
+ }
35804
+ return `${compactLabel.slice(0, Math.max(compactMaxChars - 1, 1)).trimEnd()}\u2026`;
35805
+ };
35806
+ var buildCompactSkuRailLabel = (label, maxChars) => {
35807
+ const words = label.trim().split(/\s+/).filter(Boolean);
35808
+ if (words.length === 0) return label;
35809
+ const [firstWord] = words;
35810
+ const acronym = words.map((word) => word[0]).join("").toUpperCase();
35811
+ if (maxChars <= 3 && acronym) {
35812
+ return acronym;
35813
+ }
35814
+ if (firstWord.length >= 4) {
35815
+ return firstWord;
35816
+ }
35817
+ return acronym || firstWord;
35818
+ };
35201
35819
  var HourlyOutputChartComponent = ({
35202
35820
  data,
35203
35821
  pphThreshold,
35822
+ hourlyTargetOutput,
35204
35823
  shiftStart,
35205
35824
  shiftEnd,
35825
+ shiftBreaks = [],
35206
35826
  showIdleTime = false,
35207
35827
  idleTimeHourly,
35828
+ shiftDate,
35829
+ timezone,
35208
35830
  skuSegments,
35209
35831
  activeSkuId,
35210
35832
  className = ""
@@ -35212,6 +35834,7 @@ var HourlyOutputChartComponent = ({
35212
35834
  const containerRef = React144__default.useRef(null);
35213
35835
  const [containerReady, setContainerReady] = React144__default.useState(false);
35214
35836
  const [containerWidth, setContainerWidth] = React144__default.useState(0);
35837
+ const [hoveredSkuRailLabel, setHoveredSkuRailLabel] = React144__default.useState(null);
35215
35838
  const idleSlots = React144__default.useMemo(
35216
35839
  () => buildHourlyIdleSlots({
35217
35840
  idleTimeHourly,
@@ -35357,14 +35980,54 @@ var HourlyOutputChartComponent = ({
35357
35980
  }, [containerWidth]);
35358
35981
  const shiftWindow = React144__default.useMemo(() => resolveShiftWindow2({
35359
35982
  shiftStart,
35360
- slotCount: SHIFT_DURATION
35361
- }), [shiftStart, shiftEnd, SHIFT_DURATION]);
35983
+ slotCount: SHIFT_DURATION,
35984
+ shiftDate,
35985
+ timezone
35986
+ }), [shiftStart, shiftEnd, SHIFT_DURATION, shiftDate, timezone]);
35987
+ const fallbackTimelineEndOffset = React144__default.useMemo(
35988
+ () => resolveTimelineEndOffset({
35989
+ shiftStart,
35990
+ shiftEnd,
35991
+ slotCount: SHIFT_DURATION,
35992
+ shiftDate,
35993
+ timezone
35994
+ }),
35995
+ [shiftStart, shiftEnd, SHIFT_DURATION, shiftDate, timezone]
35996
+ );
35997
+ const observedTimelineEndOffset = React144__default.useMemo(() => {
35998
+ let lastObservedMinute = -1;
35999
+ idleSlots.forEach((slot) => {
36000
+ slot.idleArray.forEach((value, minuteIndex) => {
36001
+ if (value !== "x" && value !== null && value !== void 0) {
36002
+ lastObservedMinute = Math.max(
36003
+ lastObservedMinute,
36004
+ slot.hourIndex * 60 + minuteIndex
36005
+ );
36006
+ }
36007
+ });
36008
+ });
36009
+ if (lastObservedMinute < 0) return null;
36010
+ return Math.min((lastObservedMinute + 1) / 60, SHIFT_DURATION);
36011
+ }, [idleSlots, SHIFT_DURATION]);
36012
+ const timelineEndOffset = observedTimelineEndOffset ?? fallbackTimelineEndOffset;
36013
+ const targetLineEndOffset = React144__default.useMemo(() => {
36014
+ if (timelineEndOffset >= SHIFT_DURATION) {
36015
+ return SHIFT_DURATION;
36016
+ }
36017
+ if (Number.isInteger(timelineEndOffset)) {
36018
+ return timelineEndOffset;
36019
+ }
36020
+ return Math.min(Math.floor(timelineEndOffset) + 1, SHIFT_DURATION);
36021
+ }, [timelineEndOffset, SHIFT_DURATION]);
35362
36022
  const skuTimelineSegments = React144__default.useMemo(() => {
35363
- if (!skuSegments || skuSegments.length === 0 || !shiftWindow) return [];
35364
- const withOffsets = skuSegments.map((segment) => ({
35365
- segment,
35366
- offset: computeSegmentOffset(segment, shiftWindow) ?? 0
35367
- })).sort((a, b) => a.offset - b.offset);
36023
+ if (!skuSegments || skuSegments.length === 0 || !shiftWindow || timelineEndOffset <= 0) {
36024
+ return [];
36025
+ }
36026
+ const withOffsets = skuSegments.flatMap((segment) => {
36027
+ const offset = computeSegmentOffset(segment, shiftWindow);
36028
+ if (offset === null) return [];
36029
+ return [{ segment, offset }];
36030
+ }).sort((a, b) => a.offset - b.offset);
35368
36031
  if (withOffsets.length === 0) return [];
35369
36032
  const deduped = [];
35370
36033
  const DUPLICATE_OFFSET_THRESHOLD = 1 / 60;
@@ -35378,9 +36041,9 @@ var HourlyOutputChartComponent = ({
35378
36041
  deduped[0] = { ...deduped[0], offset: 0 };
35379
36042
  }
35380
36043
  return deduped.map((entry, index) => {
35381
- const nextOffset = index < deduped.length - 1 ? deduped[index + 1].offset : SHIFT_DURATION;
35382
- const start = Math.max(0, Math.min(entry.offset, SHIFT_DURATION));
35383
- const end = Math.max(start, Math.min(nextOffset, SHIFT_DURATION));
36044
+ const nextOffset = index < deduped.length - 1 ? deduped[index + 1].offset : timelineEndOffset;
36045
+ const start = Math.max(0, Math.min(entry.offset, timelineEndOffset));
36046
+ const end = Math.max(start, Math.min(nextOffset, timelineEndOffset));
35384
36047
  return {
35385
36048
  skuId: entry.segment.sku_id,
35386
36049
  label: entry.segment.sku_code,
@@ -35389,15 +36052,94 @@ var HourlyOutputChartComponent = ({
35389
36052
  pphThreshold: entry.segment.pph_threshold ?? pphThreshold
35390
36053
  };
35391
36054
  }).filter((segment) => segment.end > segment.start);
35392
- }, [skuSegments, shiftWindow, SHIFT_DURATION, pphThreshold]);
36055
+ }, [skuSegments, shiftWindow, timelineEndOffset, pphThreshold]);
36056
+ const targetTimelineSegments = React144__default.useMemo(() => {
36057
+ if (skuTimelineSegments.length === 0) return [];
36058
+ return skuTimelineSegments.map((segment, index) => ({
36059
+ ...segment,
36060
+ end: index === skuTimelineSegments.length - 1 ? Math.max(segment.start, targetLineEndOffset) : segment.end
36061
+ })).filter((segment) => segment.end > segment.start);
36062
+ }, [skuTimelineSegments, targetLineEndOffset]);
36063
+ const hasExplicitHourlyTargetOutputProp = React144__default.useMemo(
36064
+ () => hourlyTargetOutput !== void 0,
36065
+ [hourlyTargetOutput]
36066
+ );
36067
+ const fallbackHourlyTargetOutput = React144__default.useMemo(() => {
36068
+ if (hasExplicitHourlyTargetOutputProp) return void 0;
36069
+ if (skuTimelineSegments.length > 0) return void 0;
36070
+ const plan = buildHourlyTargetPlan({
36071
+ shiftStart,
36072
+ shiftEnd,
36073
+ breaks: shiftBreaks,
36074
+ pphThreshold,
36075
+ rounding: "floor"
36076
+ });
36077
+ if (!plan.targets.length) return void 0;
36078
+ return plan.targets.map((value) => Number.isFinite(value) ? value : null);
36079
+ }, [
36080
+ hasExplicitHourlyTargetOutputProp,
36081
+ skuTimelineSegments.length,
36082
+ shiftStart,
36083
+ shiftEnd,
36084
+ shiftBreaks,
36085
+ pphThreshold
36086
+ ]);
36087
+ const effectiveHourlyTargetOutput = React144__default.useMemo(
36088
+ () => hasExplicitHourlyTargetOutputProp ? hourlyTargetOutput : fallbackHourlyTargetOutput,
36089
+ [hasExplicitHourlyTargetOutputProp, hourlyTargetOutput, fallbackHourlyTargetOutput]
36090
+ );
36091
+ const hasHourlyTargetOutputProp = React144__default.useMemo(
36092
+ () => effectiveHourlyTargetOutput !== void 0,
36093
+ [effectiveHourlyTargetOutput]
36094
+ );
36095
+ const hasExplicitHourlyTargets = React144__default.useMemo(
36096
+ () => Array.isArray(effectiveHourlyTargetOutput) && effectiveHourlyTargetOutput.some((value) => value !== null && value !== void 0),
36097
+ [effectiveHourlyTargetOutput]
36098
+ );
36099
+ const hourlyTargetSegments = React144__default.useMemo(() => {
36100
+ if (!hasExplicitHourlyTargets) return [];
36101
+ const segments = [];
36102
+ let runStart = null;
36103
+ let runValue = null;
36104
+ const flush = (endIndex) => {
36105
+ if (runStart === null || runValue === null) return;
36106
+ segments.push({ start: runStart, end: endIndex, value: runValue });
36107
+ runStart = null;
36108
+ runValue = null;
36109
+ };
36110
+ for (let i = 0; i < SHIFT_DURATION; i += 1) {
36111
+ const rawValue = Array.isArray(effectiveHourlyTargetOutput) ? effectiveHourlyTargetOutput[i] : null;
36112
+ const value = rawValue === null || rawValue === void 0 ? null : Number(rawValue);
36113
+ if (value === null || !Number.isFinite(value)) {
36114
+ flush(i);
36115
+ continue;
36116
+ }
36117
+ if (runStart === null || runValue === null) {
36118
+ runStart = i;
36119
+ runValue = value;
36120
+ continue;
36121
+ }
36122
+ if (Math.abs(runValue - value) > 1e-6) {
36123
+ flush(i);
36124
+ runStart = i;
36125
+ runValue = value;
36126
+ }
36127
+ }
36128
+ flush(SHIFT_DURATION);
36129
+ return segments.filter((segment) => segment.end > segment.start);
36130
+ }, [SHIFT_DURATION, hasExplicitHourlyTargets, effectiveHourlyTargetOutput]);
35393
36131
  const activeSkuHourIndices = React144__default.useMemo(() => {
35394
36132
  const indices = /* @__PURE__ */ new Set();
35395
36133
  const targets = Array(SHIFT_DURATION).fill(pphThreshold);
35396
- if (!skuSegments || !shiftWindow) return { indices, targets };
35397
- const segmentsWithOffsets = skuSegments.map((seg) => ({
35398
- ...seg,
35399
- offset: computeSegmentOffset(seg, shiftWindow) || 0
35400
- })).sort((a, b) => a.offset - b.offset);
36134
+ if (!skuSegments || !shiftWindow) return { indices, targets, hasTimeline: false };
36135
+ const segmentsWithOffsets = skuSegments.flatMap((segment) => {
36136
+ const offset = computeSegmentOffset(segment, shiftWindow);
36137
+ if (offset === null) return [];
36138
+ return [{ ...segment, offset }];
36139
+ }).sort((a, b) => a.offset - b.offset);
36140
+ if (segmentsWithOffsets.length === 0) {
36141
+ return { indices, targets, hasTimeline: false };
36142
+ }
35401
36143
  for (let i = 0; i < SHIFT_DURATION; i++) {
35402
36144
  const midpoint = i + 0.5;
35403
36145
  let activeSeg = segmentsWithOffsets[0];
@@ -35417,13 +36159,15 @@ var HourlyOutputChartComponent = ({
35417
36159
  }
35418
36160
  }
35419
36161
  }
35420
- return { indices, targets };
36162
+ return { indices, targets, hasTimeline: true };
35421
36163
  }, [skuSegments, activeSkuId, shiftWindow, SHIFT_DURATION, pphThreshold]);
35422
36164
  const chartData = React144__default.useMemo(() => {
35423
- const { indices, targets } = activeSkuHourIndices;
36165
+ const { indices, targets, hasTimeline } = activeSkuHourIndices;
35424
36166
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
35425
36167
  const idleSlot = idleSlots[i];
35426
- const currentTarget = targets[i] || pphThreshold;
36168
+ const explicitTarget = hasHourlyTargetOutputProp ? effectiveHourlyTargetOutput?.[i] ?? null : void 0;
36169
+ const currentTarget = hasHourlyTargetOutputProp ? explicitTarget : targets[i] || pphThreshold;
36170
+ const comparisonTarget = currentTarget === null || currentTarget === void 0 ? targets[i] || pphThreshold : currentTarget;
35427
36171
  return {
35428
36172
  hourIndex: idleSlot?.hourIndex ?? i,
35429
36173
  hour: idleSlot?.hour || "",
@@ -35431,16 +36175,16 @@ var HourlyOutputChartComponent = ({
35431
36175
  output: animatedData[i] || 0,
35432
36176
  originalOutput: data[i] || 0,
35433
36177
  // Keep original data for labels
35434
- target: currentTarget,
35435
- color: (animatedData[i] || 0) >= Math.round(currentTarget) ? "#00AB45" : "#E34329",
36178
+ target: currentTarget ?? null,
36179
+ color: (animatedData[i] || 0) >= comparisonTarget ? "#00AB45" : "#E34329",
35436
36180
  idleMinutes: idleSlot?.idleMinutes || 0,
35437
36181
  idleArray: idleSlot?.idleArray || [],
35438
36182
  skuIndex: i,
35439
36183
  isHighlighted: indices.has(i),
35440
- isDimmed: !!activeSkuId && !indices.has(i)
36184
+ isDimmed: hasTimeline && !!activeSkuId && !indices.has(i)
35441
36185
  };
35442
36186
  });
35443
- }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId]);
36187
+ }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId, effectiveHourlyTargetOutput, hasHourlyTargetOutputProp]);
35444
36188
  const renderSkuTimelineRail = React144__default.useCallback((props) => {
35445
36189
  if (!skuTimelineSegments.length || SHIFT_DURATION <= 0) return null;
35446
36190
  const offset = props?.offset;
@@ -35452,8 +36196,9 @@ var HourlyOutputChartComponent = ({
35452
36196
  const railHeight = 3;
35453
36197
  const railY = top - 10;
35454
36198
  const baselineY = railY + railHeight / 2;
35455
- const labelYHigh = railY - 20;
35456
- const labelYLow = railY - 6;
36199
+ const showHoverLabel = (label, centerX, rY) => {
36200
+ setHoveredSkuRailLabel({ label, centerX, railY: rY });
36201
+ };
35457
36202
  return /* @__PURE__ */ jsxs("g", { children: [
35458
36203
  /* @__PURE__ */ jsx(
35459
36204
  "line",
@@ -35471,9 +36216,43 @@ var HourlyOutputChartComponent = ({
35471
36216
  const xStart = left + segment.start / SHIFT_DURATION * width;
35472
36217
  const xEnd = left + segment.end / SHIFT_DURATION * width;
35473
36218
  const segmentWidth = Math.max(1, xEnd - xStart);
36219
+ const labelPadding = segmentWidth < 48 ? 4 : 8;
36220
+ const inlineLabelText = formatSkuRailLabel(segment.label, segmentWidth, {
36221
+ horizontalPadding: labelPadding,
36222
+ cssTruncation: true
36223
+ });
36224
+ const badgeLabelText = formatSkuRailLabel(segment.label, 28, {
36225
+ horizontalPadding: 4,
36226
+ cssTruncation: true
36227
+ });
36228
+ const isMicroSegment = segmentWidth < 18;
36229
+ const labelText = isMicroSegment ? badgeLabelText : inlineLabelText;
36230
+ const labelClipWidth = Math.max(segmentWidth - labelPadding * 2, 0);
35474
36231
  const isActive = !!activeSkuId && segment.skuId === activeSkuId;
35475
36232
  const isDimmed = !!activeSkuId && segment.skuId !== activeSkuId;
36233
+ const hoverHitWidth = Math.max(segmentWidth + 12, isMicroSegment ? 20 : segmentWidth);
36234
+ const hoverHitX = Math.max(
36235
+ left,
36236
+ Math.min(xStart - (hoverHitWidth - segmentWidth) / 2, left + width - hoverHitWidth)
36237
+ );
36238
+ const hoverHitHeight = isMicroSegment ? 34 : 28;
36239
+ const hoverHitY = railY - (isMicroSegment ? 30 : 24);
36240
+ const microBadgeWidth = labelText ? Math.max(labelText.length * 6.2 + 10, 18) : 0;
36241
+ const microBadgeX = xStart + segmentWidth / 2 - microBadgeWidth / 2;
36242
+ const microBadgeY = railY - 24;
35476
36243
  return /* @__PURE__ */ jsxs("g", { children: [
36244
+ /* @__PURE__ */ jsx(
36245
+ "rect",
36246
+ {
36247
+ x: hoverHitX,
36248
+ y: hoverHitY,
36249
+ width: hoverHitWidth,
36250
+ height: hoverHitHeight,
36251
+ fill: "transparent",
36252
+ onMouseEnter: () => showHoverLabel(segment.label, xStart + segmentWidth / 2, railY),
36253
+ onMouseLeave: () => setHoveredSkuRailLabel((current) => current?.label === segment.label ? null : current)
36254
+ }
36255
+ ),
35477
36256
  /* @__PURE__ */ jsx(
35478
36257
  "rect",
35479
36258
  {
@@ -35484,7 +36263,7 @@ var HourlyOutputChartComponent = ({
35484
36263
  rx: 1.5,
35485
36264
  fill: isActive ? "#3b82f6" : "#cbd5e1",
35486
36265
  opacity: isDimmed ? 0.3 : 1,
35487
- style: { transition: "all 0.3s ease" }
36266
+ style: { transition: "all 0.3s ease", pointerEvents: "none" }
35488
36267
  }
35489
36268
  ),
35490
36269
  index > 0 && /* @__PURE__ */ jsx(
@@ -35496,22 +36275,59 @@ var HourlyOutputChartComponent = ({
35496
36275
  y2: railY + railHeight + 3,
35497
36276
  stroke: "#94a3b8",
35498
36277
  strokeWidth: 1,
35499
- opacity: 0.6
36278
+ opacity: 0.6,
36279
+ style: { pointerEvents: "none" }
35500
36280
  }
35501
36281
  ),
35502
- segmentWidth >= 36 && /* @__PURE__ */ jsx(
35503
- "text",
36282
+ isMicroSegment && labelText && /* @__PURE__ */ jsxs("g", { style: { pointerEvents: "none" }, children: [
36283
+ /* @__PURE__ */ jsx(
36284
+ "rect",
36285
+ {
36286
+ x: microBadgeX,
36287
+ y: microBadgeY,
36288
+ width: microBadgeWidth,
36289
+ height: 14,
36290
+ rx: 7,
36291
+ fill: "white",
36292
+ stroke: isActive ? "#3b82f6" : "#cbd5e1",
36293
+ strokeWidth: 1,
36294
+ opacity: isDimmed ? 0.55 : 0.98
36295
+ }
36296
+ ),
36297
+ /* @__PURE__ */ jsx(
36298
+ "text",
36299
+ {
36300
+ x: xStart + segmentWidth / 2,
36301
+ y: microBadgeY + 10,
36302
+ textAnchor: "middle",
36303
+ fontSize: 8,
36304
+ fontWeight: 700,
36305
+ fill: isActive ? "#2563eb" : "#64748b",
36306
+ opacity: isDimmed ? 0.55 : 1,
36307
+ style: { transition: "all 0.3s ease" },
36308
+ children: labelText
36309
+ }
36310
+ )
36311
+ ] }),
36312
+ !isMicroSegment && labelText && labelClipWidth > 0 && /* @__PURE__ */ jsx(
36313
+ "foreignObject",
35504
36314
  {
35505
- x: xStart + segmentWidth / 2,
35506
- y: index % 2 === 0 ? labelYHigh : labelYLow,
35507
- textAnchor: "middle",
35508
- fontSize: 10,
35509
- fontWeight: 700,
35510
- letterSpacing: "0.02em",
35511
- fill: isActive ? "#2563eb" : "#64748b",
35512
- opacity: isDimmed ? 0.4 : 1,
35513
- style: { transition: "all 0.3s ease", pointerEvents: "none" },
35514
- children: segment.label
36315
+ x: xStart + labelPadding,
36316
+ y: railY - 24,
36317
+ width: labelClipWidth,
36318
+ height: 18,
36319
+ style: { pointerEvents: "none", overflow: "visible" },
36320
+ children: /* @__PURE__ */ jsx(
36321
+ "div",
36322
+ {
36323
+ 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"}`,
36324
+ style: {
36325
+ opacity: isDimmed ? 0.4 : 1,
36326
+ transition: "all 0.3s ease"
36327
+ },
36328
+ children: labelText
36329
+ }
36330
+ )
35515
36331
  }
35516
36332
  )
35517
36333
  ] }, `sku-rail-${segment.skuId}-${segment.start}-${index}`);
@@ -35569,14 +36385,17 @@ var HourlyOutputChartComponent = ({
35569
36385
  );
35570
36386
  }, [idleBarState.visible, idleBarState.key, idleBarState.shouldAnimate]);
35571
36387
  const maxDataValue = Math.max(...data, 0);
35572
- const maxTargetValue = Math.max(...chartData.map((d) => d.target), pphThreshold, 0);
36388
+ const numericChartTargets = chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target));
36389
+ const maxTargetValue = Math.max(...numericChartTargets, pphThreshold, 0);
35573
36390
  const maxYValue = Math.max(
35574
36391
  Math.ceil(maxTargetValue * 1.5),
35575
36392
  Math.ceil(maxDataValue * 1.15)
35576
36393
  // Add 15% headroom above max value
35577
36394
  );
35578
36395
  const generateYAxisTicks = () => {
35579
- const uniqueTargets = [...new Set(chartData.map((d) => Math.round(d.target)))].sort((a, b) => a - b);
36396
+ const uniqueTargets = [...new Set(
36397
+ chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target)).map((target) => Math.round(target))
36398
+ )].sort((a, b) => a - b);
35580
36399
  const rawTicks = [0];
35581
36400
  uniqueTargets.forEach((target) => {
35582
36401
  if (target > 0) {
@@ -35611,14 +36430,54 @@ var HourlyOutputChartComponent = ({
35611
36430
  };
35612
36431
  const renderTargetLine = React144__default.useCallback((props) => {
35613
36432
  const { offset, yAxisMap } = props;
35614
- if (!offset || !yAxisMap || SHIFT_DURATION <= 0) return null;
36433
+ if (!offset || !yAxisMap || SHIFT_DURATION <= 0 || targetLineEndOffset <= 0) return null;
35615
36434
  const { left, width } = offset;
35616
36435
  const yAxis = yAxisMap["default"] || yAxisMap[0];
35617
36436
  if (!yAxis || !yAxis.scale) return null;
35618
36437
  const lines = [];
35619
36438
  const offsetToX = (o) => left + o / SHIFT_DURATION * width;
35620
- if (skuTimelineSegments.length > 0) {
35621
- skuTimelineSegments.forEach((segment, index) => {
36439
+ if (hasHourlyTargetOutputProp && hourlyTargetSegments.length > 0) {
36440
+ hourlyTargetSegments.forEach((segment, index) => {
36441
+ const y = yAxis.scale(segment.value);
36442
+ const xStart = offsetToX(segment.start);
36443
+ const xEnd = offsetToX(segment.end);
36444
+ lines.push(
36445
+ /* @__PURE__ */ jsx(
36446
+ "line",
36447
+ {
36448
+ x1: xStart,
36449
+ y1: y,
36450
+ x2: xEnd,
36451
+ y2: y,
36452
+ stroke: "#E34329",
36453
+ strokeDasharray: "3 3",
36454
+ strokeWidth: 2
36455
+ },
36456
+ `target-hourly-h-${index}`
36457
+ )
36458
+ );
36459
+ const next = hourlyTargetSegments[index + 1];
36460
+ if (next && Math.abs(next.value - segment.value) > 1e-6) {
36461
+ const nextY = yAxis.scale(next.value);
36462
+ lines.push(
36463
+ /* @__PURE__ */ jsx(
36464
+ "line",
36465
+ {
36466
+ x1: xEnd,
36467
+ y1: y,
36468
+ x2: xEnd,
36469
+ y2: nextY,
36470
+ stroke: "#E34329",
36471
+ strokeDasharray: "3 3",
36472
+ strokeWidth: 2
36473
+ },
36474
+ `target-hourly-v-${index}`
36475
+ )
36476
+ );
36477
+ }
36478
+ });
36479
+ } else if (!hasHourlyTargetOutputProp && targetTimelineSegments.length > 0) {
36480
+ targetTimelineSegments.forEach((segment, index) => {
35622
36481
  const target = segment.pphThreshold || pphThreshold;
35623
36482
  const y = yAxis.scale(target);
35624
36483
  const xStart = offsetToX(segment.start);
@@ -35638,7 +36497,7 @@ var HourlyOutputChartComponent = ({
35638
36497
  `target-h-${index}`
35639
36498
  )
35640
36499
  );
35641
- const next = skuTimelineSegments[index + 1];
36500
+ const next = targetTimelineSegments[index + 1];
35642
36501
  if (next) {
35643
36502
  const nextTarget = next.pphThreshold || pphThreshold;
35644
36503
  if (nextTarget !== target) {
@@ -35661,7 +36520,7 @@ var HourlyOutputChartComponent = ({
35661
36520
  }
35662
36521
  }
35663
36522
  });
35664
- } else {
36523
+ } else if (!hasHourlyTargetOutputProp) {
35665
36524
  const y = yAxis.scale(pphThreshold);
35666
36525
  lines.push(
35667
36526
  /* @__PURE__ */ jsx(
@@ -35680,10 +36539,13 @@ var HourlyOutputChartComponent = ({
35680
36539
  );
35681
36540
  }
35682
36541
  return /* @__PURE__ */ jsx("g", { children: lines });
35683
- }, [skuTimelineSegments, SHIFT_DURATION, pphThreshold]);
36542
+ }, [hourlyTargetSegments, targetTimelineSegments, SHIFT_DURATION, pphThreshold, targetLineEndOffset, hasHourlyTargetOutputProp]);
35684
36543
  const renderLegend = () => {
35685
- const uniqueTargets = [...new Set(chartData.map((d) => Math.round(d.target)))].sort((a, b) => a - b);
35686
- const targetText = uniqueTargets.length === 1 ? `Target: ${uniqueTargets[0]} units/hr` : `Target: ${uniqueTargets[0]} - ${uniqueTargets[uniqueTargets.length - 1]} units/hr`;
36544
+ const uniqueTargets = [...new Set(
36545
+ chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target)).map((target) => Math.round(target))
36546
+ )].sort((a, b) => a - b);
36547
+ const unitLabel = hasHourlyTargetOutputProp ? "units" : "units/hr";
36548
+ const targetText = uniqueTargets.length === 0 ? `Target` : uniqueTargets.length === 1 ? `Target: ${uniqueTargets[0]} ${unitLabel}` : `Target: ${uniqueTargets[0]} - ${uniqueTargets[uniqueTargets.length - 1]} ${unitLabel}`;
35687
36549
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-sm text-gray-600 bg-white py-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border border-gray-100 rounded-full px-3 py-1", children: [
35688
36550
  /* @__PURE__ */ jsx("div", { className: "w-8 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t-2 border-[#E34329] border-dashed" }) }),
35689
36551
  /* @__PURE__ */ jsx("span", { children: targetText })
@@ -35696,254 +36558,274 @@ var HourlyOutputChartComponent = ({
35696
36558
  className: `w-full h-full min-w-0 flex flex-col ${className}`,
35697
36559
  style: { minHeight: "200px", minWidth: 0 },
35698
36560
  children: [
35699
- containerReady ? /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
35700
- BarChart$1,
35701
- {
35702
- data: chartData,
35703
- margin: {
35704
- // Reserve headroom for the SKU timeline rail + staggered
35705
- // labels only when SKU segments are rendered. Non-SKU charts
35706
- // keep the original 10px top so recharts has enough vertical
35707
- // space to show the target (pph) tick label on the Y-axis.
35708
- top: skuTimelineSegments.length > 0 ? 40 : 10,
35709
- right: 10,
35710
- bottom: 10,
35711
- left: 6
35712
- },
35713
- barCategoryGap: "25%",
35714
- children: [
35715
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
35716
- /* @__PURE__ */ jsx(
35717
- XAxis,
35718
- {
35719
- xAxisId: "default",
35720
- dataKey: "hour",
35721
- tick: { fontSize: xAxisConfig.tickFont },
35722
- interval: xAxisConfig.interval,
35723
- angle: xAxisConfig.angle,
35724
- textAnchor: "end",
35725
- tickMargin: xAxisConfig.tickMargin,
35726
- height: xAxisConfig.height
35727
- }
35728
- ),
35729
- /* @__PURE__ */ jsx(
35730
- XAxis,
35731
- {
35732
- xAxisId: "sku",
35733
- type: "number",
35734
- dataKey: "skuIndex",
35735
- domain: [0, Math.max(SHIFT_DURATION, 0)],
35736
- hide: true,
35737
- allowDataOverflow: true
35738
- }
35739
- ),
35740
- /* @__PURE__ */ jsx(
35741
- YAxis,
35742
- {
35743
- yAxisId: "default",
35744
- tickMargin: 8,
35745
- width: 48,
35746
- domain: [0, maxYValue],
35747
- ticks: generateYAxisTicks(),
35748
- tickFormatter: (value) => value,
35749
- tick: (props) => {
35750
- const { x, y, payload } = props;
35751
- return /* @__PURE__ */ jsx("g", { transform: `translate(${x},${y})`, children: /* @__PURE__ */ jsx(
35752
- "text",
35753
- {
35754
- x: -2,
35755
- y: 0,
35756
- dy: 4,
35757
- textAnchor: "end",
35758
- fill: "#666",
35759
- fontSize: 12,
35760
- children: payload.value
35761
- },
35762
- `tick-${payload.value}-${x}-${y}`
35763
- ) });
36561
+ containerReady ? /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 relative", children: [
36562
+ /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
36563
+ BarChart$1,
36564
+ {
36565
+ data: chartData,
36566
+ margin: {
36567
+ // Reserve headroom for the SKU timeline rail + staggered
36568
+ // labels only when SKU segments are rendered. Non-SKU charts
36569
+ // keep the original 10px top so recharts has enough vertical
36570
+ // space to show the target (pph) tick label on the Y-axis.
36571
+ top: skuTimelineSegments.length > 0 ? 40 : 10,
36572
+ right: 10,
36573
+ bottom: 10,
36574
+ left: 6
36575
+ },
36576
+ barCategoryGap: "25%",
36577
+ children: [
36578
+ /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
36579
+ /* @__PURE__ */ jsx(
36580
+ XAxis,
36581
+ {
36582
+ xAxisId: "default",
36583
+ dataKey: "hour",
36584
+ tick: { fontSize: xAxisConfig.tickFont },
36585
+ interval: xAxisConfig.interval,
36586
+ angle: xAxisConfig.angle,
36587
+ textAnchor: "end",
36588
+ tickMargin: xAxisConfig.tickMargin,
36589
+ height: xAxisConfig.height
35764
36590
  }
35765
- }
35766
- ),
35767
- /* @__PURE__ */ jsx(YAxis, { yAxisId: "idle", domain: [0, 60], hide: true }),
35768
- /* @__PURE__ */ jsx(
35769
- Tooltip,
35770
- {
35771
- cursor: false,
35772
- contentStyle: {
35773
- backgroundColor: "white",
35774
- border: "none",
35775
- borderRadius: "12px",
35776
- boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
35777
- padding: "0",
35778
- fontSize: "14px"
35779
- },
35780
- content: (props) => {
35781
- if (!props.active || !props.payload || props.payload.length === 0)
35782
- return null;
35783
- const data2 = props.payload[0].payload;
35784
- const idlePeriods = showIdleTime ? getHourlyIdlePeriods({
35785
- idleArray: data2.idleArray,
35786
- shiftStart,
35787
- hourIndex: Number.isFinite(data2.hourIndex) ? data2.hourIndex : 0
35788
- }) : [];
35789
- return /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-xl shadow-xl border border-gray-100 p-4 min-w-[220px]", children: [
35790
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ jsx("p", { className: "font-semibold text-gray-900 text-sm", children: data2.timeRange }) }),
35791
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
35792
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
35793
- /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Output" }),
35794
- /* @__PURE__ */ jsxs("span", { className: "font-semibold text-gray-900 text-sm", children: [
35795
- Math.round(data2.output),
35796
- " units"
35797
- ] })
35798
- ] }),
35799
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
35800
- /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Target" }),
35801
- /* @__PURE__ */ jsxs("span", { className: "font-semibold text-gray-600 text-sm", children: [
35802
- Math.round(data2.target),
35803
- " units"
35804
- ] })
35805
- ] }),
35806
- showIdleTime && data2.idleMinutes > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
35807
- /* @__PURE__ */ jsx("div", { className: "border-t border-gray-100 pt-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
35808
- /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Idle Time" }),
35809
- /* @__PURE__ */ jsxs("span", { className: "font-semibold text-orange-600 text-sm", children: [
35810
- data2.idleMinutes,
35811
- " minutes"
36591
+ ),
36592
+ /* @__PURE__ */ jsx(
36593
+ XAxis,
36594
+ {
36595
+ xAxisId: "sku",
36596
+ type: "number",
36597
+ dataKey: "skuIndex",
36598
+ domain: [0, Math.max(SHIFT_DURATION, 0)],
36599
+ hide: true,
36600
+ allowDataOverflow: true
36601
+ }
36602
+ ),
36603
+ /* @__PURE__ */ jsx(
36604
+ YAxis,
36605
+ {
36606
+ yAxisId: "default",
36607
+ tickMargin: 8,
36608
+ width: 48,
36609
+ domain: [0, maxYValue],
36610
+ ticks: generateYAxisTicks(),
36611
+ tickFormatter: (value) => value,
36612
+ tick: (props) => {
36613
+ const { x, y, payload } = props;
36614
+ return /* @__PURE__ */ jsx("g", { transform: `translate(${x},${y})`, children: /* @__PURE__ */ jsx(
36615
+ "text",
36616
+ {
36617
+ x: -2,
36618
+ y: 0,
36619
+ dy: 4,
36620
+ textAnchor: "end",
36621
+ fill: "#666",
36622
+ fontSize: 12,
36623
+ children: payload.value
36624
+ },
36625
+ `tick-${payload.value}-${x}-${y}`
36626
+ ) });
36627
+ }
36628
+ }
36629
+ ),
36630
+ /* @__PURE__ */ jsx(YAxis, { yAxisId: "idle", domain: [0, 60], hide: true }),
36631
+ /* @__PURE__ */ jsx(
36632
+ Tooltip,
36633
+ {
36634
+ cursor: { fill: "#f1f5f9" },
36635
+ contentStyle: { backgroundColor: "transparent", border: "none", padding: 0 },
36636
+ content: (props) => {
36637
+ if (!props.active || !props.payload || props.payload.length === 0)
36638
+ return null;
36639
+ const data2 = props.payload[0].payload;
36640
+ const idlePeriods = showIdleTime ? getHourlyIdlePeriods({
36641
+ idleArray: data2.idleArray,
36642
+ shiftStart,
36643
+ hourIndex: Number.isFinite(data2.hourIndex) ? data2.hourIndex : 0
36644
+ }) : [];
36645
+ return /* @__PURE__ */ 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: [
36646
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between mb-4 pb-3 border-b border-slate-100", children: /* @__PURE__ */ jsx("p", { className: "font-semibold text-slate-900 text-sm tracking-tight", children: data2.timeRange }) }),
36647
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
36648
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
36649
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500 font-medium tracking-wide", children: "Output" }),
36650
+ /* @__PURE__ */ jsxs("span", { className: "font-bold text-slate-900 text-sm", children: [
36651
+ Math.round(data2.output),
36652
+ " ",
36653
+ /* @__PURE__ */ jsx("span", { className: "text-slate-400 font-normal text-xs ml-0.5", children: "units" })
36654
+ ] })
36655
+ ] }),
36656
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
36657
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500 font-medium tracking-wide", children: "Target" }),
36658
+ /* @__PURE__ */ jsxs("span", { className: "font-bold text-slate-700 text-sm", children: [
36659
+ Math.round(data2.target),
36660
+ " ",
36661
+ /* @__PURE__ */ jsx("span", { className: "text-slate-400 font-normal text-xs ml-0.5", children: "units" })
36662
+ ] })
36663
+ ] }),
36664
+ showIdleTime && data2.idleMinutes > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
36665
+ /* @__PURE__ */ jsx("div", { className: "pt-3 mt-3 border-t border-slate-100", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
36666
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500 font-medium tracking-wide", children: "Idle Time" }),
36667
+ /* @__PURE__ */ jsxs("span", { className: "font-bold text-orange-600 text-sm flex items-center gap-1.5", children: [
36668
+ /* @__PURE__ */ 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" }),
36669
+ data2.idleMinutes,
36670
+ " ",
36671
+ /* @__PURE__ */ jsx("span", { className: "text-orange-500/70 font-normal text-xs ml-0.5", children: "min" })
36672
+ ] })
36673
+ ] }) }),
36674
+ idlePeriods.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 bg-slate-50/80 rounded-lg p-3 border border-slate-100/50", children: [
36675
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-slate-400 text-[10px] mb-2.5 uppercase tracking-wider", children: "Idle Periods" }),
36676
+ /* @__PURE__ */ jsx("div", { className: "space-y-2.5 max-h-32 overflow-y-auto pr-1 custom-scrollbar", children: idlePeriods.map((period, index) => {
36677
+ return /* @__PURE__ */ jsxs(
36678
+ "div",
36679
+ {
36680
+ className: "flex items-start gap-2.5 text-xs",
36681
+ children: [
36682
+ /* @__PURE__ */ 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)]" }),
36683
+ /* @__PURE__ */ jsx("span", { className: "text-slate-700 font-medium tracking-tight", children: period.duration === 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
36684
+ period.startTime,
36685
+ " ",
36686
+ /* @__PURE__ */ jsxs("span", { className: "text-slate-400 font-normal ml-1", children: [
36687
+ "(",
36688
+ period.duration,
36689
+ "m)"
36690
+ ] })
36691
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
36692
+ period.startTime,
36693
+ " ",
36694
+ /* @__PURE__ */ jsx("span", { className: "text-slate-400 mx-0.5", children: "\u2192" }),
36695
+ " ",
36696
+ period.endTime,
36697
+ " ",
36698
+ /* @__PURE__ */ jsxs("span", { className: "text-slate-400 font-normal ml-1", children: [
36699
+ "(",
36700
+ period.duration,
36701
+ "m)"
36702
+ ] })
36703
+ ] }) })
36704
+ ]
36705
+ },
36706
+ index
36707
+ );
36708
+ }) })
35812
36709
  ] })
35813
- ] }) }),
35814
- idlePeriods.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 bg-gray-50 rounded-lg p-2.5", children: [
35815
- /* @__PURE__ */ jsx("p", { className: "font-medium text-gray-700 text-xs mb-2", children: "Idle periods:" }),
35816
- /* @__PURE__ */ jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idlePeriods.map((period, index) => {
35817
- return /* @__PURE__ */ jsxs(
35818
- "div",
35819
- {
35820
- className: "text-gray-600 flex items-center gap-2 text-xs",
35821
- children: [
35822
- /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0" }),
35823
- /* @__PURE__ */ jsx("span", { children: period.duration === 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
35824
- period.startTime,
35825
- " (",
35826
- period.duration,
35827
- " min)"
35828
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
35829
- period.startTime,
35830
- " -",
35831
- " ",
35832
- period.endTime,
35833
- " (",
35834
- period.duration,
35835
- " mins)"
35836
- ] }) })
35837
- ]
35838
- },
35839
- index
35840
- );
35841
- }) })
35842
36710
  ] })
35843
36711
  ] })
35844
- ] })
35845
- ] });
35846
- },
35847
- animationDuration: 200
35848
- }
35849
- ),
35850
- /* @__PURE__ */ jsx(Customized, { component: renderTargetLine }),
35851
- /* @__PURE__ */ jsx(Customized, { component: renderSkuTimelineRail }),
35852
- /* @__PURE__ */ jsxs(
35853
- Bar,
35854
- {
35855
- xAxisId: "default",
35856
- dataKey: "output",
35857
- yAxisId: "default",
35858
- maxBarSize: 35,
35859
- radius: [10, 10, 0, 0],
35860
- isAnimationActive: false,
35861
- children: [
35862
- chartData.map((entry, index) => /* @__PURE__ */ jsx(
35863
- Cell,
35864
- {
35865
- fill: entry.color,
35866
- stroke: "transparent",
35867
- strokeWidth: 0,
35868
- style: {
35869
- filter: entry.isHighlighted ? "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)" : "brightness(1)",
35870
- transform: entry.isHighlighted ? "translateY(-4px)" : "translateY(0)",
35871
- opacity: entry.isDimmed ? 0.4 : 1,
35872
- transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
35873
- cursor: "pointer"
35874
- },
35875
- onMouseEnter: (e) => {
35876
- const target = e.target;
35877
- target.style.filter = "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)";
35878
- target.style.transform = "translateY(-4px)";
35879
- target.style.opacity = "1";
36712
+ ] });
36713
+ },
36714
+ animationDuration: 200
36715
+ }
36716
+ ),
36717
+ /* @__PURE__ */ jsx(Customized, { component: renderTargetLine }),
36718
+ /* @__PURE__ */ jsx(Customized, { component: renderSkuTimelineRail }),
36719
+ /* @__PURE__ */ jsxs(
36720
+ Bar,
36721
+ {
36722
+ xAxisId: "default",
36723
+ dataKey: "output",
36724
+ yAxisId: "default",
36725
+ maxBarSize: 35,
36726
+ radius: [10, 10, 0, 0],
36727
+ isAnimationActive: false,
36728
+ children: [
36729
+ chartData.map((entry, index) => /* @__PURE__ */ jsx(
36730
+ Cell,
36731
+ {
36732
+ fill: entry.color,
36733
+ stroke: "transparent",
36734
+ strokeWidth: 0,
36735
+ style: {
36736
+ filter: entry.isHighlighted ? "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)" : "brightness(1)",
36737
+ transform: entry.isHighlighted ? "translateY(-4px)" : "translateY(0)",
36738
+ opacity: entry.isDimmed ? 0.4 : 1,
36739
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
36740
+ cursor: "pointer"
36741
+ },
36742
+ onMouseEnter: (e) => {
36743
+ const target = e.target;
36744
+ target.style.filter = "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)";
36745
+ target.style.transform = "translateY(-4px)";
36746
+ target.style.opacity = "1";
36747
+ },
36748
+ onMouseLeave: (e) => {
36749
+ const target = e.target;
36750
+ target.style.filter = entry.isHighlighted ? "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)" : "brightness(1)";
36751
+ target.style.transform = entry.isHighlighted ? "translateY(-4px)" : "translateY(0)";
36752
+ target.style.opacity = entry.isDimmed ? "0.4" : "1";
36753
+ }
35880
36754
  },
35881
- onMouseLeave: (e) => {
35882
- const target = e.target;
35883
- target.style.filter = entry.isHighlighted ? "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)" : "brightness(1)";
35884
- target.style.transform = entry.isHighlighted ? "translateY(-4px)" : "translateY(0)";
35885
- target.style.opacity = entry.isDimmed ? "0.4" : "1";
36755
+ `cell-${index}`
36756
+ )),
36757
+ /* @__PURE__ */ jsx(
36758
+ LabelList,
36759
+ {
36760
+ dataKey: "originalOutput",
36761
+ position: "top",
36762
+ content: (props) => {
36763
+ const { x, y, width, value, payload } = props;
36764
+ const actualValue = payload?.originalOutput || value;
36765
+ if (!actualValue || actualValue === 0) return null;
36766
+ return /* @__PURE__ */ jsx(
36767
+ "text",
36768
+ {
36769
+ x: x + width / 2,
36770
+ y: y - 8,
36771
+ textAnchor: "middle",
36772
+ fontSize: "12",
36773
+ fontWeight: "600",
36774
+ fill: "#374151",
36775
+ style: {
36776
+ opacity: 1,
36777
+ pointerEvents: "none",
36778
+ transition: "none"
36779
+ },
36780
+ children: Math.round(actualValue)
36781
+ }
36782
+ );
36783
+ }
35886
36784
  }
35887
- },
35888
- `cell-${index}`
35889
- )),
35890
- /* @__PURE__ */ jsx(
35891
- LabelList,
35892
- {
35893
- dataKey: "originalOutput",
35894
- position: "top",
35895
- content: (props) => {
35896
- const { x, y, width, value, payload } = props;
35897
- const actualValue = payload?.originalOutput || value;
35898
- if (!actualValue || actualValue === 0) return null;
35899
- return /* @__PURE__ */ jsx(
35900
- "text",
35901
- {
35902
- x: x + width / 2,
35903
- y: y - 8,
35904
- textAnchor: "middle",
35905
- fontSize: "12",
35906
- fontWeight: "600",
35907
- fill: "#374151",
35908
- style: {
35909
- opacity: 1,
35910
- pointerEvents: "none",
35911
- transition: "none"
35912
- },
35913
- children: Math.round(actualValue)
35914
- }
35915
- );
36785
+ )
36786
+ ]
36787
+ }
36788
+ ),
36789
+ IdleBar,
36790
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
36791
+ "pattern",
36792
+ {
36793
+ id: "idlePattern",
36794
+ patternUnits: "userSpaceOnUse",
36795
+ width: "4",
36796
+ height: "4",
36797
+ children: [
36798
+ /* @__PURE__ */ jsx("rect", { width: "4", height: "4", fill: "#4b5563", opacity: "0.6" }),
36799
+ /* @__PURE__ */ jsx(
36800
+ "path",
36801
+ {
36802
+ d: "M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2",
36803
+ stroke: "#374151",
36804
+ strokeWidth: "0.8",
36805
+ opacity: "0.8"
35916
36806
  }
35917
- }
35918
- )
35919
- ]
35920
- }
35921
- ),
35922
- IdleBar,
35923
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
35924
- "pattern",
35925
- {
35926
- id: "idlePattern",
35927
- patternUnits: "userSpaceOnUse",
35928
- width: "4",
35929
- height: "4",
35930
- children: [
35931
- /* @__PURE__ */ jsx("rect", { width: "4", height: "4", fill: "#4b5563", opacity: "0.6" }),
35932
- /* @__PURE__ */ jsx(
35933
- "path",
35934
- {
35935
- d: "M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2",
35936
- stroke: "#374151",
35937
- strokeWidth: "0.8",
35938
- opacity: "0.8"
35939
- }
35940
- )
35941
- ]
35942
- }
35943
- ) })
35944
- ]
35945
- }
35946
- ) }) }) : /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center bg-gray-50 rounded-lg", children: /* @__PURE__ */ jsx("div", { className: "text-gray-500 text-sm", children: "Loading chart..." }) }),
36807
+ )
36808
+ ]
36809
+ }
36810
+ ) })
36811
+ ]
36812
+ }
36813
+ ) }),
36814
+ hoveredSkuRailLabel && /* @__PURE__ */ jsx(
36815
+ "div",
36816
+ {
36817
+ className: "absolute z-50 pointer-events-none transform -translate-x-1/2 -translate-y-full transition-opacity duration-200",
36818
+ style: {
36819
+ left: hoveredSkuRailLabel.centerX,
36820
+ top: hoveredSkuRailLabel.railY - 12
36821
+ },
36822
+ children: /* @__PURE__ */ 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: [
36823
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold tracking-wider text-blue-500 uppercase leading-none mb-1", children: "SKU" }),
36824
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] font-semibold text-slate-800 whitespace-normal break-words leading-snug", children: hoveredSkuRailLabel.label })
36825
+ ] })
36826
+ }
36827
+ )
36828
+ ] }) : /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center bg-gray-50 rounded-lg", children: /* @__PURE__ */ jsx("div", { className: "text-gray-500 text-sm", children: "Loading chart..." }) }),
35947
36829
  /* @__PURE__ */ jsx("div", { className: "flex-none pt-2", children: renderLegend() })
35948
36830
  ]
35949
36831
  }
@@ -35961,6 +36843,38 @@ var HourlyOutputChart = React144__default.memo(
35961
36843
  if (!prevProps.data.every((val, idx) => val === nextProps.data[idx])) {
35962
36844
  return false;
35963
36845
  }
36846
+ const prevHasHourlyTargetOutputProp = prevProps.hourlyTargetOutput !== void 0;
36847
+ const nextHasHourlyTargetOutputProp = nextProps.hourlyTargetOutput !== void 0;
36848
+ if (prevHasHourlyTargetOutputProp !== nextHasHourlyTargetOutputProp) {
36849
+ return false;
36850
+ }
36851
+ if (prevProps.hourlyTargetOutput === null || nextProps.hourlyTargetOutput === null) {
36852
+ if (prevProps.hourlyTargetOutput !== nextProps.hourlyTargetOutput) {
36853
+ return false;
36854
+ }
36855
+ }
36856
+ const prevHourlyTargets = prevProps.hourlyTargetOutput || [];
36857
+ const nextHourlyTargets = nextProps.hourlyTargetOutput || [];
36858
+ if (prevHourlyTargets.length !== nextHourlyTargets.length) {
36859
+ return false;
36860
+ }
36861
+ for (let i = 0; i < prevHourlyTargets.length; i += 1) {
36862
+ if (prevHourlyTargets[i] !== nextHourlyTargets[i]) {
36863
+ return false;
36864
+ }
36865
+ }
36866
+ const prevShiftBreaks = prevProps.shiftBreaks || [];
36867
+ const nextShiftBreaks = nextProps.shiftBreaks || [];
36868
+ if (prevShiftBreaks.length !== nextShiftBreaks.length) {
36869
+ return false;
36870
+ }
36871
+ for (let i = 0; i < prevShiftBreaks.length; i += 1) {
36872
+ const prevBreak = prevShiftBreaks[i] || {};
36873
+ const nextBreak = nextShiftBreaks[i] || {};
36874
+ if (prevBreak.startTime !== nextBreak.startTime || prevBreak.endTime !== nextBreak.endTime || prevBreak.duration !== nextBreak.duration || prevBreak.remarks !== nextBreak.remarks) {
36875
+ return false;
36876
+ }
36877
+ }
35964
36878
  const prevIdle = prevProps.idleTimeHourly || {};
35965
36879
  const nextIdle = nextProps.idleTimeHourly || {};
35966
36880
  const prevKeys = Object.keys(prevIdle);
@@ -36148,7 +37062,6 @@ var VideoCard = React144__default.memo(({
36148
37062
  shouldPlay,
36149
37063
  onClick,
36150
37064
  onFatalError,
36151
- isVeryLowEfficiency = false,
36152
37065
  legend,
36153
37066
  cropping,
36154
37067
  canvasFps = 30,
@@ -36220,11 +37133,6 @@ var VideoCard = React144__default.memo(({
36220
37133
  }
36221
37134
  },
36222
37135
  children: [
36223
- isVeryLowEfficiency && /* @__PURE__ */ jsx("div", { className: `absolute ${compact ? "top-0.5 left-1" : "top-1 left-2"} z-30`, children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
36224
- /* @__PURE__ */ jsx("div", { className: "absolute -inset-1 bg-red-400/50 rounded-full blur-sm animate-pulse" }),
36225
- /* @__PURE__ */ jsx("div", { className: "absolute -inset-0.5 bg-red-500/30 rounded-full blur-md animate-ping [animation-duration:1.5s]" }),
36226
- /* @__PURE__ */ 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: "!" })
36227
- ] }) }),
36228
37136
  /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full overflow-hidden bg-black", children: [
36229
37137
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black z-0", children: /* @__PURE__ */ jsxs("div", { className: "animate-pulse flex flex-col items-center", children: [
36230
37138
  /* @__PURE__ */ jsx(Camera, { className: `w-5 h-5 sm:${compact ? "w-4 h-4" : "w-6 h-6"} text-gray-500` }),
@@ -36634,7 +37542,6 @@ var VideoGridView = React144__default.memo(({
36634
37542
  const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
36635
37543
  const workspaceKey = `${workspace.line_id || "unknown"}-${workspaceId}`;
36636
37544
  const isVisible = visibleWorkspaces.has(workspaceId);
36637
- const isVeryLowEfficiency = workspace.show_exclamation ?? (workspace.efficiency < 50 && workspace.efficiency >= 10);
36638
37545
  const workspaceCropping = getWorkspaceCropping(workspaceId, workspace.workspace_name);
36639
37546
  const workspaceStream = videoStreamsByWorkspaceId?.[workspaceId];
36640
37547
  const lastSeenLabel = workspace.workspace_uuid ? lastSeenByWorkspaceId[workspace.workspace_uuid]?.timeSinceLastUpdate : void 0;
@@ -36651,7 +37558,6 @@ var VideoGridView = React144__default.memo(({
36651
37558
  workspaceId,
36652
37559
  workspaceKey,
36653
37560
  isVisible,
36654
- isVeryLowEfficiency,
36655
37561
  workspaceCropping,
36656
37562
  fallbackUrl,
36657
37563
  hlsUrl,
@@ -36699,7 +37605,6 @@ var VideoGridView = React144__default.memo(({
36699
37605
  isR2Stream: card.isR2Stream,
36700
37606
  fallbackUrl: card.fallbackUrl
36701
37607
  }),
36702
- isVeryLowEfficiency: card.isVeryLowEfficiency,
36703
37608
  legend: effectiveLegend,
36704
37609
  cropping: card.workspaceCropping,
36705
37610
  canvasFps: effectiveCanvasFps,
@@ -37619,223 +38524,6 @@ var UptimeLineChartComponent = ({ points, className = "" }) => {
37619
38524
  ] }) }) });
37620
38525
  };
37621
38526
  var UptimeLineChart = React144__default.memo(UptimeLineChartComponent);
37622
- var padTime = (value) => value.toString().padStart(2, "0");
37623
- var parseTime = (timeValue) => {
37624
- if (!timeValue) return null;
37625
- const [hourPart, minutePart] = timeValue.split(":");
37626
- const hour = Number.parseInt(hourPart, 10);
37627
- const minute = Number.parseInt(minutePart ?? "0", 10);
37628
- if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null;
37629
- return { hour, minute };
37630
- };
37631
- var normalizeIdleTimeHourly = (idleTimeHourly) => {
37632
- if (!idleTimeHourly || typeof idleTimeHourly !== "object") {
37633
- return {};
37634
- }
37635
- return Object.fromEntries(
37636
- Object.entries(idleTimeHourly).map(([key, value]) => {
37637
- if (Array.isArray(value)) return [key, value];
37638
- if (value && Array.isArray(value.values)) {
37639
- return [key, value.values];
37640
- }
37641
- return [key, []];
37642
- })
37643
- );
37644
- };
37645
- var interpretIdleValue = (value) => {
37646
- if (value === 1 || value === "1") return "idle";
37647
- if (value === 0 || value === "0") return "active";
37648
- if (value === "x" || value === null || value === void 0) return "unknown";
37649
- return "unknown";
37650
- };
37651
- var getShiftDurationMinutes = (shiftStart, shiftEnd) => {
37652
- const start = parseTime(shiftStart);
37653
- const end = parseTime(shiftEnd);
37654
- if (!start || !end) return null;
37655
- let duration = end.hour * 60 + end.minute - (start.hour * 60 + start.minute);
37656
- if (duration <= 0) {
37657
- duration += 24 * 60;
37658
- }
37659
- return duration > 0 ? duration : null;
37660
- };
37661
- var getShiftElapsedMinutes = ({
37662
- shiftStart,
37663
- shiftEnd,
37664
- shiftDate,
37665
- timezone,
37666
- now: now4 = /* @__PURE__ */ new Date()
37667
- }) => {
37668
- if (!shiftDate || !timezone) return null;
37669
- const startTime = parseTime(shiftStart);
37670
- const endTime = parseTime(shiftEnd);
37671
- if (!startTime || !endTime) return null;
37672
- const shiftStartDate = fromZonedTime(
37673
- `${shiftDate}T${padTime(startTime.hour)}:${padTime(startTime.minute)}:00`,
37674
- timezone
37675
- );
37676
- let shiftEndDate = fromZonedTime(
37677
- `${shiftDate}T${padTime(endTime.hour)}:${padTime(endTime.minute)}:00`,
37678
- timezone
37679
- );
37680
- if (shiftEndDate <= shiftStartDate) {
37681
- shiftEndDate = addDays(shiftEndDate, 1);
37682
- }
37683
- const shiftMinutes = Math.max(differenceInMinutes(shiftEndDate, shiftStartDate), 0);
37684
- if (shiftMinutes <= 0) return null;
37685
- const elapsed = differenceInMinutes(now4, shiftStartDate);
37686
- return Math.min(Math.max(elapsed, 0), shiftMinutes);
37687
- };
37688
- var maskFutureHourlySeries = ({
37689
- data,
37690
- shiftStart,
37691
- shiftEnd,
37692
- shiftDate,
37693
- timezone,
37694
- now: now4 = /* @__PURE__ */ new Date()
37695
- }) => {
37696
- if (!Array.isArray(data)) {
37697
- return [];
37698
- }
37699
- const normalizedData = data.map((value) => typeof value === "number" && Number.isFinite(value) ? value : null);
37700
- if (!normalizedData.length) {
37701
- return normalizedData;
37702
- }
37703
- const shiftMinutes = getShiftDurationMinutes(shiftStart, shiftEnd);
37704
- const elapsedMinutes = getShiftElapsedMinutes({
37705
- shiftStart,
37706
- shiftEnd,
37707
- shiftDate,
37708
- timezone,
37709
- now: now4
37710
- });
37711
- if (shiftMinutes === null || elapsedMinutes === null || elapsedMinutes >= shiftMinutes) {
37712
- return normalizedData;
37713
- }
37714
- return normalizedData.map((value, index) => {
37715
- const slotStartMinutes = index * 60;
37716
- return slotStartMinutes > elapsedMinutes ? null : value;
37717
- });
37718
- };
37719
- var buildUptimeSeries = ({
37720
- idleTimeHourly,
37721
- shiftStart,
37722
- shiftEnd,
37723
- shiftDate,
37724
- timezone,
37725
- elapsedMinutes
37726
- }) => {
37727
- const normalizedIdle = normalizeIdleTimeHourly(idleTimeHourly || {});
37728
- const hasIdleData = Object.keys(normalizedIdle).length > 0;
37729
- if (!hasIdleData || !shiftDate || !timezone) {
37730
- return {
37731
- points: [],
37732
- activeMinutes: 0,
37733
- idleMinutes: 0,
37734
- availableMinutes: 0,
37735
- shiftMinutes: 0,
37736
- elapsedMinutes: 0,
37737
- hasData: false
37738
- };
37739
- }
37740
- const startTime = parseTime(shiftStart);
37741
- const endTime = parseTime(shiftEnd);
37742
- if (!startTime || !endTime) {
37743
- return {
37744
- points: [],
37745
- activeMinutes: 0,
37746
- idleMinutes: 0,
37747
- availableMinutes: 0,
37748
- shiftMinutes: 0,
37749
- elapsedMinutes: 0,
37750
- hasData: false
37751
- };
37752
- }
37753
- const shiftStartDate = fromZonedTime(
37754
- `${shiftDate}T${padTime(startTime.hour)}:${padTime(startTime.minute)}:00`,
37755
- timezone
37756
- );
37757
- let shiftEndDate = fromZonedTime(
37758
- `${shiftDate}T${padTime(endTime.hour)}:${padTime(endTime.minute)}:00`,
37759
- timezone
37760
- );
37761
- if (shiftEndDate <= shiftStartDate) {
37762
- shiftEndDate = addDays(shiftEndDate, 1);
37763
- }
37764
- const shiftMinutes = Math.max(differenceInMinutes(shiftEndDate, shiftStartDate), 0);
37765
- if (shiftMinutes <= 0) {
37766
- return {
37767
- points: [],
37768
- activeMinutes: 0,
37769
- idleMinutes: 0,
37770
- availableMinutes: 0,
37771
- shiftMinutes: 0,
37772
- elapsedMinutes: 0,
37773
- hasData: false
37774
- };
37775
- }
37776
- const elapsedMinutesClamped = Number.isFinite(elapsedMinutes) ? Math.min(Math.max(Math.floor(elapsedMinutes ?? 0), 0), shiftMinutes) : shiftMinutes;
37777
- const points = [];
37778
- let activeMinutes = 0;
37779
- let idleMinutes = 0;
37780
- for (let minuteIndex = 0; minuteIndex < shiftMinutes; minuteIndex += 1) {
37781
- const minuteDate = addMinutes(shiftStartDate, minuteIndex);
37782
- const timeLabel = formatInTimeZone(minuteDate, timezone, "h:mm a");
37783
- if (minuteIndex >= elapsedMinutesClamped) {
37784
- points.push({
37785
- minuteIndex,
37786
- timeLabel,
37787
- uptime: null,
37788
- status: "unknown"
37789
- });
37790
- continue;
37791
- }
37792
- const hourKey = formatInTimeZone(minuteDate, timezone, "H");
37793
- const minuteKey = Number.parseInt(formatInTimeZone(minuteDate, timezone, "m"), 10);
37794
- const hourBucket = normalizedIdle[hourKey] || [];
37795
- const value = Array.isArray(hourBucket) ? hourBucket[minuteKey] : void 0;
37796
- const status = interpretIdleValue(value);
37797
- if (status === "active") activeMinutes += 1;
37798
- if (status === "idle") idleMinutes += 1;
37799
- points.push({
37800
- minuteIndex,
37801
- timeLabel,
37802
- uptime: status === "active" ? 1 : status === "idle" ? 0 : null,
37803
- status
37804
- });
37805
- }
37806
- return {
37807
- points,
37808
- activeMinutes,
37809
- idleMinutes,
37810
- availableMinutes: activeMinutes + idleMinutes,
37811
- shiftMinutes,
37812
- elapsedMinutes: elapsedMinutesClamped,
37813
- hasData: activeMinutes + idleMinutes > 0
37814
- };
37815
- };
37816
- var getUptimeUtilizationPercent = (shift) => {
37817
- const efficiency = shift.efficiency;
37818
- if (Number.isFinite(efficiency)) {
37819
- return Math.round(Math.max(0, Math.min(100, Number(efficiency))));
37820
- }
37821
- const idleSeconds = Number.isFinite(shift.idleTime) ? Number(shift.idleTime) : 0;
37822
- const activeSeconds = Number.isFinite(shift.activeTimeSeconds) ? Number(shift.activeTimeSeconds) : null;
37823
- let availableSeconds = Number.isFinite(shift.availableTimeSeconds) ? Number(shift.availableTimeSeconds) : null;
37824
- if (availableSeconds === null) {
37825
- if ((activeSeconds ?? 0) > 0 || idleSeconds > 0) {
37826
- availableSeconds = (activeSeconds ?? 0) + idleSeconds;
37827
- } else {
37828
- return 0;
37829
- }
37830
- }
37831
- if (availableSeconds <= 0) return 0;
37832
- const clampedIdleSeconds = Math.min(Math.max(idleSeconds, 0), availableSeconds);
37833
- const productiveSeconds = Math.max(
37834
- activeSeconds ?? availableSeconds - clampedIdleSeconds,
37835
- 0
37836
- );
37837
- return Math.round(productiveSeconds / availableSeconds * 100);
37838
- };
37839
38527
  var getTimeFromTimeString = (timeStr) => {
37840
38528
  if (!timeStr) {
37841
38529
  return { hour: 0, minute: 0, decimalHour: 0 };
@@ -49901,261 +50589,6 @@ Underperforming Workspaces: ${lineInfo.metrics.underperforming_workspaces} / ${l
49901
50589
  }
49902
50590
  );
49903
50591
  };
49904
-
49905
- // src/lib/utils/hourlyTargets.ts
49906
- var stripSeconds2 = (timeStr) => timeStr ? timeStr.slice(0, 5) : timeStr;
49907
- var MINUTES_PER_DAY = 24 * 60;
49908
- var parseTimeToMinutes2 = (timeString) => {
49909
- const normalized = stripSeconds2(timeString || "");
49910
- if (!normalized || !/^[0-2]\d:[0-5]\d$/.test(normalized)) return Number.NaN;
49911
- const [hours, minutes] = normalized.split(":").map(Number);
49912
- return hours * 60 + minutes;
49913
- };
49914
- var normalizeBreaksOnShiftTimeline = (shiftStart, breaks) => {
49915
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
49916
- if (!Number.isFinite(shiftStartMinutes)) return [];
49917
- const normalizedBreaks = [];
49918
- for (const entry of breaks) {
49919
- const startRaw = parseTimeToMinutes2(entry.startTime);
49920
- const endRaw = parseTimeToMinutes2(entry.endTime);
49921
- if (!Number.isFinite(startRaw) || !Number.isFinite(endRaw)) continue;
49922
- let start = startRaw;
49923
- let end = endRaw;
49924
- if (end <= start) {
49925
- end += 24 * 60;
49926
- }
49927
- if (start < shiftStartMinutes) {
49928
- start += 24 * 60;
49929
- end += 24 * 60;
49930
- }
49931
- const label = entry.remarks?.trim() || "Break";
49932
- normalizedBreaks.push({ start, end, label });
49933
- }
49934
- return normalizedBreaks;
49935
- };
49936
- var roundTarget = (value, mode) => {
49937
- if (!Number.isFinite(value)) return 0;
49938
- switch (mode) {
49939
- case "floor":
49940
- return Math.floor(value);
49941
- case "ceil":
49942
- return Math.ceil(value);
49943
- case "round":
49944
- default:
49945
- return Math.round(value);
49946
- }
49947
- };
49948
- var formatDateKey = (date) => {
49949
- const year = date.getUTCFullYear();
49950
- const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
49951
- const day = `${date.getUTCDate()}`.padStart(2, "0");
49952
- return `${year}-${month}-${day}`;
49953
- };
49954
- var shiftDateKey = (dateKey, deltaDays) => {
49955
- const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
49956
- const year = Number.isFinite(yearPart) ? yearPart : 1970;
49957
- const month = Number.isFinite(monthPart) ? monthPart : 1;
49958
- const day = Number.isFinite(dayPart) ? dayPart : 1;
49959
- const date = new Date(Date.UTC(year, month - 1, day));
49960
- date.setUTCDate(date.getUTCDate() + deltaDays);
49961
- return formatDateKey(date);
49962
- };
49963
- var getZonedNowSnapshot = (timeZone, now4) => {
49964
- const formatter = new Intl.DateTimeFormat("en-US", {
49965
- timeZone,
49966
- year: "numeric",
49967
- month: "2-digit",
49968
- day: "2-digit",
49969
- hour: "2-digit",
49970
- minute: "2-digit",
49971
- hourCycle: "h23"
49972
- });
49973
- const parts = formatter.formatToParts(now4).reduce((acc, part) => {
49974
- if (part.type !== "literal") {
49975
- acc[part.type] = part.value;
49976
- }
49977
- return acc;
49978
- }, {});
49979
- const year = Number(parts.year);
49980
- const month = Number(parts.month);
49981
- const day = Number(parts.day);
49982
- const hour = Number(parts.hour);
49983
- const minute = Number(parts.minute);
49984
- return {
49985
- dateKey: `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`,
49986
- minutesOfDay: (Number.isFinite(hour) ? hour : 0) * 60 + (Number.isFinite(minute) ? minute : 0)
49987
- };
49988
- };
49989
- var getDateKeyInTimeZone = (timeZone, now4 = /* @__PURE__ */ new Date()) => getZonedNowSnapshot(timeZone, now4).dateKey;
49990
- var buildHourlyIntervals = ({
49991
- shiftStart,
49992
- shiftEnd,
49993
- bucketMinutes = 60,
49994
- fallbackHours = 11
49995
- }) => {
49996
- const startMinutes = parseTimeToMinutes2(shiftStart);
49997
- if (!Number.isFinite(startMinutes)) return [];
49998
- const bucket = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.floor(bucketMinutes) : 60;
49999
- let totalMinutes;
50000
- const endRaw = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
50001
- if (!Number.isFinite(endRaw)) {
50002
- totalMinutes = Math.max(0, Math.round(fallbackHours * 60));
50003
- } else {
50004
- let endMinutes = endRaw;
50005
- if (endMinutes <= startMinutes) {
50006
- endMinutes += 24 * 60;
50007
- }
50008
- totalMinutes = endMinutes - startMinutes;
50009
- }
50010
- if (!Number.isFinite(totalMinutes) || totalMinutes <= 0) return [];
50011
- const count = Math.ceil(totalMinutes / bucket);
50012
- const shiftEndMinutes = startMinutes + totalMinutes;
50013
- const intervals = [];
50014
- for (let i = 0; i < count; i += 1) {
50015
- const start = startMinutes + i * bucket;
50016
- const end = Math.min(start + bucket, shiftEndMinutes);
50017
- const minutes = Math.max(0, end - start);
50018
- if (minutes <= 0) continue;
50019
- intervals.push({ start, end, minutes });
50020
- }
50021
- return intervals;
50022
- };
50023
- var computeBreakMinutesByInterval = ({
50024
- intervals,
50025
- shiftStart,
50026
- breaks
50027
- }) => {
50028
- if (!intervals.length || !breaks.length) return intervals.map(() => 0);
50029
- const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
50030
- return intervals.map((interval) => {
50031
- if (!normalizedBreaks.length) return 0;
50032
- let total = 0;
50033
- for (const brk of normalizedBreaks) {
50034
- const overlap = Math.max(0, Math.min(interval.end, brk.end) - Math.max(interval.start, brk.start));
50035
- total += overlap;
50036
- if (total >= interval.minutes) return interval.minutes;
50037
- }
50038
- return Math.min(interval.minutes, total);
50039
- });
50040
- };
50041
- var computeBreakRemarksByInterval = ({
50042
- intervals,
50043
- shiftStart,
50044
- breaks
50045
- }) => {
50046
- if (!intervals.length || !breaks.length) return intervals.map(() => "");
50047
- const normalizedBreaks = normalizeBreaksOnShiftTimeline(shiftStart, breaks);
50048
- return intervals.map((interval) => {
50049
- 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);
50050
- return labels.join(", ");
50051
- });
50052
- };
50053
- var computeEffectiveTargets = ({
50054
- intervals,
50055
- breakMinutes,
50056
- pphThreshold,
50057
- rounding = "round"
50058
- }) => {
50059
- return intervals.map((interval, idx) => {
50060
- const intervalMinutes = Number(interval?.minutes) || 0;
50061
- const breakMins = Number(breakMinutes?.[idx]) || 0;
50062
- const plannedWorkMinutes = Math.max(0, intervalMinutes - breakMins);
50063
- if (!Number.isFinite(pphThreshold) || pphThreshold <= 0) return 0;
50064
- if (plannedWorkMinutes <= 0) return 0;
50065
- return roundTarget(pphThreshold * plannedWorkMinutes / 60, rounding);
50066
- });
50067
- };
50068
- var buildHourlyTargetPlan = ({
50069
- shiftStart,
50070
- shiftEnd,
50071
- breaks = [],
50072
- pphThreshold,
50073
- bucketMinutes = 60,
50074
- fallbackHours = 11,
50075
- rounding = "round"
50076
- }) => {
50077
- const intervals = buildHourlyIntervals({
50078
- shiftStart,
50079
- shiftEnd,
50080
- bucketMinutes,
50081
- fallbackHours
50082
- });
50083
- const breakMinutes = computeBreakMinutesByInterval({
50084
- intervals,
50085
- shiftStart,
50086
- breaks
50087
- });
50088
- const breakRemarks = computeBreakRemarksByInterval({
50089
- intervals,
50090
- shiftStart,
50091
- breaks
50092
- });
50093
- const productiveMinutes = intervals.map((interval, idx) => Math.max(0, (Number(interval?.minutes) || 0) - (Number(breakMinutes[idx]) || 0)));
50094
- const targets = computeEffectiveTargets({
50095
- intervals,
50096
- breakMinutes,
50097
- pphThreshold,
50098
- rounding
50099
- });
50100
- return {
50101
- intervals,
50102
- breakMinutes,
50103
- breakRemarks,
50104
- productiveMinutes,
50105
- targets
50106
- };
50107
- };
50108
- var isHourlyIntervalComplete = ({
50109
- reportDate,
50110
- shiftStart,
50111
- shiftEnd,
50112
- interval,
50113
- timeZone = "Asia/Kolkata",
50114
- now: now4 = /* @__PURE__ */ new Date()
50115
- }) => {
50116
- if (!reportDate) return true;
50117
- const snapshot = getZonedNowSnapshot(timeZone, now4);
50118
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50119
- const shiftEndMinutes = shiftEnd ? parseTimeToMinutes2(shiftEnd) : Number.NaN;
50120
- const wrapsMidnight = Number.isFinite(shiftStartMinutes) && Number.isFinite(shiftEndMinutes) && shiftEndMinutes <= shiftStartMinutes;
50121
- if (reportDate === snapshot.dateKey) {
50122
- return interval.end <= snapshot.minutesOfDay;
50123
- }
50124
- if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
50125
- return interval.end <= snapshot.minutesOfDay + MINUTES_PER_DAY;
50126
- }
50127
- return reportDate < snapshot.dateKey;
50128
- };
50129
- var isShiftInProgressForReportDate = ({
50130
- reportDate,
50131
- shiftStart,
50132
- shiftEnd,
50133
- timeZone = "Asia/Kolkata",
50134
- now: now4 = /* @__PURE__ */ new Date()
50135
- }) => {
50136
- if (!reportDate || !shiftStart || !shiftEnd) return false;
50137
- const shiftStartMinutes = parseTimeToMinutes2(shiftStart);
50138
- const shiftEndMinutesRaw = parseTimeToMinutes2(shiftEnd);
50139
- if (!Number.isFinite(shiftStartMinutes) || !Number.isFinite(shiftEndMinutesRaw)) {
50140
- return false;
50141
- }
50142
- let shiftEndMinutes = shiftEndMinutesRaw;
50143
- const wrapsMidnight = shiftEndMinutes <= shiftStartMinutes;
50144
- if (wrapsMidnight) {
50145
- shiftEndMinutes += MINUTES_PER_DAY;
50146
- }
50147
- const snapshot = getZonedNowSnapshot(timeZone, now4);
50148
- let currentMinutes = null;
50149
- if (reportDate === snapshot.dateKey) {
50150
- currentMinutes = snapshot.minutesOfDay;
50151
- } else if (wrapsMidnight && reportDate === shiftDateKey(snapshot.dateKey, -1)) {
50152
- currentMinutes = snapshot.minutesOfDay + MINUTES_PER_DAY;
50153
- }
50154
- if (currentMinutes === null) {
50155
- return false;
50156
- }
50157
- return shiftStartMinutes <= currentMinutes && currentMinutes < shiftEndMinutes;
50158
- };
50159
50592
  var formatOperationalDateKey = (dateKey, options) => {
50160
50593
  const [yearPart, monthPart, dayPart] = dateKey.split("-").map(Number);
50161
50594
  const year = Number.isFinite(yearPart) ? yearPart : 1970;
@@ -50166,7 +50599,7 @@ var formatOperationalDateKey = (dateKey, options) => {
50166
50599
  timeZone: "UTC"
50167
50600
  });
50168
50601
  };
50169
- var getSegmentMinutes = (isoString, timeZone, reportDate) => {
50602
+ var getSegmentMinutes2 = (isoString, timeZone, reportDate) => {
50170
50603
  const date = new Date(isoString);
50171
50604
  if (isNaN(date.getTime())) return NaN;
50172
50605
  const formatter = new Intl.DateTimeFormat("en-US", {
@@ -50187,13 +50620,13 @@ var getSegmentMinutes = (isoString, timeZone, reportDate) => {
50187
50620
  if (dateKey > reportDate) {
50188
50621
  const d1 = new Date(dateKey);
50189
50622
  const d2 = new Date(reportDate);
50190
- const diffDays = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
50191
- minutes += diffDays * 24 * 60;
50623
+ const diffDays2 = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
50624
+ minutes += diffDays2 * 24 * 60;
50192
50625
  } else if (dateKey < reportDate) {
50193
50626
  const d1 = new Date(dateKey);
50194
50627
  const d2 = new Date(reportDate);
50195
- const diffDays = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
50196
- minutes -= diffDays * 24 * 60;
50628
+ const diffDays2 = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
50629
+ minutes -= diffDays2 * 24 * 60;
50197
50630
  }
50198
50631
  return minutes;
50199
50632
  };
@@ -50603,7 +51036,7 @@ var LinePdfGenerator = ({
50603
51036
  const skuRemarksByIndex = {};
50604
51037
  if (lineInfo.metrics.sku_segments && lineInfo.metrics.sku_segments.length > 0) {
50605
51038
  lineInfo.metrics.sku_segments.forEach((segment, segmentIndex) => {
50606
- const segmentMinutes = getSegmentMinutes(segment.start_time, reportTimezone, lineInfo.date);
51039
+ const segmentMinutes = getSegmentMinutes2(segment.start_time, reportTimezone, lineInfo.date);
50607
51040
  if (!isNaN(segmentMinutes)) {
50608
51041
  const intervalIndex = hourlyTimeRanges.findIndex(
50609
51042
  (inv) => segmentMinutes >= inv.start && segmentMinutes < inv.end
@@ -52338,7 +52771,7 @@ var formatOperationalDateKey2 = (dateKey, options) => {
52338
52771
  timeZone: "UTC"
52339
52772
  });
52340
52773
  };
52341
- var getSegmentMinutes2 = (isoString, timeZone, reportDate) => {
52774
+ var getSegmentMinutes3 = (isoString, timeZone, reportDate) => {
52342
52775
  const date = new Date(isoString);
52343
52776
  if (isNaN(date.getTime())) return NaN;
52344
52777
  const formatter = new Intl.DateTimeFormat("en-US", {
@@ -52359,13 +52792,13 @@ var getSegmentMinutes2 = (isoString, timeZone, reportDate) => {
52359
52792
  if (dateKey > reportDate) {
52360
52793
  const d1 = new Date(dateKey);
52361
52794
  const d2 = new Date(reportDate);
52362
- const diffDays = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
52363
- minutes += diffDays * 24 * 60;
52795
+ const diffDays2 = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
52796
+ minutes += diffDays2 * 24 * 60;
52364
52797
  } else if (dateKey < reportDate) {
52365
52798
  const d1 = new Date(dateKey);
52366
52799
  const d2 = new Date(reportDate);
52367
- const diffDays = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
52368
- minutes -= diffDays * 24 * 60;
52800
+ const diffDays2 = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
52801
+ minutes -= diffDays2 * 24 * 60;
52369
52802
  }
52370
52803
  return minutes;
52371
52804
  };
@@ -52597,7 +53030,7 @@ var WorkspacePdfGenerator = ({
52597
53030
  const skuRemarksByIndex = {};
52598
53031
  if (workspace.sku_segments && workspace.sku_segments.length > 0) {
52599
53032
  workspace.sku_segments.forEach((segment, segmentIndex) => {
52600
- const segmentMinutes = getSegmentMinutes2(segment.start_time, reportTimezone, workspace.date);
53033
+ const segmentMinutes = getSegmentMinutes3(segment.start_time, reportTimezone, workspace.date);
52601
53034
  if (!isNaN(segmentMinutes)) {
52602
53035
  const intervalIndex = hourlyIntervals.findIndex(
52603
53036
  (inv) => segmentMinutes >= inv.start && segmentMinutes < inv.end
@@ -53439,12 +53872,10 @@ var formatPercentRange = (min, max) => {
53439
53872
  return `${format10(min)}-${format10(max)}%`;
53440
53873
  };
53441
53874
  var Legend5 = ({
53442
- useBottleneckLabel = false,
53443
53875
  legend,
53444
53876
  metricLabel = "Efficiency"
53445
53877
  }) => {
53446
53878
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
53447
- const exclamationLabel = useBottleneckLabel ? "Bottleneck" : "<50% efficiency";
53448
53879
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 sm:gap-4 text-xs font-medium text-slate-600", children: [
53449
53880
  /* @__PURE__ */ jsxs("div", { className: "font-medium text-gray-700 hidden sm:block", children: [
53450
53881
  metricLabel,
@@ -53463,11 +53894,6 @@ var Legend5 = ({
53463
53894
  /* @__PURE__ */ jsx("div", { className: "w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full bg-[#E34329]" }),
53464
53895
  /* @__PURE__ */ jsx("span", { children: formatPercentRange(effectiveLegend.red_min, effectiveLegend.red_max) })
53465
53896
  ] })
53466
- ] }),
53467
- /* @__PURE__ */ jsx("div", { className: "hidden sm:block w-px h-4 bg-slate-200 mx-1" }),
53468
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
53469
- /* @__PURE__ */ 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: "!" }),
53470
- /* @__PURE__ */ jsx("span", { children: exclamationLabel })
53471
53897
  ] })
53472
53898
  ] });
53473
53899
  };
@@ -53581,7 +54007,6 @@ var WorkspaceGrid = React144__default.memo(({
53581
54007
  factoryView = "factory",
53582
54008
  line2Uuid = "line-2",
53583
54009
  className = "",
53584
- hasFlowBuffers = false,
53585
54010
  legend = DEFAULT_EFFICIENCY_LEGEND,
53586
54011
  videoSources = {},
53587
54012
  videoStreamsByWorkspaceId = {},
@@ -53625,7 +54050,7 @@ var WorkspaceGrid = React144__default.memo(({
53625
54050
  );
53626
54051
  return /* @__PURE__ */ jsxs("div", { className: `flex flex-col w-full h-full overflow-hidden bg-slate-50/50 ${className}`, children: [
53627
54052
  /* @__PURE__ */ jsxs("div", { className: "flex-none px-4 py-3 z-20 flex flex-row items-center justify-between gap-4", children: [
53628
- /* @__PURE__ */ jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ 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__ */ jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) }) }),
54053
+ /* @__PURE__ */ jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ 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__ */ jsx(Legend5, { legend, metricLabel: legendMetricLabel }) }) }),
53629
54054
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3 shrink-0", children: [
53630
54055
  toolbarRightContent,
53631
54056
  mapViewEnabled && /* @__PURE__ */ jsx(
@@ -53645,7 +54070,7 @@ var WorkspaceGrid = React144__default.memo(({
53645
54070
  )
53646
54071
  ] })
53647
54072
  ] }),
53648
- /* @__PURE__ */ jsx("div", { className: "sm:hidden px-3 py-2 bg-white border-b border-slate-200/60 z-10", children: /* @__PURE__ */ jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) }),
54073
+ /* @__PURE__ */ jsx("div", { className: "sm:hidden px-3 py-2 bg-white border-b border-slate-200/60 z-10", children: /* @__PURE__ */ jsx(Legend5, { legend, metricLabel: legendMetricLabel }) }),
53649
54074
  /* @__PURE__ */ jsx("div", { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: viewMode === "video" ? /* @__PURE__ */ jsx(
53650
54075
  motion.div,
53651
54076
  {
@@ -64306,6 +64731,7 @@ var buildLineInfoSnapshot = (lineDetails, metrics2) => {
64306
64731
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
64307
64732
  output_array: metrics2.output_array || [],
64308
64733
  output_hourly: metrics2.output_hourly,
64734
+ hourly_target_output: metrics2.hourly_target_output,
64309
64735
  line_threshold: metrics2.line_threshold ?? 0,
64310
64736
  threshold_pph: metrics2.threshold_pph ?? 0,
64311
64737
  shift_start: metrics2.shift_start || "06:00",
@@ -64392,6 +64818,7 @@ var transformLineMetrics = (lineId, detailResponse, queryDate, queryShiftId) =>
64392
64818
  underperforming_workspace_names: [],
64393
64819
  underperforming_workspace_uuids: [],
64394
64820
  output_array: [],
64821
+ hourly_target_output: void 0,
64395
64822
  line_threshold: 0,
64396
64823
  threshold_pph: 0,
64397
64824
  shift_start: "06:00",
@@ -65429,6 +65856,10 @@ var BottomSection = memo$1(({
65429
65856
  workspaceDisplayNames,
65430
65857
  hourlyOutputData,
65431
65858
  hourlyThreshold,
65859
+ hourlyTargetOutput,
65860
+ shiftBreaks,
65861
+ idleTimeHourly,
65862
+ timezone,
65432
65863
  urlDate,
65433
65864
  urlShift,
65434
65865
  navigate,
@@ -65599,8 +66030,13 @@ var BottomSection = memo$1(({
65599
66030
  {
65600
66031
  data: hourlyOutputData,
65601
66032
  pphThreshold: hourlyThreshold,
66033
+ hourlyTargetOutput,
65602
66034
  shiftStart: lineInfo.metrics.shift_start || "06:00",
65603
66035
  shiftEnd: lineInfo.metrics.shift_end,
66036
+ shiftBreaks,
66037
+ idleTimeHourly,
66038
+ shiftDate: lineInfo.date,
66039
+ timezone,
65604
66040
  skuSegments: skuAware ? skuSegments : void 0,
65605
66041
  activeSkuId
65606
66042
  }
@@ -65621,6 +66057,12 @@ var BottomSection = memo$1(({
65621
66057
  if (prevProps.lineInfo.monitoring_mode !== nextProps.lineInfo.monitoring_mode) return false;
65622
66058
  if (prevProps.skuAware !== nextProps.skuAware) return false;
65623
66059
  if (prevProps.activeSkuId !== nextProps.activeSkuId) return false;
66060
+ if (JSON.stringify(prevProps.shiftBreaks || []) !== JSON.stringify(nextProps.shiftBreaks || [])) {
66061
+ return false;
66062
+ }
66063
+ if (JSON.stringify(prevProps.hourlyTargetOutput || []) !== JSON.stringify(nextProps.hourlyTargetOutput || [])) {
66064
+ return false;
66065
+ }
65624
66066
  const prevSkuSegmentsSignature = JSON.stringify(
65625
66067
  (prevProps.skuSegments || []).map((segment) => ({
65626
66068
  sku_id: segment.sku_id,
@@ -65693,7 +66135,9 @@ var BottomSection = memo$1(({
65693
66135
  );
65694
66136
  if (prevWorkspaceSignature !== nextWorkspaceSignature) return false;
65695
66137
  if (prevProps.lineInfo.metrics.shift_start !== nextProps.lineInfo.metrics.shift_start) return false;
66138
+ if (prevProps.lineInfo.metrics.shift_end !== nextProps.lineInfo.metrics.shift_end) return false;
65696
66139
  if (prevProps.hourlyThreshold !== nextProps.hourlyThreshold) return false;
66140
+ if (JSON.stringify(prevProps.idleTimeHourly || {}) !== JSON.stringify(nextProps.idleTimeHourly || {})) return false;
65697
66141
  if (prevProps.urlDate !== nextProps.urlDate || prevProps.urlShift !== nextProps.urlShift) return false;
65698
66142
  if (prevProps.workspaceDisplayNames !== nextProps.workspaceDisplayNames) return false;
65699
66143
  return true;
@@ -65961,6 +66405,7 @@ var KPIDetailView = ({
65961
66405
  }
65962
66406
  }, [urlDate, urlShift, urlTab]);
65963
66407
  const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineId);
66408
+ const lineTimezone = shiftConfig?.timezone || configuredTimezone;
65964
66409
  const getShiftName = useCallback((shiftId) => {
65965
66410
  return getShiftNameById(shiftId, configuredTimezone, shiftConfig);
65966
66411
  }, [configuredTimezone, shiftConfig]);
@@ -66001,12 +66446,12 @@ var KPIDetailView = ({
66001
66446
  operationalTodayDate.setHours(0, 0, 0, 0);
66002
66447
  compareDateInZone.setHours(0, 0, 0, 0);
66003
66448
  const diffTime = compareDateInZone.getTime() - operationalTodayDate.getTime();
66004
- const diffDays = Math.round(diffTime / (1e3 * 60 * 60 * 24));
66005
- if (diffDays === 0) return "Today";
66006
- if (diffDays === -1) return "Yesterday";
66007
- if (diffDays === 1) return "Tomorrow";
66008
- if (diffDays < -1) return `${Math.abs(diffDays)} days ago`;
66009
- if (diffDays > 1) return `${diffDays} days ahead`;
66449
+ const diffDays2 = Math.round(diffTime / (1e3 * 60 * 60 * 24));
66450
+ if (diffDays2 === 0) return "Today";
66451
+ if (diffDays2 === -1) return "Yesterday";
66452
+ if (diffDays2 === 1) return "Tomorrow";
66453
+ if (diffDays2 < -1) return `${Math.abs(diffDays2)} days ago`;
66454
+ if (diffDays2 > 1) return `${diffDays2} days ahead`;
66010
66455
  return "Today";
66011
66456
  }, [configuredTimezone, shiftConfig]);
66012
66457
  const {
@@ -66216,6 +66661,7 @@ var KPIDetailView = ({
66216
66661
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
66217
66662
  output_array: metrics2.output_array || [],
66218
66663
  output_hourly: metrics2.output_hourly,
66664
+ hourly_target_output: metrics2.hourly_target_output,
66219
66665
  line_threshold: metrics2.line_threshold ?? 0,
66220
66666
  threshold_pph: metrics2.threshold_pph ?? 0,
66221
66667
  shift_start: metrics2.shift_start || "06:00",
@@ -66225,7 +66671,7 @@ var KPIDetailView = ({
66225
66671
  idle_time_hourly: metrics2.idle_time_hourly || null,
66226
66672
  // Multi-SKU additive fields (Phase 6) — propagated from
66227
66673
  // `useLineDetailPageData`. Backend authoritative; we never recompute.
66228
- // The selector below uses these to swap header KPIs per selected SKU.
66674
+ // The output-card selection below uses these to swap header KPIs per selected SKU.
66229
66675
  sku_aware: Boolean(metrics2.sku_aware),
66230
66676
  real_sku_count: metrics2.real_sku_count ?? 0,
66231
66677
  sku_breakdown: Array.isArray(metrics2.sku_breakdown) ? metrics2.sku_breakdown : [],
@@ -66322,7 +66768,7 @@ var KPIDetailView = ({
66322
66768
  }, [lineSkuSegments, outputChartSkuBreakdown, hasUrlDate, hasUrlShift]);
66323
66769
  const normalizedSelectedSkuId = selectedSkuId !== "all" ? selectedSkuId : null;
66324
66770
  const isLineSkuAware = Boolean(resolvedLineInfo?.metrics.sku_aware);
66325
- const showSkuSelector = isLineSkuAware && realSkuOptions.length > 0;
66771
+ const showSkuSelector = isLineSkuAware && realSkuOptions.length > 0 && !isUptimeMode;
66326
66772
  useEffect(() => {
66327
66773
  if (selectedSkuId === "all") return;
66328
66774
  const stillPresent = realSkuOptions.some((item) => item.sku_id === selectedSkuId);
@@ -66349,15 +66795,11 @@ var KPIDetailView = ({
66349
66795
  ...resolvedLineInfo.metrics,
66350
66796
  current_output: selectedSkuRow.current_output ?? 0,
66351
66797
  ideal_output: selectedSkuRow.ideal_output ?? 0,
66352
- avg_efficiency: selectedSkuRow.avg_efficiency ?? resolvedLineInfo.metrics.avg_efficiency,
66353
66798
  total_workspaces: selectedSkuRow.total_workspaces ?? resolvedLineInfo.metrics.total_workspaces,
66354
66799
  underperforming_workspaces: selectedSkuRow.underperforming_workspaces ?? resolvedLineInfo.metrics.underperforming_workspaces,
66355
66800
  underperforming_workspace_names: selectedSkuRow.underperforming_workspace_names ?? resolvedLineInfo.metrics.underperforming_workspace_names,
66356
66801
  underperforming_workspace_uuids: selectedSkuRow.underperforming_workspace_uuids ?? resolvedLineInfo.metrics.underperforming_workspace_uuids,
66357
- output_array: selectedSkuRow.output_array ?? resolvedLineInfo.metrics.output_array,
66358
- output_hourly: selectedSkuRow.output_hourly ?? resolvedLineInfo.metrics.output_hourly,
66359
- line_threshold: selectedSkuRow.line_threshold ?? resolvedLineInfo.metrics.line_threshold,
66360
- poorest_performing_workspaces: selectedSkuRow.poorest_performing_workspaces ?? resolvedLineInfo.metrics.poorest_performing_workspaces
66802
+ line_threshold: selectedSkuRow.line_threshold ?? resolvedLineInfo.metrics.line_threshold
66361
66803
  }
66362
66804
  };
66363
66805
  }, [resolvedLineInfo, selectedSkuRow]);
@@ -67136,11 +67578,9 @@ var KPIDetailView = ({
67136
67578
  showIdleTime: idleTimeVlmEnabled
67137
67579
  }
67138
67580
  ) : (
67139
- // Phase 6: pass `displayLineInfo` so the SKU selector
67140
- // swaps `current_output`, `ideal_output`,
67141
- // `avg_efficiency`, `line_threshold` on the header KPI
67142
- // cards. When `selectedSkuId === 'all'`, this is exactly
67143
- // `resolvedLineInfo` (aggregate path preserved).
67581
+ // Keep the line output + underperforming cards SKU-aware,
67582
+ // while Average Efficiency stays on the aggregate line
67583
+ // metrics even when a specific SKU is selected.
67144
67584
  /* @__PURE__ */ jsx(
67145
67585
  MetricCards,
67146
67586
  {
@@ -67185,6 +67625,10 @@ var KPIDetailView = ({
67185
67625
  workspaceDisplayNames,
67186
67626
  hourlyOutputData,
67187
67627
  hourlyThreshold,
67628
+ hourlyTargetOutput: chartMetrics?.hourly_target_output,
67629
+ shiftBreaks: shiftConfig?.shifts?.find((shift) => shift.shiftId === resolvedLineInfo.shift_id)?.breaks || [],
67630
+ idleTimeHourly: chartMetrics?.idle_time_hourly,
67631
+ timezone: lineTimezone,
67188
67632
  urlDate,
67189
67633
  urlShift,
67190
67634
  navigate,
@@ -67250,8 +67694,8 @@ var KPIDetailView = ({
67250
67694
  showIdleTime: idleTimeVlmEnabled
67251
67695
  }
67252
67696
  ) : (
67253
- // Phase 6: pass `displayLineInfo` so the SKU selector
67254
- // swaps the four SKU-specific fields on the header.
67697
+ // Keep the line output + underperforming cards SKU-aware,
67698
+ // while Average Efficiency stays aggregate.
67255
67699
  /* @__PURE__ */ jsx(
67256
67700
  MetricCards,
67257
67701
  {
@@ -67296,6 +67740,8 @@ var KPIDetailView = ({
67296
67740
  workspaceDisplayNames,
67297
67741
  hourlyOutputData,
67298
67742
  hourlyThreshold,
67743
+ idleTimeHourly: chartMetrics?.idle_time_hourly,
67744
+ timezone: lineTimezone,
67299
67745
  urlDate,
67300
67746
  urlShift,
67301
67747
  navigate,
@@ -73532,12 +73978,12 @@ var getDaysDifference = (date, timezone = "UTC", shiftStartTime = "06:00") => {
73532
73978
  operationalTodayDate.setHours(0, 0, 0, 0);
73533
73979
  compareDateInTz.setHours(0, 0, 0, 0);
73534
73980
  const diffTime = compareDateInTz.getTime() - operationalTodayDate.getTime();
73535
- const diffDays = Math.round(diffTime / (1e3 * 60 * 60 * 24));
73536
- if (diffDays === 0) return "Today";
73537
- if (diffDays === -1) return "Yesterday";
73538
- if (diffDays === 1) return "Tomorrow";
73539
- if (diffDays < -1) return `${Math.abs(diffDays)} days ago`;
73540
- if (diffDays > 1) return `${diffDays} days ahead`;
73981
+ const diffDays2 = Math.round(diffTime / (1e3 * 60 * 60 * 24));
73982
+ if (diffDays2 === 0) return "Today";
73983
+ if (diffDays2 === -1) return "Yesterday";
73984
+ if (diffDays2 === 1) return "Tomorrow";
73985
+ if (diffDays2 < -1) return `${Math.abs(diffDays2)} days ago`;
73986
+ if (diffDays2 > 1) return `${diffDays2} days ahead`;
73541
73987
  return "Today";
73542
73988
  };
73543
73989
  var getInitialTab = (sourceType, defaultTab, fromMonthly, urlDate) => {
@@ -73973,7 +74419,7 @@ var WorkspaceDetailView = ({
73973
74419
  () => resolveLiveSkuId(workspace?.sku_segments),
73974
74420
  [workspace?.sku_segments]
73975
74421
  );
73976
- const activeSkuId = selectedSkuId || liveSkuId;
74422
+ const activeSkuId = selectedSkuId;
73977
74423
  const resolvedLineId = effectiveLineId || workspace?.line_id || cachedDetailedMetrics?.line_id || cachedOverviewMetrics?.line_id || overviewFallback?.line_id;
73978
74424
  const { timezone: cycleTimeTimezone } = useTimezone({
73979
74425
  lineId: resolvedLineId || void 0,
@@ -74941,14 +75387,16 @@ var WorkspaceDetailView = ({
74941
75387
  {
74942
75388
  data: workspace.hourly_action_counts || [],
74943
75389
  pphThreshold: workspace.pph_threshold || 0,
75390
+ hourlyTargetOutput: workspace.hourly_target_output,
74944
75391
  shiftStart: workspace.shift_start || "06:00",
74945
75392
  shiftEnd: workspace.shift_end,
75393
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
74946
75394
  showIdleTime: showChartIdleTime,
74947
75395
  idleTimeHourly: workspace.idle_time_hourly,
74948
75396
  idleTimeClips,
74949
75397
  idleTimeClipClassifications,
74950
75398
  shiftDate: idleClipDate,
74951
- timezone,
75399
+ timezone: effectiveCycleTimeTimezone,
74952
75400
  skuSegments: isSkuAware ? skuSegments : void 0,
74953
75401
  activeSkuId
74954
75402
  }
@@ -75087,14 +75535,16 @@ var WorkspaceDetailView = ({
75087
75535
  {
75088
75536
  data: workspace.hourly_action_counts || [],
75089
75537
  pphThreshold: workspace.pph_threshold || 0,
75538
+ hourlyTargetOutput: workspace.hourly_target_output,
75090
75539
  shiftStart: workspace.shift_start || "06:00",
75091
75540
  shiftEnd: workspace.shift_end,
75541
+ shiftBreaks: shiftConfig?.shifts?.find((shift2) => shift2.shiftId === workspace.shift_id)?.breaks || [],
75092
75542
  showIdleTime: showChartIdleTime,
75093
75543
  idleTimeHourly: workspace.idle_time_hourly,
75094
75544
  idleTimeClips,
75095
75545
  idleTimeClipClassifications,
75096
75546
  shiftDate: idleClipDate,
75097
- timezone,
75547
+ timezone: effectiveCycleTimeTimezone,
75098
75548
  skuSegments: isSkuAware ? skuSegments : void 0,
75099
75549
  activeSkuId
75100
75550
  }
@@ -78371,8 +78821,8 @@ var ImprovementCenterView = () => {
78371
78821
  const firstSeen = new Date(new Date(openedAt).toLocaleString("en-US", { timeZone: timezone }));
78372
78822
  if (Number.isNaN(firstSeen.getTime())) return void 0;
78373
78823
  const now4 = new Date((/* @__PURE__ */ new Date()).toLocaleString("en-US", { timeZone: timezone }));
78374
- const diffDays = Math.max(0, Math.floor((now4.getTime() - firstSeen.getTime()) / (1e3 * 60 * 60 * 24)));
78375
- return Math.max(1, Math.ceil(diffDays / 7));
78824
+ const diffDays2 = Math.max(0, Math.floor((now4.getTime() - firstSeen.getTime()) / (1e3 * 60 * 60 * 24)));
78825
+ return Math.max(1, Math.ceil(diffDays2 / 7));
78376
78826
  };
78377
78827
  const toZonedDate = (value) => {
78378
78828
  if (!value) return void 0;
@@ -79053,12 +79503,12 @@ var ThreadSidebar = ({
79053
79503
  const date = new Date(dateString);
79054
79504
  const now4 = /* @__PURE__ */ new Date();
79055
79505
  const diffMs = now4.getTime() - date.getTime();
79056
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
79057
- if (diffDays === 0) {
79506
+ const diffDays2 = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
79507
+ if (diffDays2 === 0) {
79058
79508
  return "Today";
79059
- } else if (diffDays === 1) {
79509
+ } else if (diffDays2 === 1) {
79060
79510
  return "Yesterday";
79061
- } else if (diffDays < 7) {
79511
+ } else if (diffDays2 < 7) {
79062
79512
  return date.toLocaleDateString("en-US", { weekday: "short" });
79063
79513
  } else {
79064
79514
  return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });