@optifye/dashboard-core 6.12.0 → 6.12.1

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,7 @@ 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 hourlyTargetOutput = Array.isArray(data.hourly_target_output) ? data.hourly_target_output.map((value) => value === null || value === void 0 ? null : coerceNumber(value, 0)) : null;
12986
13008
  const hourlyCycleTimes = Array.isArray(data.hourly_cycle_times) ? data.hourly_cycle_times.map((value) => coerceNumber(value, 0)) : [];
12987
13009
  const cycleCompletionClipCount = data.cycle_completion_clip_count === null || data.cycle_completion_clip_count === void 0 ? null : coerceNumber(data.cycle_completion_clip_count, 0);
12988
13010
  const cycleTimeDataStatus = data.cycle_time_data_status === "missing_clips" ? "missing_clips" : data.cycle_time_data_status === "available" ? "available" : null;
@@ -13034,6 +13056,7 @@ var toWorkspaceDetailedMetrics = ({
13034
13056
  avg_efficiency: coerceNumber(data.efficiency ?? data.avg_efficiency, 0),
13035
13057
  total_actions: totalActions,
13036
13058
  hourly_action_counts: hourlyActionCounts,
13059
+ hourly_target_output: hourlyTargetOutput,
13037
13060
  hourly_cycle_times: hourlyCycleTimes,
13038
13061
  cycle_completion_clip_count: cycleCompletionClipCount,
13039
13062
  cycle_time_data_status: cycleTimeDataStatus,
@@ -19650,7 +19673,7 @@ function formatRelativeTime(timestamp) {
19650
19673
  const diffSeconds = Math.floor(diffMs / 1e3);
19651
19674
  const diffMinutes = Math.floor(diffSeconds / 60);
19652
19675
  const diffHours = Math.floor(diffMinutes / 60);
19653
- const diffDays = Math.floor(diffHours / 24);
19676
+ const diffDays2 = Math.floor(diffHours / 24);
19654
19677
  if (diffSeconds < 60) {
19655
19678
  return "Less than a minute ago";
19656
19679
  }
@@ -19662,8 +19685,8 @@ function formatRelativeTime(timestamp) {
19662
19685
  const hourLabel = diffHours === 1 ? "hour" : "hours";
19663
19686
  return `${diffHours} ${hourLabel} ago`;
19664
19687
  }
19665
- const dayLabel = diffDays === 1 ? "day" : "days";
19666
- return `${diffDays} ${dayLabel} ago`;
19688
+ const dayLabel = diffDays2 === 1 ? "day" : "days";
19689
+ return `${diffDays2} ${dayLabel} ago`;
19667
19690
  } catch (error) {
19668
19691
  console.error("[formatRelativeTime] Error formatting timestamp:", error);
19669
19692
  return "Unknown";
@@ -35162,6 +35185,223 @@ var Button = React144.forwardRef(
35162
35185
  }
35163
35186
  );
35164
35187
  Button.displayName = "Button";
35188
+ var padTime = (value) => value.toString().padStart(2, "0");
35189
+ var parseTime = (timeValue) => {
35190
+ if (!timeValue) return null;
35191
+ const [hourPart, minutePart] = timeValue.split(":");
35192
+ const hour = Number.parseInt(hourPart, 10);
35193
+ const minute = Number.parseInt(minutePart ?? "0", 10);
35194
+ if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null;
35195
+ return { hour, minute };
35196
+ };
35197
+ var normalizeIdleTimeHourly = (idleTimeHourly) => {
35198
+ if (!idleTimeHourly || typeof idleTimeHourly !== "object") {
35199
+ return {};
35200
+ }
35201
+ return Object.fromEntries(
35202
+ Object.entries(idleTimeHourly).map(([key, value]) => {
35203
+ if (Array.isArray(value)) return [key, value];
35204
+ if (value && Array.isArray(value.values)) {
35205
+ return [key, value.values];
35206
+ }
35207
+ return [key, []];
35208
+ })
35209
+ );
35210
+ };
35211
+ var interpretIdleValue = (value) => {
35212
+ if (value === 1 || value === "1") return "idle";
35213
+ if (value === 0 || value === "0") return "active";
35214
+ if (value === "x" || value === null || value === void 0) return "unknown";
35215
+ return "unknown";
35216
+ };
35217
+ var getShiftDurationMinutes = (shiftStart, shiftEnd) => {
35218
+ const start = parseTime(shiftStart);
35219
+ const end = parseTime(shiftEnd);
35220
+ if (!start || !end) return null;
35221
+ let duration = end.hour * 60 + end.minute - (start.hour * 60 + start.minute);
35222
+ if (duration <= 0) {
35223
+ duration += 24 * 60;
35224
+ }
35225
+ return duration > 0 ? duration : null;
35226
+ };
35227
+ var getShiftElapsedMinutes = ({
35228
+ shiftStart,
35229
+ shiftEnd,
35230
+ shiftDate,
35231
+ timezone,
35232
+ now: now4 = /* @__PURE__ */ new Date()
35233
+ }) => {
35234
+ if (!shiftDate || !timezone) return null;
35235
+ const startTime = parseTime(shiftStart);
35236
+ const endTime = parseTime(shiftEnd);
35237
+ if (!startTime || !endTime) return null;
35238
+ const shiftStartDate = fromZonedTime(
35239
+ `${shiftDate}T${padTime(startTime.hour)}:${padTime(startTime.minute)}:00`,
35240
+ timezone
35241
+ );
35242
+ let shiftEndDate = fromZonedTime(
35243
+ `${shiftDate}T${padTime(endTime.hour)}:${padTime(endTime.minute)}:00`,
35244
+ timezone
35245
+ );
35246
+ if (shiftEndDate <= shiftStartDate) {
35247
+ shiftEndDate = addDays(shiftEndDate, 1);
35248
+ }
35249
+ const shiftMinutes = Math.max(differenceInMinutes(shiftEndDate, shiftStartDate), 0);
35250
+ if (shiftMinutes <= 0) return null;
35251
+ const elapsed = differenceInMinutes(now4, shiftStartDate);
35252
+ return Math.min(Math.max(elapsed, 0), shiftMinutes);
35253
+ };
35254
+ var maskFutureHourlySeries = ({
35255
+ data,
35256
+ shiftStart,
35257
+ shiftEnd,
35258
+ shiftDate,
35259
+ timezone,
35260
+ now: now4 = /* @__PURE__ */ new Date()
35261
+ }) => {
35262
+ if (!Array.isArray(data)) {
35263
+ return [];
35264
+ }
35265
+ const normalizedData = data.map((value) => typeof value === "number" && Number.isFinite(value) ? value : null);
35266
+ if (!normalizedData.length) {
35267
+ return normalizedData;
35268
+ }
35269
+ const shiftMinutes = getShiftDurationMinutes(shiftStart, shiftEnd);
35270
+ const elapsedMinutes = getShiftElapsedMinutes({
35271
+ shiftStart,
35272
+ shiftEnd,
35273
+ shiftDate,
35274
+ timezone,
35275
+ now: now4
35276
+ });
35277
+ if (shiftMinutes === null || elapsedMinutes === null || elapsedMinutes >= shiftMinutes) {
35278
+ return normalizedData;
35279
+ }
35280
+ return normalizedData.map((value, index) => {
35281
+ const slotStartMinutes = index * 60;
35282
+ return slotStartMinutes > elapsedMinutes ? null : value;
35283
+ });
35284
+ };
35285
+ var buildUptimeSeries = ({
35286
+ idleTimeHourly,
35287
+ shiftStart,
35288
+ shiftEnd,
35289
+ shiftDate,
35290
+ timezone,
35291
+ elapsedMinutes
35292
+ }) => {
35293
+ const normalizedIdle = normalizeIdleTimeHourly(idleTimeHourly || {});
35294
+ const hasIdleData = Object.keys(normalizedIdle).length > 0;
35295
+ if (!hasIdleData || !shiftDate || !timezone) {
35296
+ return {
35297
+ points: [],
35298
+ activeMinutes: 0,
35299
+ idleMinutes: 0,
35300
+ availableMinutes: 0,
35301
+ shiftMinutes: 0,
35302
+ elapsedMinutes: 0,
35303
+ hasData: false
35304
+ };
35305
+ }
35306
+ const startTime = parseTime(shiftStart);
35307
+ const endTime = parseTime(shiftEnd);
35308
+ if (!startTime || !endTime) {
35309
+ return {
35310
+ points: [],
35311
+ activeMinutes: 0,
35312
+ idleMinutes: 0,
35313
+ availableMinutes: 0,
35314
+ shiftMinutes: 0,
35315
+ elapsedMinutes: 0,
35316
+ hasData: false
35317
+ };
35318
+ }
35319
+ const shiftStartDate = fromZonedTime(
35320
+ `${shiftDate}T${padTime(startTime.hour)}:${padTime(startTime.minute)}:00`,
35321
+ timezone
35322
+ );
35323
+ let shiftEndDate = fromZonedTime(
35324
+ `${shiftDate}T${padTime(endTime.hour)}:${padTime(endTime.minute)}:00`,
35325
+ timezone
35326
+ );
35327
+ if (shiftEndDate <= shiftStartDate) {
35328
+ shiftEndDate = addDays(shiftEndDate, 1);
35329
+ }
35330
+ const shiftMinutes = Math.max(differenceInMinutes(shiftEndDate, shiftStartDate), 0);
35331
+ if (shiftMinutes <= 0) {
35332
+ return {
35333
+ points: [],
35334
+ activeMinutes: 0,
35335
+ idleMinutes: 0,
35336
+ availableMinutes: 0,
35337
+ shiftMinutes: 0,
35338
+ elapsedMinutes: 0,
35339
+ hasData: false
35340
+ };
35341
+ }
35342
+ const elapsedMinutesClamped = Number.isFinite(elapsedMinutes) ? Math.min(Math.max(Math.floor(elapsedMinutes ?? 0), 0), shiftMinutes) : shiftMinutes;
35343
+ const points = [];
35344
+ let activeMinutes = 0;
35345
+ let idleMinutes = 0;
35346
+ for (let minuteIndex = 0; minuteIndex < shiftMinutes; minuteIndex += 1) {
35347
+ const minuteDate = addMinutes(shiftStartDate, minuteIndex);
35348
+ const timeLabel = formatInTimeZone(minuteDate, timezone, "h:mm a");
35349
+ if (minuteIndex >= elapsedMinutesClamped) {
35350
+ points.push({
35351
+ minuteIndex,
35352
+ timeLabel,
35353
+ uptime: null,
35354
+ status: "unknown"
35355
+ });
35356
+ continue;
35357
+ }
35358
+ const hourKey = formatInTimeZone(minuteDate, timezone, "H");
35359
+ const minuteKey = Number.parseInt(formatInTimeZone(minuteDate, timezone, "m"), 10);
35360
+ const hourBucket = normalizedIdle[hourKey] || [];
35361
+ const value = Array.isArray(hourBucket) ? hourBucket[minuteKey] : void 0;
35362
+ const status = interpretIdleValue(value);
35363
+ if (status === "active") activeMinutes += 1;
35364
+ if (status === "idle") idleMinutes += 1;
35365
+ points.push({
35366
+ minuteIndex,
35367
+ timeLabel,
35368
+ uptime: status === "active" ? 1 : status === "idle" ? 0 : null,
35369
+ status
35370
+ });
35371
+ }
35372
+ return {
35373
+ points,
35374
+ activeMinutes,
35375
+ idleMinutes,
35376
+ availableMinutes: activeMinutes + idleMinutes,
35377
+ shiftMinutes,
35378
+ elapsedMinutes: elapsedMinutesClamped,
35379
+ hasData: activeMinutes + idleMinutes > 0
35380
+ };
35381
+ };
35382
+ var getUptimeUtilizationPercent = (shift) => {
35383
+ const efficiency = shift.efficiency;
35384
+ if (Number.isFinite(efficiency)) {
35385
+ return Math.round(Math.max(0, Math.min(100, Number(efficiency))));
35386
+ }
35387
+ const idleSeconds = Number.isFinite(shift.idleTime) ? Number(shift.idleTime) : 0;
35388
+ const activeSeconds = Number.isFinite(shift.activeTimeSeconds) ? Number(shift.activeTimeSeconds) : null;
35389
+ let availableSeconds = Number.isFinite(shift.availableTimeSeconds) ? Number(shift.availableTimeSeconds) : null;
35390
+ if (availableSeconds === null) {
35391
+ if ((activeSeconds ?? 0) > 0 || idleSeconds > 0) {
35392
+ availableSeconds = (activeSeconds ?? 0) + idleSeconds;
35393
+ } else {
35394
+ return 0;
35395
+ }
35396
+ }
35397
+ if (availableSeconds <= 0) return 0;
35398
+ const clampedIdleSeconds = Math.min(Math.max(idleSeconds, 0), availableSeconds);
35399
+ const productiveSeconds = Math.max(
35400
+ activeSeconds ?? availableSeconds - clampedIdleSeconds,
35401
+ 0
35402
+ );
35403
+ return Math.round(productiveSeconds / availableSeconds * 100);
35404
+ };
35165
35405
 
35166
35406
  // src/components/charts/skuDividerUtils.ts
35167
35407
  var HOURLY_TIME_RE = /^(\d{1,2}):(\d{2})/;
@@ -35174,37 +35414,162 @@ var parseTimeOfDay = (timeValue) => {
35174
35414
  if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
35175
35415
  return { hour, minute };
35176
35416
  };
35177
- var computeSegmentOffset = (segment, shift) => {
35178
- if (!segment?.start_time) return null;
35417
+ var parseDateKey = (value) => {
35418
+ const [yearPart, monthPart, dayPart] = value.split("-").map(Number);
35419
+ if (!Number.isFinite(yearPart) || !Number.isFinite(monthPart) || !Number.isFinite(dayPart)) {
35420
+ return null;
35421
+ }
35422
+ return Date.UTC(yearPart, monthPart - 1, dayPart);
35423
+ };
35424
+ var diffDays = (left, right) => {
35425
+ const leftUtc = parseDateKey(left);
35426
+ const rightUtc = parseDateKey(right);
35427
+ if (leftUtc === null || rightUtc === null) return null;
35428
+ return Math.round((leftUtc - rightUtc) / (24 * 60 * 60 * 1e3));
35429
+ };
35430
+ var getSegmentMinutes = (isoString, timeZone, reportDate) => {
35431
+ const date = new Date(isoString);
35432
+ if (Number.isNaN(date.getTime())) return Number.NaN;
35433
+ const formatter = new Intl.DateTimeFormat("en-US", {
35434
+ timeZone,
35435
+ year: "numeric",
35436
+ month: "2-digit",
35437
+ day: "2-digit",
35438
+ hour: "2-digit",
35439
+ minute: "2-digit",
35440
+ hourCycle: "h23"
35441
+ });
35442
+ const parts = formatter.formatToParts(date).reduce((acc, part) => {
35443
+ if (part.type !== "literal") {
35444
+ acc[part.type] = part.value;
35445
+ }
35446
+ return acc;
35447
+ }, {});
35448
+ const dateKey = `${parts.year}-${parts.month}-${parts.day}`;
35449
+ let minutes = Number(parts.hour) * 60 + Number(parts.minute);
35450
+ const dayDelta = diffDays(dateKey, reportDate);
35451
+ if (dayDelta === null) return Number.NaN;
35452
+ minutes += dayDelta * 24 * 60;
35453
+ return minutes;
35454
+ };
35455
+ var computeSegmentOffsetUtc = (segment, shift) => {
35179
35456
  const parsedMs = Date.parse(segment.start_time);
35180
35457
  if (Number.isNaN(parsedMs)) return null;
35181
35458
  const date = new Date(parsedMs);
35182
35459
  const segHour = date.getUTCHours();
35183
35460
  const segMinute = date.getUTCMinutes();
35184
35461
  const segMinutes = segHour * 60 + segMinute;
35185
- let shiftStartMinutes = shift.startHour * 60 + shift.startMinute;
35462
+ const shiftStartMinutes = shift.startHour * 60 + shift.startMinute;
35186
35463
  let deltaMinutes = segMinutes - shiftStartMinutes;
35187
35464
  if (deltaMinutes < 0) deltaMinutes += 24 * 60;
35188
35465
  const offsetHours = deltaMinutes / 60;
35189
35466
  if (offsetHours < 0 || offsetHours > shift.slotCount) return null;
35190
35467
  return offsetHours;
35191
35468
  };
35469
+ var computeSegmentOffset = (segment, shift) => {
35470
+ if (!segment?.start_time) return null;
35471
+ if (shift.shiftDate && shift.timezone) {
35472
+ const segmentMinutes = getSegmentMinutes(
35473
+ segment.start_time,
35474
+ shift.timezone,
35475
+ shift.shiftDate
35476
+ );
35477
+ if (!Number.isFinite(segmentMinutes)) return null;
35478
+ const shiftStartMinutes = shift.startHour * 60 + shift.startMinute;
35479
+ const deltaMinutes = segmentMinutes - shiftStartMinutes;
35480
+ const offsetHours = deltaMinutes / 60;
35481
+ if (offsetHours < 0 || offsetHours > shift.slotCount) return null;
35482
+ return offsetHours;
35483
+ }
35484
+ return computeSegmentOffsetUtc(segment, shift);
35485
+ };
35192
35486
  var resolveShiftWindow2 = (params) => {
35193
35487
  const startTime = parseTimeOfDay(params.shiftStart);
35194
35488
  if (!startTime) return null;
35195
35489
  return {
35196
35490
  startHour: startTime.hour,
35197
35491
  startMinute: startTime.minute,
35198
- slotCount: params.slotCount
35492
+ slotCount: params.slotCount,
35493
+ ...params.shiftDate ? { shiftDate: params.shiftDate } : {},
35494
+ ...params.timezone ? { timezone: params.timezone } : {}
35199
35495
  };
35200
35496
  };
35497
+ var resolveTimelineEndOffset = ({
35498
+ shiftStart,
35499
+ shiftEnd,
35500
+ slotCount,
35501
+ shiftDate,
35502
+ timezone,
35503
+ now: now4
35504
+ }) => {
35505
+ const normalizedSlotCount = Number.isFinite(slotCount) && slotCount > 0 ? slotCount : 0;
35506
+ if (!shiftDate || !timezone) {
35507
+ return normalizedSlotCount;
35508
+ }
35509
+ const shiftMinutes = getShiftDurationMinutes(shiftStart, shiftEnd);
35510
+ const elapsedMinutes = getShiftElapsedMinutes({
35511
+ shiftStart,
35512
+ shiftEnd,
35513
+ shiftDate,
35514
+ timezone,
35515
+ now: now4
35516
+ });
35517
+ if (shiftMinutes === null || elapsedMinutes === null) {
35518
+ return normalizedSlotCount;
35519
+ }
35520
+ if (elapsedMinutes >= shiftMinutes) {
35521
+ return normalizedSlotCount;
35522
+ }
35523
+ return Math.max(0, Math.min(elapsedMinutes / 60, normalizedSlotCount));
35524
+ };
35525
+ var formatSkuRailLabel = (label, segmentWidth, options = {}) => {
35526
+ const horizontalPadding = options.horizontalPadding ?? 8;
35527
+ const minVisibleChars = options.minVisibleChars ?? 4;
35528
+ const averageCharacterWidth = options.averageCharacterWidth ?? 6.6;
35529
+ const compactAverageCharacterWidth = options.compactAverageCharacterWidth ?? 5;
35530
+ const cssTruncation = options.cssTruncation ?? false;
35531
+ const fullLabelThreshold = 6;
35532
+ if (!label) return null;
35533
+ if (!Number.isFinite(segmentWidth) || segmentWidth <= horizontalPadding * 2) return null;
35534
+ const usableWidth = Math.max(segmentWidth - horizontalPadding * 2, 0);
35535
+ const maxChars = Math.floor(usableWidth / averageCharacterWidth);
35536
+ if (maxChars >= Math.max(minVisibleChars, fullLabelThreshold)) {
35537
+ if (label.length <= maxChars || cssTruncation) return label;
35538
+ if (maxChars <= 1) return null;
35539
+ return `${label.slice(0, Math.max(maxChars - 1, 1)).trimEnd()}\u2026`;
35540
+ }
35541
+ const compactLabel = buildCompactSkuRailLabel(label, Math.max(maxChars, 1));
35542
+ const compactMaxChars = Math.floor(usableWidth / compactAverageCharacterWidth);
35543
+ if (compactMaxChars < 1) return null;
35544
+ if (compactLabel.length <= compactMaxChars || cssTruncation) return compactLabel;
35545
+ if (compactMaxChars <= 2) {
35546
+ return compactLabel.slice(0, compactMaxChars);
35547
+ }
35548
+ return `${compactLabel.slice(0, Math.max(compactMaxChars - 1, 1)).trimEnd()}\u2026`;
35549
+ };
35550
+ var buildCompactSkuRailLabel = (label, maxChars) => {
35551
+ const words = label.trim().split(/\s+/).filter(Boolean);
35552
+ if (words.length === 0) return label;
35553
+ const [firstWord] = words;
35554
+ const acronym = words.map((word) => word[0]).join("").toUpperCase();
35555
+ if (maxChars <= 3 && acronym) {
35556
+ return acronym;
35557
+ }
35558
+ if (firstWord.length >= 4) {
35559
+ return firstWord;
35560
+ }
35561
+ return acronym || firstWord;
35562
+ };
35201
35563
  var HourlyOutputChartComponent = ({
35202
35564
  data,
35203
35565
  pphThreshold,
35566
+ hourlyTargetOutput,
35204
35567
  shiftStart,
35205
35568
  shiftEnd,
35206
35569
  showIdleTime = false,
35207
35570
  idleTimeHourly,
35571
+ shiftDate,
35572
+ timezone,
35208
35573
  skuSegments,
35209
35574
  activeSkuId,
35210
35575
  className = ""
@@ -35212,6 +35577,7 @@ var HourlyOutputChartComponent = ({
35212
35577
  const containerRef = React144__default.useRef(null);
35213
35578
  const [containerReady, setContainerReady] = React144__default.useState(false);
35214
35579
  const [containerWidth, setContainerWidth] = React144__default.useState(0);
35580
+ const [hoveredSkuRailLabel, setHoveredSkuRailLabel] = React144__default.useState(null);
35215
35581
  const idleSlots = React144__default.useMemo(
35216
35582
  () => buildHourlyIdleSlots({
35217
35583
  idleTimeHourly,
@@ -35357,14 +35723,54 @@ var HourlyOutputChartComponent = ({
35357
35723
  }, [containerWidth]);
35358
35724
  const shiftWindow = React144__default.useMemo(() => resolveShiftWindow2({
35359
35725
  shiftStart,
35360
- slotCount: SHIFT_DURATION
35361
- }), [shiftStart, shiftEnd, SHIFT_DURATION]);
35726
+ slotCount: SHIFT_DURATION,
35727
+ shiftDate,
35728
+ timezone
35729
+ }), [shiftStart, shiftEnd, SHIFT_DURATION, shiftDate, timezone]);
35730
+ const fallbackTimelineEndOffset = React144__default.useMemo(
35731
+ () => resolveTimelineEndOffset({
35732
+ shiftStart,
35733
+ shiftEnd,
35734
+ slotCount: SHIFT_DURATION,
35735
+ shiftDate,
35736
+ timezone
35737
+ }),
35738
+ [shiftStart, shiftEnd, SHIFT_DURATION, shiftDate, timezone]
35739
+ );
35740
+ const observedTimelineEndOffset = React144__default.useMemo(() => {
35741
+ let lastObservedMinute = -1;
35742
+ idleSlots.forEach((slot) => {
35743
+ slot.idleArray.forEach((value, minuteIndex) => {
35744
+ if (value !== "x" && value !== null && value !== void 0) {
35745
+ lastObservedMinute = Math.max(
35746
+ lastObservedMinute,
35747
+ slot.hourIndex * 60 + minuteIndex
35748
+ );
35749
+ }
35750
+ });
35751
+ });
35752
+ if (lastObservedMinute < 0) return null;
35753
+ return Math.min((lastObservedMinute + 1) / 60, SHIFT_DURATION);
35754
+ }, [idleSlots, SHIFT_DURATION]);
35755
+ const timelineEndOffset = observedTimelineEndOffset ?? fallbackTimelineEndOffset;
35756
+ const targetLineEndOffset = React144__default.useMemo(() => {
35757
+ if (timelineEndOffset >= SHIFT_DURATION) {
35758
+ return SHIFT_DURATION;
35759
+ }
35760
+ if (Number.isInteger(timelineEndOffset)) {
35761
+ return timelineEndOffset;
35762
+ }
35763
+ return Math.min(Math.floor(timelineEndOffset) + 1, SHIFT_DURATION);
35764
+ }, [timelineEndOffset, SHIFT_DURATION]);
35362
35765
  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);
35766
+ if (!skuSegments || skuSegments.length === 0 || !shiftWindow || timelineEndOffset <= 0) {
35767
+ return [];
35768
+ }
35769
+ const withOffsets = skuSegments.flatMap((segment) => {
35770
+ const offset = computeSegmentOffset(segment, shiftWindow);
35771
+ if (offset === null) return [];
35772
+ return [{ segment, offset }];
35773
+ }).sort((a, b) => a.offset - b.offset);
35368
35774
  if (withOffsets.length === 0) return [];
35369
35775
  const deduped = [];
35370
35776
  const DUPLICATE_OFFSET_THRESHOLD = 1 / 60;
@@ -35378,9 +35784,9 @@ var HourlyOutputChartComponent = ({
35378
35784
  deduped[0] = { ...deduped[0], offset: 0 };
35379
35785
  }
35380
35786
  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));
35787
+ const nextOffset = index < deduped.length - 1 ? deduped[index + 1].offset : timelineEndOffset;
35788
+ const start = Math.max(0, Math.min(entry.offset, timelineEndOffset));
35789
+ const end = Math.max(start, Math.min(nextOffset, timelineEndOffset));
35384
35790
  return {
35385
35791
  skuId: entry.segment.sku_id,
35386
35792
  label: entry.segment.sku_code,
@@ -35389,15 +35795,66 @@ var HourlyOutputChartComponent = ({
35389
35795
  pphThreshold: entry.segment.pph_threshold ?? pphThreshold
35390
35796
  };
35391
35797
  }).filter((segment) => segment.end > segment.start);
35392
- }, [skuSegments, shiftWindow, SHIFT_DURATION, pphThreshold]);
35798
+ }, [skuSegments, shiftWindow, timelineEndOffset, pphThreshold]);
35799
+ const targetTimelineSegments = React144__default.useMemo(() => {
35800
+ if (skuTimelineSegments.length === 0) return [];
35801
+ return skuTimelineSegments.map((segment, index) => ({
35802
+ ...segment,
35803
+ end: index === skuTimelineSegments.length - 1 ? Math.max(segment.start, targetLineEndOffset) : segment.end
35804
+ })).filter((segment) => segment.end > segment.start);
35805
+ }, [skuTimelineSegments, targetLineEndOffset]);
35806
+ const hasHourlyTargetOutputProp = React144__default.useMemo(
35807
+ () => hourlyTargetOutput !== void 0,
35808
+ [hourlyTargetOutput]
35809
+ );
35810
+ const hasExplicitHourlyTargets = React144__default.useMemo(
35811
+ () => Array.isArray(hourlyTargetOutput) && hourlyTargetOutput.some((value) => value !== null && value !== void 0),
35812
+ [hourlyTargetOutput]
35813
+ );
35814
+ const hourlyTargetSegments = React144__default.useMemo(() => {
35815
+ if (!hasExplicitHourlyTargets) return [];
35816
+ const segments = [];
35817
+ let runStart = null;
35818
+ let runValue = null;
35819
+ const flush = (endIndex) => {
35820
+ if (runStart === null || runValue === null) return;
35821
+ segments.push({ start: runStart, end: endIndex, value: runValue });
35822
+ runStart = null;
35823
+ runValue = null;
35824
+ };
35825
+ for (let i = 0; i < SHIFT_DURATION; i += 1) {
35826
+ const rawValue = Array.isArray(hourlyTargetOutput) ? hourlyTargetOutput[i] : null;
35827
+ const value = rawValue === null || rawValue === void 0 ? null : Number(rawValue);
35828
+ if (value === null || !Number.isFinite(value)) {
35829
+ flush(i);
35830
+ continue;
35831
+ }
35832
+ if (runStart === null || runValue === null) {
35833
+ runStart = i;
35834
+ runValue = value;
35835
+ continue;
35836
+ }
35837
+ if (Math.abs(runValue - value) > 1e-6) {
35838
+ flush(i);
35839
+ runStart = i;
35840
+ runValue = value;
35841
+ }
35842
+ }
35843
+ flush(SHIFT_DURATION);
35844
+ return segments.filter((segment) => segment.end > segment.start);
35845
+ }, [SHIFT_DURATION, hasExplicitHourlyTargets, hourlyTargetOutput]);
35393
35846
  const activeSkuHourIndices = React144__default.useMemo(() => {
35394
35847
  const indices = /* @__PURE__ */ new Set();
35395
35848
  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);
35849
+ if (!skuSegments || !shiftWindow) return { indices, targets, hasTimeline: false };
35850
+ const segmentsWithOffsets = skuSegments.flatMap((segment) => {
35851
+ const offset = computeSegmentOffset(segment, shiftWindow);
35852
+ if (offset === null) return [];
35853
+ return [{ ...segment, offset }];
35854
+ }).sort((a, b) => a.offset - b.offset);
35855
+ if (segmentsWithOffsets.length === 0) {
35856
+ return { indices, targets, hasTimeline: false };
35857
+ }
35401
35858
  for (let i = 0; i < SHIFT_DURATION; i++) {
35402
35859
  const midpoint = i + 0.5;
35403
35860
  let activeSeg = segmentsWithOffsets[0];
@@ -35417,13 +35874,15 @@ var HourlyOutputChartComponent = ({
35417
35874
  }
35418
35875
  }
35419
35876
  }
35420
- return { indices, targets };
35877
+ return { indices, targets, hasTimeline: true };
35421
35878
  }, [skuSegments, activeSkuId, shiftWindow, SHIFT_DURATION, pphThreshold]);
35422
35879
  const chartData = React144__default.useMemo(() => {
35423
- const { indices, targets } = activeSkuHourIndices;
35880
+ const { indices, targets, hasTimeline } = activeSkuHourIndices;
35424
35881
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
35425
35882
  const idleSlot = idleSlots[i];
35426
- const currentTarget = targets[i] || pphThreshold;
35883
+ const explicitTarget = hasHourlyTargetOutputProp ? hourlyTargetOutput?.[i] ?? null : void 0;
35884
+ const currentTarget = hasHourlyTargetOutputProp ? explicitTarget : targets[i] || pphThreshold;
35885
+ const comparisonTarget = currentTarget === null || currentTarget === void 0 ? targets[i] || pphThreshold : currentTarget;
35427
35886
  return {
35428
35887
  hourIndex: idleSlot?.hourIndex ?? i,
35429
35888
  hour: idleSlot?.hour || "",
@@ -35431,16 +35890,16 @@ var HourlyOutputChartComponent = ({
35431
35890
  output: animatedData[i] || 0,
35432
35891
  originalOutput: data[i] || 0,
35433
35892
  // Keep original data for labels
35434
- target: currentTarget,
35435
- color: (animatedData[i] || 0) >= Math.round(currentTarget) ? "#00AB45" : "#E34329",
35893
+ target: currentTarget ?? null,
35894
+ color: (animatedData[i] || 0) >= comparisonTarget ? "#00AB45" : "#E34329",
35436
35895
  idleMinutes: idleSlot?.idleMinutes || 0,
35437
35896
  idleArray: idleSlot?.idleArray || [],
35438
35897
  skuIndex: i,
35439
35898
  isHighlighted: indices.has(i),
35440
- isDimmed: !!activeSkuId && !indices.has(i)
35899
+ isDimmed: hasTimeline && !!activeSkuId && !indices.has(i)
35441
35900
  };
35442
35901
  });
35443
- }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId]);
35902
+ }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION, activeSkuHourIndices, activeSkuId, hourlyTargetOutput, hasHourlyTargetOutputProp]);
35444
35903
  const renderSkuTimelineRail = React144__default.useCallback((props) => {
35445
35904
  if (!skuTimelineSegments.length || SHIFT_DURATION <= 0) return null;
35446
35905
  const offset = props?.offset;
@@ -35452,8 +35911,9 @@ var HourlyOutputChartComponent = ({
35452
35911
  const railHeight = 3;
35453
35912
  const railY = top - 10;
35454
35913
  const baselineY = railY + railHeight / 2;
35455
- const labelYHigh = railY - 20;
35456
- const labelYLow = railY - 6;
35914
+ const showHoverLabel = (label, centerX, rY) => {
35915
+ setHoveredSkuRailLabel({ label, centerX, railY: rY });
35916
+ };
35457
35917
  return /* @__PURE__ */ jsxs("g", { children: [
35458
35918
  /* @__PURE__ */ jsx(
35459
35919
  "line",
@@ -35471,9 +35931,43 @@ var HourlyOutputChartComponent = ({
35471
35931
  const xStart = left + segment.start / SHIFT_DURATION * width;
35472
35932
  const xEnd = left + segment.end / SHIFT_DURATION * width;
35473
35933
  const segmentWidth = Math.max(1, xEnd - xStart);
35934
+ const labelPadding = segmentWidth < 48 ? 4 : 8;
35935
+ const inlineLabelText = formatSkuRailLabel(segment.label, segmentWidth, {
35936
+ horizontalPadding: labelPadding,
35937
+ cssTruncation: true
35938
+ });
35939
+ const badgeLabelText = formatSkuRailLabel(segment.label, 28, {
35940
+ horizontalPadding: 4,
35941
+ cssTruncation: true
35942
+ });
35943
+ const isMicroSegment = segmentWidth < 18;
35944
+ const labelText = isMicroSegment ? badgeLabelText : inlineLabelText;
35945
+ const labelClipWidth = Math.max(segmentWidth - labelPadding * 2, 0);
35474
35946
  const isActive = !!activeSkuId && segment.skuId === activeSkuId;
35475
35947
  const isDimmed = !!activeSkuId && segment.skuId !== activeSkuId;
35948
+ const hoverHitWidth = Math.max(segmentWidth + 12, isMicroSegment ? 20 : segmentWidth);
35949
+ const hoverHitX = Math.max(
35950
+ left,
35951
+ Math.min(xStart - (hoverHitWidth - segmentWidth) / 2, left + width - hoverHitWidth)
35952
+ );
35953
+ const hoverHitHeight = isMicroSegment ? 34 : 28;
35954
+ const hoverHitY = railY - (isMicroSegment ? 30 : 24);
35955
+ const microBadgeWidth = labelText ? Math.max(labelText.length * 6.2 + 10, 18) : 0;
35956
+ const microBadgeX = xStart + segmentWidth / 2 - microBadgeWidth / 2;
35957
+ const microBadgeY = railY - 24;
35476
35958
  return /* @__PURE__ */ jsxs("g", { children: [
35959
+ /* @__PURE__ */ jsx(
35960
+ "rect",
35961
+ {
35962
+ x: hoverHitX,
35963
+ y: hoverHitY,
35964
+ width: hoverHitWidth,
35965
+ height: hoverHitHeight,
35966
+ fill: "transparent",
35967
+ onMouseEnter: () => showHoverLabel(segment.label, xStart + segmentWidth / 2, railY),
35968
+ onMouseLeave: () => setHoveredSkuRailLabel((current) => current?.label === segment.label ? null : current)
35969
+ }
35970
+ ),
35477
35971
  /* @__PURE__ */ jsx(
35478
35972
  "rect",
35479
35973
  {
@@ -35484,7 +35978,7 @@ var HourlyOutputChartComponent = ({
35484
35978
  rx: 1.5,
35485
35979
  fill: isActive ? "#3b82f6" : "#cbd5e1",
35486
35980
  opacity: isDimmed ? 0.3 : 1,
35487
- style: { transition: "all 0.3s ease" }
35981
+ style: { transition: "all 0.3s ease", pointerEvents: "none" }
35488
35982
  }
35489
35983
  ),
35490
35984
  index > 0 && /* @__PURE__ */ jsx(
@@ -35496,22 +35990,59 @@ var HourlyOutputChartComponent = ({
35496
35990
  y2: railY + railHeight + 3,
35497
35991
  stroke: "#94a3b8",
35498
35992
  strokeWidth: 1,
35499
- opacity: 0.6
35993
+ opacity: 0.6,
35994
+ style: { pointerEvents: "none" }
35500
35995
  }
35501
35996
  ),
35502
- segmentWidth >= 36 && /* @__PURE__ */ jsx(
35503
- "text",
35997
+ isMicroSegment && labelText && /* @__PURE__ */ jsxs("g", { style: { pointerEvents: "none" }, children: [
35998
+ /* @__PURE__ */ jsx(
35999
+ "rect",
36000
+ {
36001
+ x: microBadgeX,
36002
+ y: microBadgeY,
36003
+ width: microBadgeWidth,
36004
+ height: 14,
36005
+ rx: 7,
36006
+ fill: "white",
36007
+ stroke: isActive ? "#3b82f6" : "#cbd5e1",
36008
+ strokeWidth: 1,
36009
+ opacity: isDimmed ? 0.55 : 0.98
36010
+ }
36011
+ ),
36012
+ /* @__PURE__ */ jsx(
36013
+ "text",
36014
+ {
36015
+ x: xStart + segmentWidth / 2,
36016
+ y: microBadgeY + 10,
36017
+ textAnchor: "middle",
36018
+ fontSize: 8,
36019
+ fontWeight: 700,
36020
+ fill: isActive ? "#2563eb" : "#64748b",
36021
+ opacity: isDimmed ? 0.55 : 1,
36022
+ style: { transition: "all 0.3s ease" },
36023
+ children: labelText
36024
+ }
36025
+ )
36026
+ ] }),
36027
+ !isMicroSegment && labelText && labelClipWidth > 0 && /* @__PURE__ */ jsx(
36028
+ "foreignObject",
35504
36029
  {
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
36030
+ x: xStart + labelPadding,
36031
+ y: railY - 24,
36032
+ width: labelClipWidth,
36033
+ height: 18,
36034
+ style: { pointerEvents: "none", overflow: "visible" },
36035
+ children: /* @__PURE__ */ jsx(
36036
+ "div",
36037
+ {
36038
+ 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"}`,
36039
+ style: {
36040
+ opacity: isDimmed ? 0.4 : 1,
36041
+ transition: "all 0.3s ease"
36042
+ },
36043
+ children: labelText
36044
+ }
36045
+ )
35515
36046
  }
35516
36047
  )
35517
36048
  ] }, `sku-rail-${segment.skuId}-${segment.start}-${index}`);
@@ -35569,14 +36100,17 @@ var HourlyOutputChartComponent = ({
35569
36100
  );
35570
36101
  }, [idleBarState.visible, idleBarState.key, idleBarState.shouldAnimate]);
35571
36102
  const maxDataValue = Math.max(...data, 0);
35572
- const maxTargetValue = Math.max(...chartData.map((d) => d.target), pphThreshold, 0);
36103
+ const numericChartTargets = chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target));
36104
+ const maxTargetValue = Math.max(...numericChartTargets, pphThreshold, 0);
35573
36105
  const maxYValue = Math.max(
35574
36106
  Math.ceil(maxTargetValue * 1.5),
35575
36107
  Math.ceil(maxDataValue * 1.15)
35576
36108
  // Add 15% headroom above max value
35577
36109
  );
35578
36110
  const generateYAxisTicks = () => {
35579
- const uniqueTargets = [...new Set(chartData.map((d) => Math.round(d.target)))].sort((a, b) => a - b);
36111
+ const uniqueTargets = [...new Set(
36112
+ chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target)).map((target) => Math.round(target))
36113
+ )].sort((a, b) => a - b);
35580
36114
  const rawTicks = [0];
35581
36115
  uniqueTargets.forEach((target) => {
35582
36116
  if (target > 0) {
@@ -35611,14 +36145,54 @@ var HourlyOutputChartComponent = ({
35611
36145
  };
35612
36146
  const renderTargetLine = React144__default.useCallback((props) => {
35613
36147
  const { offset, yAxisMap } = props;
35614
- if (!offset || !yAxisMap || SHIFT_DURATION <= 0) return null;
36148
+ if (!offset || !yAxisMap || SHIFT_DURATION <= 0 || targetLineEndOffset <= 0) return null;
35615
36149
  const { left, width } = offset;
35616
36150
  const yAxis = yAxisMap["default"] || yAxisMap[0];
35617
36151
  if (!yAxis || !yAxis.scale) return null;
35618
36152
  const lines = [];
35619
36153
  const offsetToX = (o) => left + o / SHIFT_DURATION * width;
35620
- if (skuTimelineSegments.length > 0) {
35621
- skuTimelineSegments.forEach((segment, index) => {
36154
+ if (hasHourlyTargetOutputProp && hourlyTargetSegments.length > 0) {
36155
+ hourlyTargetSegments.forEach((segment, index) => {
36156
+ const y = yAxis.scale(segment.value);
36157
+ const xStart = offsetToX(segment.start);
36158
+ const xEnd = offsetToX(segment.end);
36159
+ lines.push(
36160
+ /* @__PURE__ */ jsx(
36161
+ "line",
36162
+ {
36163
+ x1: xStart,
36164
+ y1: y,
36165
+ x2: xEnd,
36166
+ y2: y,
36167
+ stroke: "#E34329",
36168
+ strokeDasharray: "3 3",
36169
+ strokeWidth: 2
36170
+ },
36171
+ `target-hourly-h-${index}`
36172
+ )
36173
+ );
36174
+ const next = hourlyTargetSegments[index + 1];
36175
+ if (next && Math.abs(next.value - segment.value) > 1e-6) {
36176
+ const nextY = yAxis.scale(next.value);
36177
+ lines.push(
36178
+ /* @__PURE__ */ jsx(
36179
+ "line",
36180
+ {
36181
+ x1: xEnd,
36182
+ y1: y,
36183
+ x2: xEnd,
36184
+ y2: nextY,
36185
+ stroke: "#E34329",
36186
+ strokeDasharray: "3 3",
36187
+ strokeWidth: 2
36188
+ },
36189
+ `target-hourly-v-${index}`
36190
+ )
36191
+ );
36192
+ }
36193
+ });
36194
+ } else if (!hasHourlyTargetOutputProp && targetTimelineSegments.length > 0) {
36195
+ targetTimelineSegments.forEach((segment, index) => {
35622
36196
  const target = segment.pphThreshold || pphThreshold;
35623
36197
  const y = yAxis.scale(target);
35624
36198
  const xStart = offsetToX(segment.start);
@@ -35638,7 +36212,7 @@ var HourlyOutputChartComponent = ({
35638
36212
  `target-h-${index}`
35639
36213
  )
35640
36214
  );
35641
- const next = skuTimelineSegments[index + 1];
36215
+ const next = targetTimelineSegments[index + 1];
35642
36216
  if (next) {
35643
36217
  const nextTarget = next.pphThreshold || pphThreshold;
35644
36218
  if (nextTarget !== target) {
@@ -35661,7 +36235,7 @@ var HourlyOutputChartComponent = ({
35661
36235
  }
35662
36236
  }
35663
36237
  });
35664
- } else {
36238
+ } else if (!hasHourlyTargetOutputProp) {
35665
36239
  const y = yAxis.scale(pphThreshold);
35666
36240
  lines.push(
35667
36241
  /* @__PURE__ */ jsx(
@@ -35680,10 +36254,13 @@ var HourlyOutputChartComponent = ({
35680
36254
  );
35681
36255
  }
35682
36256
  return /* @__PURE__ */ jsx("g", { children: lines });
35683
- }, [skuTimelineSegments, SHIFT_DURATION, pphThreshold]);
36257
+ }, [hourlyTargetSegments, targetTimelineSegments, SHIFT_DURATION, pphThreshold, targetLineEndOffset, hasHourlyTargetOutputProp]);
35684
36258
  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`;
36259
+ const uniqueTargets = [...new Set(
36260
+ chartData.map((d) => d.target).filter((target) => target !== null && Number.isFinite(target)).map((target) => Math.round(target))
36261
+ )].sort((a, b) => a - b);
36262
+ const unitLabel = hasHourlyTargetOutputProp ? "units" : "units/hr";
36263
+ const targetText = uniqueTargets.length === 0 ? `Target` : uniqueTargets.length === 1 ? `Target: ${uniqueTargets[0]} ${unitLabel}` : `Target: ${uniqueTargets[0]} - ${uniqueTargets[uniqueTargets.length - 1]} ${unitLabel}`;
35687
36264
  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
36265
  /* @__PURE__ */ jsx("div", { className: "w-8 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t-2 border-[#E34329] border-dashed" }) }),
35689
36266
  /* @__PURE__ */ jsx("span", { children: targetText })
@@ -35696,254 +36273,274 @@ var HourlyOutputChartComponent = ({
35696
36273
  className: `w-full h-full min-w-0 flex flex-col ${className}`,
35697
36274
  style: { minHeight: "200px", minWidth: 0 },
35698
36275
  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
- ) });
36276
+ containerReady ? /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 relative", children: [
36277
+ /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
36278
+ BarChart$1,
36279
+ {
36280
+ data: chartData,
36281
+ margin: {
36282
+ // Reserve headroom for the SKU timeline rail + staggered
36283
+ // labels only when SKU segments are rendered. Non-SKU charts
36284
+ // keep the original 10px top so recharts has enough vertical
36285
+ // space to show the target (pph) tick label on the Y-axis.
36286
+ top: skuTimelineSegments.length > 0 ? 40 : 10,
36287
+ right: 10,
36288
+ bottom: 10,
36289
+ left: 6
36290
+ },
36291
+ barCategoryGap: "25%",
36292
+ children: [
36293
+ /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
36294
+ /* @__PURE__ */ jsx(
36295
+ XAxis,
36296
+ {
36297
+ xAxisId: "default",
36298
+ dataKey: "hour",
36299
+ tick: { fontSize: xAxisConfig.tickFont },
36300
+ interval: xAxisConfig.interval,
36301
+ angle: xAxisConfig.angle,
36302
+ textAnchor: "end",
36303
+ tickMargin: xAxisConfig.tickMargin,
36304
+ height: xAxisConfig.height
35764
36305
  }
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"
36306
+ ),
36307
+ /* @__PURE__ */ jsx(
36308
+ XAxis,
36309
+ {
36310
+ xAxisId: "sku",
36311
+ type: "number",
36312
+ dataKey: "skuIndex",
36313
+ domain: [0, Math.max(SHIFT_DURATION, 0)],
36314
+ hide: true,
36315
+ allowDataOverflow: true
36316
+ }
36317
+ ),
36318
+ /* @__PURE__ */ jsx(
36319
+ YAxis,
36320
+ {
36321
+ yAxisId: "default",
36322
+ tickMargin: 8,
36323
+ width: 48,
36324
+ domain: [0, maxYValue],
36325
+ ticks: generateYAxisTicks(),
36326
+ tickFormatter: (value) => value,
36327
+ tick: (props) => {
36328
+ const { x, y, payload } = props;
36329
+ return /* @__PURE__ */ jsx("g", { transform: `translate(${x},${y})`, children: /* @__PURE__ */ jsx(
36330
+ "text",
36331
+ {
36332
+ x: -2,
36333
+ y: 0,
36334
+ dy: 4,
36335
+ textAnchor: "end",
36336
+ fill: "#666",
36337
+ fontSize: 12,
36338
+ children: payload.value
36339
+ },
36340
+ `tick-${payload.value}-${x}-${y}`
36341
+ ) });
36342
+ }
36343
+ }
36344
+ ),
36345
+ /* @__PURE__ */ jsx(YAxis, { yAxisId: "idle", domain: [0, 60], hide: true }),
36346
+ /* @__PURE__ */ jsx(
36347
+ Tooltip,
36348
+ {
36349
+ cursor: { fill: "#f1f5f9" },
36350
+ contentStyle: { backgroundColor: "transparent", border: "none", padding: 0 },
36351
+ content: (props) => {
36352
+ if (!props.active || !props.payload || props.payload.length === 0)
36353
+ return null;
36354
+ const data2 = props.payload[0].payload;
36355
+ const idlePeriods = showIdleTime ? getHourlyIdlePeriods({
36356
+ idleArray: data2.idleArray,
36357
+ shiftStart,
36358
+ hourIndex: Number.isFinite(data2.hourIndex) ? data2.hourIndex : 0
36359
+ }) : [];
36360
+ 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: [
36361
+ /* @__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 }) }),
36362
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
36363
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
36364
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500 font-medium tracking-wide", children: "Output" }),
36365
+ /* @__PURE__ */ jsxs("span", { className: "font-bold text-slate-900 text-sm", children: [
36366
+ Math.round(data2.output),
36367
+ " ",
36368
+ /* @__PURE__ */ jsx("span", { className: "text-slate-400 font-normal text-xs ml-0.5", children: "units" })
36369
+ ] })
36370
+ ] }),
36371
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
36372
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500 font-medium tracking-wide", children: "Target" }),
36373
+ /* @__PURE__ */ jsxs("span", { className: "font-bold text-slate-700 text-sm", children: [
36374
+ Math.round(data2.target),
36375
+ " ",
36376
+ /* @__PURE__ */ jsx("span", { className: "text-slate-400 font-normal text-xs ml-0.5", children: "units" })
36377
+ ] })
36378
+ ] }),
36379
+ showIdleTime && data2.idleMinutes > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
36380
+ /* @__PURE__ */ jsx("div", { className: "pt-3 mt-3 border-t border-slate-100", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
36381
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500 font-medium tracking-wide", children: "Idle Time" }),
36382
+ /* @__PURE__ */ jsxs("span", { className: "font-bold text-orange-600 text-sm flex items-center gap-1.5", children: [
36383
+ /* @__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" }),
36384
+ data2.idleMinutes,
36385
+ " ",
36386
+ /* @__PURE__ */ jsx("span", { className: "text-orange-500/70 font-normal text-xs ml-0.5", children: "min" })
36387
+ ] })
36388
+ ] }) }),
36389
+ idlePeriods.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 bg-slate-50/80 rounded-lg p-3 border border-slate-100/50", children: [
36390
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-slate-400 text-[10px] mb-2.5 uppercase tracking-wider", children: "Idle Periods" }),
36391
+ /* @__PURE__ */ jsx("div", { className: "space-y-2.5 max-h-32 overflow-y-auto pr-1 custom-scrollbar", children: idlePeriods.map((period, index) => {
36392
+ return /* @__PURE__ */ jsxs(
36393
+ "div",
36394
+ {
36395
+ className: "flex items-start gap-2.5 text-xs",
36396
+ children: [
36397
+ /* @__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)]" }),
36398
+ /* @__PURE__ */ jsx("span", { className: "text-slate-700 font-medium tracking-tight", children: period.duration === 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
36399
+ period.startTime,
36400
+ " ",
36401
+ /* @__PURE__ */ jsxs("span", { className: "text-slate-400 font-normal ml-1", children: [
36402
+ "(",
36403
+ period.duration,
36404
+ "m)"
36405
+ ] })
36406
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
36407
+ period.startTime,
36408
+ " ",
36409
+ /* @__PURE__ */ jsx("span", { className: "text-slate-400 mx-0.5", children: "\u2192" }),
36410
+ " ",
36411
+ period.endTime,
36412
+ " ",
36413
+ /* @__PURE__ */ jsxs("span", { className: "text-slate-400 font-normal ml-1", children: [
36414
+ "(",
36415
+ period.duration,
36416
+ "m)"
36417
+ ] })
36418
+ ] }) })
36419
+ ]
36420
+ },
36421
+ index
36422
+ );
36423
+ }) })
35812
36424
  ] })
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
36425
  ] })
35843
36426
  ] })
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";
36427
+ ] });
36428
+ },
36429
+ animationDuration: 200
36430
+ }
36431
+ ),
36432
+ /* @__PURE__ */ jsx(Customized, { component: renderTargetLine }),
36433
+ /* @__PURE__ */ jsx(Customized, { component: renderSkuTimelineRail }),
36434
+ /* @__PURE__ */ jsxs(
36435
+ Bar,
36436
+ {
36437
+ xAxisId: "default",
36438
+ dataKey: "output",
36439
+ yAxisId: "default",
36440
+ maxBarSize: 35,
36441
+ radius: [10, 10, 0, 0],
36442
+ isAnimationActive: false,
36443
+ children: [
36444
+ chartData.map((entry, index) => /* @__PURE__ */ jsx(
36445
+ Cell,
36446
+ {
36447
+ fill: entry.color,
36448
+ stroke: "transparent",
36449
+ strokeWidth: 0,
36450
+ style: {
36451
+ filter: entry.isHighlighted ? "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)" : "brightness(1)",
36452
+ transform: entry.isHighlighted ? "translateY(-4px)" : "translateY(0)",
36453
+ opacity: entry.isDimmed ? 0.4 : 1,
36454
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
36455
+ cursor: "pointer"
36456
+ },
36457
+ onMouseEnter: (e) => {
36458
+ const target = e.target;
36459
+ target.style.filter = "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)";
36460
+ target.style.transform = "translateY(-4px)";
36461
+ target.style.opacity = "1";
36462
+ },
36463
+ onMouseLeave: (e) => {
36464
+ const target = e.target;
36465
+ target.style.filter = entry.isHighlighted ? "drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12)) brightness(1.05)" : "brightness(1)";
36466
+ target.style.transform = entry.isHighlighted ? "translateY(-4px)" : "translateY(0)";
36467
+ target.style.opacity = entry.isDimmed ? "0.4" : "1";
36468
+ }
35880
36469
  },
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";
36470
+ `cell-${index}`
36471
+ )),
36472
+ /* @__PURE__ */ jsx(
36473
+ LabelList,
36474
+ {
36475
+ dataKey: "originalOutput",
36476
+ position: "top",
36477
+ content: (props) => {
36478
+ const { x, y, width, value, payload } = props;
36479
+ const actualValue = payload?.originalOutput || value;
36480
+ if (!actualValue || actualValue === 0) return null;
36481
+ return /* @__PURE__ */ jsx(
36482
+ "text",
36483
+ {
36484
+ x: x + width / 2,
36485
+ y: y - 8,
36486
+ textAnchor: "middle",
36487
+ fontSize: "12",
36488
+ fontWeight: "600",
36489
+ fill: "#374151",
36490
+ style: {
36491
+ opacity: 1,
36492
+ pointerEvents: "none",
36493
+ transition: "none"
36494
+ },
36495
+ children: Math.round(actualValue)
36496
+ }
36497
+ );
36498
+ }
35886
36499
  }
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
- );
36500
+ )
36501
+ ]
36502
+ }
36503
+ ),
36504
+ IdleBar,
36505
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
36506
+ "pattern",
36507
+ {
36508
+ id: "idlePattern",
36509
+ patternUnits: "userSpaceOnUse",
36510
+ width: "4",
36511
+ height: "4",
36512
+ children: [
36513
+ /* @__PURE__ */ jsx("rect", { width: "4", height: "4", fill: "#4b5563", opacity: "0.6" }),
36514
+ /* @__PURE__ */ jsx(
36515
+ "path",
36516
+ {
36517
+ d: "M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2",
36518
+ stroke: "#374151",
36519
+ strokeWidth: "0.8",
36520
+ opacity: "0.8"
35916
36521
  }
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..." }) }),
36522
+ )
36523
+ ]
36524
+ }
36525
+ ) })
36526
+ ]
36527
+ }
36528
+ ) }),
36529
+ hoveredSkuRailLabel && /* @__PURE__ */ jsx(
36530
+ "div",
36531
+ {
36532
+ className: "absolute z-50 pointer-events-none transform -translate-x-1/2 -translate-y-full transition-opacity duration-200",
36533
+ style: {
36534
+ left: hoveredSkuRailLabel.centerX,
36535
+ top: hoveredSkuRailLabel.railY - 12
36536
+ },
36537
+ 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: [
36538
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold tracking-wider text-blue-500 uppercase leading-none mb-1", children: "SKU" }),
36539
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] font-semibold text-slate-800 whitespace-normal break-words leading-snug", children: hoveredSkuRailLabel.label })
36540
+ ] })
36541
+ }
36542
+ )
36543
+ ] }) : /* @__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
36544
  /* @__PURE__ */ jsx("div", { className: "flex-none pt-2", children: renderLegend() })
35948
36545
  ]
35949
36546
  }
@@ -35961,6 +36558,16 @@ var HourlyOutputChart = React144__default.memo(
35961
36558
  if (!prevProps.data.every((val, idx) => val === nextProps.data[idx])) {
35962
36559
  return false;
35963
36560
  }
36561
+ const prevHourlyTargets = prevProps.hourlyTargetOutput || [];
36562
+ const nextHourlyTargets = nextProps.hourlyTargetOutput || [];
36563
+ if (prevHourlyTargets.length !== nextHourlyTargets.length) {
36564
+ return false;
36565
+ }
36566
+ for (let i = 0; i < prevHourlyTargets.length; i += 1) {
36567
+ if (prevHourlyTargets[i] !== nextHourlyTargets[i]) {
36568
+ return false;
36569
+ }
36570
+ }
35964
36571
  const prevIdle = prevProps.idleTimeHourly || {};
35965
36572
  const nextIdle = nextProps.idleTimeHourly || {};
35966
36573
  const prevKeys = Object.keys(prevIdle);
@@ -36148,7 +36755,6 @@ var VideoCard = React144__default.memo(({
36148
36755
  shouldPlay,
36149
36756
  onClick,
36150
36757
  onFatalError,
36151
- isVeryLowEfficiency = false,
36152
36758
  legend,
36153
36759
  cropping,
36154
36760
  canvasFps = 30,
@@ -36220,11 +36826,6 @@ var VideoCard = React144__default.memo(({
36220
36826
  }
36221
36827
  },
36222
36828
  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
36829
  /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full overflow-hidden bg-black", children: [
36229
36830
  /* @__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
36831
  /* @__PURE__ */ jsx(Camera, { className: `w-5 h-5 sm:${compact ? "w-4 h-4" : "w-6 h-6"} text-gray-500` }),
@@ -36634,7 +37235,6 @@ var VideoGridView = React144__default.memo(({
36634
37235
  const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
36635
37236
  const workspaceKey = `${workspace.line_id || "unknown"}-${workspaceId}`;
36636
37237
  const isVisible = visibleWorkspaces.has(workspaceId);
36637
- const isVeryLowEfficiency = workspace.show_exclamation ?? (workspace.efficiency < 50 && workspace.efficiency >= 10);
36638
37238
  const workspaceCropping = getWorkspaceCropping(workspaceId, workspace.workspace_name);
36639
37239
  const workspaceStream = videoStreamsByWorkspaceId?.[workspaceId];
36640
37240
  const lastSeenLabel = workspace.workspace_uuid ? lastSeenByWorkspaceId[workspace.workspace_uuid]?.timeSinceLastUpdate : void 0;
@@ -36651,7 +37251,6 @@ var VideoGridView = React144__default.memo(({
36651
37251
  workspaceId,
36652
37252
  workspaceKey,
36653
37253
  isVisible,
36654
- isVeryLowEfficiency,
36655
37254
  workspaceCropping,
36656
37255
  fallbackUrl,
36657
37256
  hlsUrl,
@@ -36699,7 +37298,6 @@ var VideoGridView = React144__default.memo(({
36699
37298
  isR2Stream: card.isR2Stream,
36700
37299
  fallbackUrl: card.fallbackUrl
36701
37300
  }),
36702
- isVeryLowEfficiency: card.isVeryLowEfficiency,
36703
37301
  legend: effectiveLegend,
36704
37302
  cropping: card.workspaceCropping,
36705
37303
  canvasFps: effectiveCanvasFps,
@@ -37619,223 +38217,6 @@ var UptimeLineChartComponent = ({ points, className = "" }) => {
37619
38217
  ] }) }) });
37620
38218
  };
37621
38219
  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
38220
  var getTimeFromTimeString = (timeStr) => {
37840
38221
  if (!timeStr) {
37841
38222
  return { hour: 0, minute: 0, decimalHour: 0 };
@@ -50166,7 +50547,7 @@ var formatOperationalDateKey = (dateKey, options) => {
50166
50547
  timeZone: "UTC"
50167
50548
  });
50168
50549
  };
50169
- var getSegmentMinutes = (isoString, timeZone, reportDate) => {
50550
+ var getSegmentMinutes2 = (isoString, timeZone, reportDate) => {
50170
50551
  const date = new Date(isoString);
50171
50552
  if (isNaN(date.getTime())) return NaN;
50172
50553
  const formatter = new Intl.DateTimeFormat("en-US", {
@@ -50187,13 +50568,13 @@ var getSegmentMinutes = (isoString, timeZone, reportDate) => {
50187
50568
  if (dateKey > reportDate) {
50188
50569
  const d1 = new Date(dateKey);
50189
50570
  const d2 = new Date(reportDate);
50190
- const diffDays = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
50191
- minutes += diffDays * 24 * 60;
50571
+ const diffDays2 = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
50572
+ minutes += diffDays2 * 24 * 60;
50192
50573
  } else if (dateKey < reportDate) {
50193
50574
  const d1 = new Date(dateKey);
50194
50575
  const d2 = new Date(reportDate);
50195
- const diffDays = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
50196
- minutes -= diffDays * 24 * 60;
50576
+ const diffDays2 = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
50577
+ minutes -= diffDays2 * 24 * 60;
50197
50578
  }
50198
50579
  return minutes;
50199
50580
  };
@@ -50603,7 +50984,7 @@ var LinePdfGenerator = ({
50603
50984
  const skuRemarksByIndex = {};
50604
50985
  if (lineInfo.metrics.sku_segments && lineInfo.metrics.sku_segments.length > 0) {
50605
50986
  lineInfo.metrics.sku_segments.forEach((segment, segmentIndex) => {
50606
- const segmentMinutes = getSegmentMinutes(segment.start_time, reportTimezone, lineInfo.date);
50987
+ const segmentMinutes = getSegmentMinutes2(segment.start_time, reportTimezone, lineInfo.date);
50607
50988
  if (!isNaN(segmentMinutes)) {
50608
50989
  const intervalIndex = hourlyTimeRanges.findIndex(
50609
50990
  (inv) => segmentMinutes >= inv.start && segmentMinutes < inv.end
@@ -52338,7 +52719,7 @@ var formatOperationalDateKey2 = (dateKey, options) => {
52338
52719
  timeZone: "UTC"
52339
52720
  });
52340
52721
  };
52341
- var getSegmentMinutes2 = (isoString, timeZone, reportDate) => {
52722
+ var getSegmentMinutes3 = (isoString, timeZone, reportDate) => {
52342
52723
  const date = new Date(isoString);
52343
52724
  if (isNaN(date.getTime())) return NaN;
52344
52725
  const formatter = new Intl.DateTimeFormat("en-US", {
@@ -52359,13 +52740,13 @@ var getSegmentMinutes2 = (isoString, timeZone, reportDate) => {
52359
52740
  if (dateKey > reportDate) {
52360
52741
  const d1 = new Date(dateKey);
52361
52742
  const d2 = new Date(reportDate);
52362
- const diffDays = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
52363
- minutes += diffDays * 24 * 60;
52743
+ const diffDays2 = Math.round((d1.getTime() - d2.getTime()) / (1e3 * 3600 * 24));
52744
+ minutes += diffDays2 * 24 * 60;
52364
52745
  } else if (dateKey < reportDate) {
52365
52746
  const d1 = new Date(dateKey);
52366
52747
  const d2 = new Date(reportDate);
52367
- const diffDays = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
52368
- minutes -= diffDays * 24 * 60;
52748
+ const diffDays2 = Math.round((d2.getTime() - d1.getTime()) / (1e3 * 3600 * 24));
52749
+ minutes -= diffDays2 * 24 * 60;
52369
52750
  }
52370
52751
  return minutes;
52371
52752
  };
@@ -52597,7 +52978,7 @@ var WorkspacePdfGenerator = ({
52597
52978
  const skuRemarksByIndex = {};
52598
52979
  if (workspace.sku_segments && workspace.sku_segments.length > 0) {
52599
52980
  workspace.sku_segments.forEach((segment, segmentIndex) => {
52600
- const segmentMinutes = getSegmentMinutes2(segment.start_time, reportTimezone, workspace.date);
52981
+ const segmentMinutes = getSegmentMinutes3(segment.start_time, reportTimezone, workspace.date);
52601
52982
  if (!isNaN(segmentMinutes)) {
52602
52983
  const intervalIndex = hourlyIntervals.findIndex(
52603
52984
  (inv) => segmentMinutes >= inv.start && segmentMinutes < inv.end
@@ -53439,12 +53820,10 @@ var formatPercentRange = (min, max) => {
53439
53820
  return `${format10(min)}-${format10(max)}%`;
53440
53821
  };
53441
53822
  var Legend5 = ({
53442
- useBottleneckLabel = false,
53443
53823
  legend,
53444
53824
  metricLabel = "Efficiency"
53445
53825
  }) => {
53446
53826
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
53447
- const exclamationLabel = useBottleneckLabel ? "Bottleneck" : "<50% efficiency";
53448
53827
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 sm:gap-4 text-xs font-medium text-slate-600", children: [
53449
53828
  /* @__PURE__ */ jsxs("div", { className: "font-medium text-gray-700 hidden sm:block", children: [
53450
53829
  metricLabel,
@@ -53463,11 +53842,6 @@ var Legend5 = ({
53463
53842
  /* @__PURE__ */ jsx("div", { className: "w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full bg-[#E34329]" }),
53464
53843
  /* @__PURE__ */ jsx("span", { children: formatPercentRange(effectiveLegend.red_min, effectiveLegend.red_max) })
53465
53844
  ] })
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
53845
  ] })
53472
53846
  ] });
53473
53847
  };
@@ -53581,7 +53955,6 @@ var WorkspaceGrid = React144__default.memo(({
53581
53955
  factoryView = "factory",
53582
53956
  line2Uuid = "line-2",
53583
53957
  className = "",
53584
- hasFlowBuffers = false,
53585
53958
  legend = DEFAULT_EFFICIENCY_LEGEND,
53586
53959
  videoSources = {},
53587
53960
  videoStreamsByWorkspaceId = {},
@@ -53625,7 +53998,7 @@ var WorkspaceGrid = React144__default.memo(({
53625
53998
  );
53626
53999
  return /* @__PURE__ */ jsxs("div", { className: `flex flex-col w-full h-full overflow-hidden bg-slate-50/50 ${className}`, children: [
53627
54000
  /* @__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 }) }) }),
54001
+ /* @__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
54002
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3 shrink-0", children: [
53630
54003
  toolbarRightContent,
53631
54004
  mapViewEnabled && /* @__PURE__ */ jsx(
@@ -53645,7 +54018,7 @@ var WorkspaceGrid = React144__default.memo(({
53645
54018
  )
53646
54019
  ] })
53647
54020
  ] }),
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 }) }),
54021
+ /* @__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
54022
  /* @__PURE__ */ jsx("div", { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: viewMode === "video" ? /* @__PURE__ */ jsx(
53650
54023
  motion.div,
53651
54024
  {
@@ -64306,6 +64679,7 @@ var buildLineInfoSnapshot = (lineDetails, metrics2) => {
64306
64679
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
64307
64680
  output_array: metrics2.output_array || [],
64308
64681
  output_hourly: metrics2.output_hourly,
64682
+ hourly_target_output: metrics2.hourly_target_output ?? null,
64309
64683
  line_threshold: metrics2.line_threshold ?? 0,
64310
64684
  threshold_pph: metrics2.threshold_pph ?? 0,
64311
64685
  shift_start: metrics2.shift_start || "06:00",
@@ -64392,6 +64766,7 @@ var transformLineMetrics = (lineId, detailResponse, queryDate, queryShiftId) =>
64392
64766
  underperforming_workspace_names: [],
64393
64767
  underperforming_workspace_uuids: [],
64394
64768
  output_array: [],
64769
+ hourly_target_output: null,
64395
64770
  line_threshold: 0,
64396
64771
  threshold_pph: 0,
64397
64772
  shift_start: "06:00",
@@ -65429,6 +65804,9 @@ var BottomSection = memo$1(({
65429
65804
  workspaceDisplayNames,
65430
65805
  hourlyOutputData,
65431
65806
  hourlyThreshold,
65807
+ hourlyTargetOutput,
65808
+ idleTimeHourly,
65809
+ timezone,
65432
65810
  urlDate,
65433
65811
  urlShift,
65434
65812
  navigate,
@@ -65599,8 +65977,12 @@ var BottomSection = memo$1(({
65599
65977
  {
65600
65978
  data: hourlyOutputData,
65601
65979
  pphThreshold: hourlyThreshold,
65980
+ hourlyTargetOutput,
65602
65981
  shiftStart: lineInfo.metrics.shift_start || "06:00",
65603
65982
  shiftEnd: lineInfo.metrics.shift_end,
65983
+ idleTimeHourly,
65984
+ shiftDate: lineInfo.date,
65985
+ timezone,
65604
65986
  skuSegments: skuAware ? skuSegments : void 0,
65605
65987
  activeSkuId
65606
65988
  }
@@ -65621,6 +66003,9 @@ var BottomSection = memo$1(({
65621
66003
  if (prevProps.lineInfo.monitoring_mode !== nextProps.lineInfo.monitoring_mode) return false;
65622
66004
  if (prevProps.skuAware !== nextProps.skuAware) return false;
65623
66005
  if (prevProps.activeSkuId !== nextProps.activeSkuId) return false;
66006
+ if (JSON.stringify(prevProps.hourlyTargetOutput || []) !== JSON.stringify(nextProps.hourlyTargetOutput || [])) {
66007
+ return false;
66008
+ }
65624
66009
  const prevSkuSegmentsSignature = JSON.stringify(
65625
66010
  (prevProps.skuSegments || []).map((segment) => ({
65626
66011
  sku_id: segment.sku_id,
@@ -65693,7 +66078,9 @@ var BottomSection = memo$1(({
65693
66078
  );
65694
66079
  if (prevWorkspaceSignature !== nextWorkspaceSignature) return false;
65695
66080
  if (prevProps.lineInfo.metrics.shift_start !== nextProps.lineInfo.metrics.shift_start) return false;
66081
+ if (prevProps.lineInfo.metrics.shift_end !== nextProps.lineInfo.metrics.shift_end) return false;
65696
66082
  if (prevProps.hourlyThreshold !== nextProps.hourlyThreshold) return false;
66083
+ if (JSON.stringify(prevProps.idleTimeHourly || {}) !== JSON.stringify(nextProps.idleTimeHourly || {})) return false;
65697
66084
  if (prevProps.urlDate !== nextProps.urlDate || prevProps.urlShift !== nextProps.urlShift) return false;
65698
66085
  if (prevProps.workspaceDisplayNames !== nextProps.workspaceDisplayNames) return false;
65699
66086
  return true;
@@ -65961,6 +66348,7 @@ var KPIDetailView = ({
65961
66348
  }
65962
66349
  }, [urlDate, urlShift, urlTab]);
65963
66350
  const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineId);
66351
+ const lineTimezone = shiftConfig?.timezone || configuredTimezone;
65964
66352
  const getShiftName = useCallback((shiftId) => {
65965
66353
  return getShiftNameById(shiftId, configuredTimezone, shiftConfig);
65966
66354
  }, [configuredTimezone, shiftConfig]);
@@ -66001,12 +66389,12 @@ var KPIDetailView = ({
66001
66389
  operationalTodayDate.setHours(0, 0, 0, 0);
66002
66390
  compareDateInZone.setHours(0, 0, 0, 0);
66003
66391
  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`;
66392
+ const diffDays2 = Math.round(diffTime / (1e3 * 60 * 60 * 24));
66393
+ if (diffDays2 === 0) return "Today";
66394
+ if (diffDays2 === -1) return "Yesterday";
66395
+ if (diffDays2 === 1) return "Tomorrow";
66396
+ if (diffDays2 < -1) return `${Math.abs(diffDays2)} days ago`;
66397
+ if (diffDays2 > 1) return `${diffDays2} days ahead`;
66010
66398
  return "Today";
66011
66399
  }, [configuredTimezone, shiftConfig]);
66012
66400
  const {
@@ -66216,6 +66604,7 @@ var KPIDetailView = ({
66216
66604
  underperforming_workspace_uuids: metrics2.underperforming_workspace_uuids || [],
66217
66605
  output_array: metrics2.output_array || [],
66218
66606
  output_hourly: metrics2.output_hourly,
66607
+ hourly_target_output: metrics2.hourly_target_output ?? null,
66219
66608
  line_threshold: metrics2.line_threshold ?? 0,
66220
66609
  threshold_pph: metrics2.threshold_pph ?? 0,
66221
66610
  shift_start: metrics2.shift_start || "06:00",
@@ -66225,7 +66614,7 @@ var KPIDetailView = ({
66225
66614
  idle_time_hourly: metrics2.idle_time_hourly || null,
66226
66615
  // Multi-SKU additive fields (Phase 6) — propagated from
66227
66616
  // `useLineDetailPageData`. Backend authoritative; we never recompute.
66228
- // The selector below uses these to swap header KPIs per selected SKU.
66617
+ // The output-card selection below uses these to swap header KPIs per selected SKU.
66229
66618
  sku_aware: Boolean(metrics2.sku_aware),
66230
66619
  real_sku_count: metrics2.real_sku_count ?? 0,
66231
66620
  sku_breakdown: Array.isArray(metrics2.sku_breakdown) ? metrics2.sku_breakdown : [],
@@ -66322,7 +66711,7 @@ var KPIDetailView = ({
66322
66711
  }, [lineSkuSegments, outputChartSkuBreakdown, hasUrlDate, hasUrlShift]);
66323
66712
  const normalizedSelectedSkuId = selectedSkuId !== "all" ? selectedSkuId : null;
66324
66713
  const isLineSkuAware = Boolean(resolvedLineInfo?.metrics.sku_aware);
66325
- const showSkuSelector = isLineSkuAware && realSkuOptions.length > 0;
66714
+ const showSkuSelector = isLineSkuAware && realSkuOptions.length > 0 && !isUptimeMode;
66326
66715
  useEffect(() => {
66327
66716
  if (selectedSkuId === "all") return;
66328
66717
  const stillPresent = realSkuOptions.some((item) => item.sku_id === selectedSkuId);
@@ -66349,15 +66738,11 @@ var KPIDetailView = ({
66349
66738
  ...resolvedLineInfo.metrics,
66350
66739
  current_output: selectedSkuRow.current_output ?? 0,
66351
66740
  ideal_output: selectedSkuRow.ideal_output ?? 0,
66352
- avg_efficiency: selectedSkuRow.avg_efficiency ?? resolvedLineInfo.metrics.avg_efficiency,
66353
66741
  total_workspaces: selectedSkuRow.total_workspaces ?? resolvedLineInfo.metrics.total_workspaces,
66354
66742
  underperforming_workspaces: selectedSkuRow.underperforming_workspaces ?? resolvedLineInfo.metrics.underperforming_workspaces,
66355
66743
  underperforming_workspace_names: selectedSkuRow.underperforming_workspace_names ?? resolvedLineInfo.metrics.underperforming_workspace_names,
66356
66744
  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
66745
+ line_threshold: selectedSkuRow.line_threshold ?? resolvedLineInfo.metrics.line_threshold
66361
66746
  }
66362
66747
  };
66363
66748
  }, [resolvedLineInfo, selectedSkuRow]);
@@ -67136,11 +67521,9 @@ var KPIDetailView = ({
67136
67521
  showIdleTime: idleTimeVlmEnabled
67137
67522
  }
67138
67523
  ) : (
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).
67524
+ // Keep the line output + underperforming cards SKU-aware,
67525
+ // while Average Efficiency stays on the aggregate line
67526
+ // metrics even when a specific SKU is selected.
67144
67527
  /* @__PURE__ */ jsx(
67145
67528
  MetricCards,
67146
67529
  {
@@ -67185,6 +67568,9 @@ var KPIDetailView = ({
67185
67568
  workspaceDisplayNames,
67186
67569
  hourlyOutputData,
67187
67570
  hourlyThreshold,
67571
+ hourlyTargetOutput: chartMetrics?.hourly_target_output ?? null,
67572
+ idleTimeHourly: chartMetrics?.idle_time_hourly,
67573
+ timezone: lineTimezone,
67188
67574
  urlDate,
67189
67575
  urlShift,
67190
67576
  navigate,
@@ -67250,8 +67636,8 @@ var KPIDetailView = ({
67250
67636
  showIdleTime: idleTimeVlmEnabled
67251
67637
  }
67252
67638
  ) : (
67253
- // Phase 6: pass `displayLineInfo` so the SKU selector
67254
- // swaps the four SKU-specific fields on the header.
67639
+ // Keep the line output + underperforming cards SKU-aware,
67640
+ // while Average Efficiency stays aggregate.
67255
67641
  /* @__PURE__ */ jsx(
67256
67642
  MetricCards,
67257
67643
  {
@@ -67296,6 +67682,8 @@ var KPIDetailView = ({
67296
67682
  workspaceDisplayNames,
67297
67683
  hourlyOutputData,
67298
67684
  hourlyThreshold,
67685
+ idleTimeHourly: chartMetrics?.idle_time_hourly,
67686
+ timezone: lineTimezone,
67299
67687
  urlDate,
67300
67688
  urlShift,
67301
67689
  navigate,
@@ -73532,12 +73920,12 @@ var getDaysDifference = (date, timezone = "UTC", shiftStartTime = "06:00") => {
73532
73920
  operationalTodayDate.setHours(0, 0, 0, 0);
73533
73921
  compareDateInTz.setHours(0, 0, 0, 0);
73534
73922
  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`;
73923
+ const diffDays2 = Math.round(diffTime / (1e3 * 60 * 60 * 24));
73924
+ if (diffDays2 === 0) return "Today";
73925
+ if (diffDays2 === -1) return "Yesterday";
73926
+ if (diffDays2 === 1) return "Tomorrow";
73927
+ if (diffDays2 < -1) return `${Math.abs(diffDays2)} days ago`;
73928
+ if (diffDays2 > 1) return `${diffDays2} days ahead`;
73541
73929
  return "Today";
73542
73930
  };
73543
73931
  var getInitialTab = (sourceType, defaultTab, fromMonthly, urlDate) => {
@@ -73973,7 +74361,7 @@ var WorkspaceDetailView = ({
73973
74361
  () => resolveLiveSkuId(workspace?.sku_segments),
73974
74362
  [workspace?.sku_segments]
73975
74363
  );
73976
- const activeSkuId = selectedSkuId || liveSkuId;
74364
+ const activeSkuId = selectedSkuId;
73977
74365
  const resolvedLineId = effectiveLineId || workspace?.line_id || cachedDetailedMetrics?.line_id || cachedOverviewMetrics?.line_id || overviewFallback?.line_id;
73978
74366
  const { timezone: cycleTimeTimezone } = useTimezone({
73979
74367
  lineId: resolvedLineId || void 0,
@@ -74941,6 +75329,7 @@ var WorkspaceDetailView = ({
74941
75329
  {
74942
75330
  data: workspace.hourly_action_counts || [],
74943
75331
  pphThreshold: workspace.pph_threshold || 0,
75332
+ hourlyTargetOutput: workspace.hourly_target_output,
74944
75333
  shiftStart: workspace.shift_start || "06:00",
74945
75334
  shiftEnd: workspace.shift_end,
74946
75335
  showIdleTime: showChartIdleTime,
@@ -74948,7 +75337,7 @@ var WorkspaceDetailView = ({
74948
75337
  idleTimeClips,
74949
75338
  idleTimeClipClassifications,
74950
75339
  shiftDate: idleClipDate,
74951
- timezone,
75340
+ timezone: effectiveCycleTimeTimezone,
74952
75341
  skuSegments: isSkuAware ? skuSegments : void 0,
74953
75342
  activeSkuId
74954
75343
  }
@@ -75087,6 +75476,7 @@ var WorkspaceDetailView = ({
75087
75476
  {
75088
75477
  data: workspace.hourly_action_counts || [],
75089
75478
  pphThreshold: workspace.pph_threshold || 0,
75479
+ hourlyTargetOutput: workspace.hourly_target_output,
75090
75480
  shiftStart: workspace.shift_start || "06:00",
75091
75481
  shiftEnd: workspace.shift_end,
75092
75482
  showIdleTime: showChartIdleTime,
@@ -75094,7 +75484,7 @@ var WorkspaceDetailView = ({
75094
75484
  idleTimeClips,
75095
75485
  idleTimeClipClassifications,
75096
75486
  shiftDate: idleClipDate,
75097
- timezone,
75487
+ timezone: effectiveCycleTimeTimezone,
75098
75488
  skuSegments: isSkuAware ? skuSegments : void 0,
75099
75489
  activeSkuId
75100
75490
  }
@@ -78371,8 +78761,8 @@ var ImprovementCenterView = () => {
78371
78761
  const firstSeen = new Date(new Date(openedAt).toLocaleString("en-US", { timeZone: timezone }));
78372
78762
  if (Number.isNaN(firstSeen.getTime())) return void 0;
78373
78763
  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));
78764
+ const diffDays2 = Math.max(0, Math.floor((now4.getTime() - firstSeen.getTime()) / (1e3 * 60 * 60 * 24)));
78765
+ return Math.max(1, Math.ceil(diffDays2 / 7));
78376
78766
  };
78377
78767
  const toZonedDate = (value) => {
78378
78768
  if (!value) return void 0;
@@ -79053,12 +79443,12 @@ var ThreadSidebar = ({
79053
79443
  const date = new Date(dateString);
79054
79444
  const now4 = /* @__PURE__ */ new Date();
79055
79445
  const diffMs = now4.getTime() - date.getTime();
79056
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
79057
- if (diffDays === 0) {
79446
+ const diffDays2 = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
79447
+ if (diffDays2 === 0) {
79058
79448
  return "Today";
79059
- } else if (diffDays === 1) {
79449
+ } else if (diffDays2 === 1) {
79060
79450
  return "Yesterday";
79061
- } else if (diffDays < 7) {
79451
+ } else if (diffDays2 < 7) {
79062
79452
  return date.toLocaleDateString("en-US", { weekday: "short" });
79063
79453
  } else {
79064
79454
  return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });