@optifye/dashboard-core 6.11.16 → 6.11.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3295,11 +3295,9 @@ var normalizeShiftDefinitions = (timezone, shiftConfig) => {
3295
3295
  }
3296
3296
  return { shifts: legacyShifts, timezone: fallbackTimezone };
3297
3297
  };
3298
- var determineShiftFromDefinitions = (timezone, shifts, now4 = /* @__PURE__ */ new Date()) => {
3298
+ var determineActiveShiftFromDefinitions = (timezone, shifts, now4 = /* @__PURE__ */ new Date()) => {
3299
3299
  const zonedNow = dateFnsTz.toZonedTime(now4, timezone);
3300
3300
  const currentMinutes = zonedNow.getHours() * 60 + zonedNow.getMinutes();
3301
- let chosen;
3302
- let operationalDate = getOperationalDate(timezone, now4, shifts[0].startTime);
3303
3301
  for (const shift of shifts) {
3304
3302
  const start = parseTimeToMinutes(shift.startTime);
3305
3303
  const endRaw = parseTimeToMinutes(shift.endTime);
@@ -3307,32 +3305,47 @@ var determineShiftFromDefinitions = (timezone, shifts, now4 = /* @__PURE__ */ ne
3307
3305
  const wraps = end <= start;
3308
3306
  if (wraps) end += 1440;
3309
3307
  if (start <= currentMinutes && currentMinutes < end) {
3310
- chosen = shift;
3311
- operationalDate = getOperationalDate(timezone, now4, shift.startTime);
3312
- break;
3308
+ return {
3309
+ shiftId: shift.shiftId,
3310
+ shiftName: shift.shiftName,
3311
+ startTime: shift.startTime,
3312
+ endTime: shift.endTime,
3313
+ timezone,
3314
+ date: getOperationalDate(timezone, now4, shift.startTime)
3315
+ };
3313
3316
  }
3314
3317
  if (start <= currentMinutes + 1440 && currentMinutes + 1440 < end) {
3315
- chosen = shift;
3316
- operationalDate = getOperationalDate(timezone, now4, shift.startTime);
3317
- break;
3318
+ return {
3319
+ shiftId: shift.shiftId,
3320
+ shiftName: shift.shiftName,
3321
+ startTime: shift.startTime,
3322
+ endTime: shift.endTime,
3323
+ timezone,
3324
+ date: getOperationalDate(timezone, now4, shift.startTime)
3325
+ };
3318
3326
  }
3319
3327
  }
3320
- if (!chosen) {
3321
- chosen = shifts[0];
3322
- operationalDate = getOperationalDate(timezone, now4, chosen.startTime);
3328
+ return null;
3329
+ };
3330
+ var getCurrentShift = (timezone, shiftConfig, now4 = /* @__PURE__ */ new Date()) => {
3331
+ const { shifts, timezone: effectiveTz } = normalizeShiftDefinitions(timezone, shiftConfig);
3332
+ const activeShift = determineActiveShiftFromDefinitions(effectiveTz, shifts, now4);
3333
+ if (activeShift) {
3334
+ return activeShift;
3323
3335
  }
3336
+ const fallbackShift = shifts[0];
3324
3337
  return {
3325
- shiftId: chosen.shiftId,
3326
- shiftName: chosen.shiftName,
3327
- startTime: chosen.startTime,
3328
- endTime: chosen.endTime,
3329
- timezone,
3330
- date: operationalDate
3338
+ shiftId: fallbackShift.shiftId,
3339
+ shiftName: fallbackShift.shiftName,
3340
+ startTime: fallbackShift.startTime,
3341
+ endTime: fallbackShift.endTime,
3342
+ timezone: effectiveTz,
3343
+ date: getOperationalDate(effectiveTz, now4, fallbackShift.startTime)
3331
3344
  };
3332
3345
  };
3333
- var getCurrentShift = (timezone, shiftConfig, now4 = /* @__PURE__ */ new Date()) => {
3346
+ var getActiveShift = (timezone, shiftConfig, now4 = /* @__PURE__ */ new Date()) => {
3334
3347
  const { shifts, timezone: effectiveTz } = normalizeShiftDefinitions(timezone, shiftConfig);
3335
- return determineShiftFromDefinitions(effectiveTz, shifts, now4);
3348
+ return determineActiveShiftFromDefinitions(effectiveTz, shifts, now4);
3336
3349
  };
3337
3350
  var isTransitionPeriod = (timezone, shiftConfig, now4 = /* @__PURE__ */ new Date()) => {
3338
3351
  const transitionMinutes = shiftConfig?.transitionPeriodMinutes ?? DEFAULT_TRANSITION_MINUTES;
@@ -3997,7 +4010,8 @@ var dashboardService = {
3997
4010
  enable,
3998
4011
  monitoring_mode,
3999
4012
  assembly,
4000
- video_grid_metric_mode
4013
+ video_grid_metric_mode,
4014
+ recent_flow_window_minutes
4001
4015
  `).eq("enable", true);
4002
4016
  if (companyId) {
4003
4017
  query = query.eq("company_id", companyId);
@@ -4022,7 +4036,8 @@ var dashboardService = {
4022
4036
  video_grid_metric_mode: normalizeVideoGridMetricMode(
4023
4037
  line.video_grid_metric_mode,
4024
4038
  line.assembly ?? false
4025
- )
4039
+ ),
4040
+ recent_flow_window_minutes: line.recent_flow_window_minutes ?? 7
4026
4041
  }));
4027
4042
  return transformedLines;
4028
4043
  } catch (err) {
@@ -4059,7 +4074,7 @@ var dashboardService = {
4059
4074
  }
4060
4075
  const lineIdsToQuery = configuredLineIds;
4061
4076
  const [line1Result, metricsResult2] = await Promise.all([
4062
- supabase.from(linesTable).select("id, line_name, factory_id, monitoring_mode, assembly, video_grid_metric_mode, factories!lines_factory_id_fkey(factory_name), company_id, companies!lines_company_id_fkey(company_name:name)").eq("id", defaultLineId).single(),
4077
+ supabase.from(linesTable).select("id, line_name, factory_id, monitoring_mode, assembly, video_grid_metric_mode, recent_flow_window_minutes, factories!lines_factory_id_fkey(factory_name), company_id, companies!lines_company_id_fkey(company_name:name)").eq("id", defaultLineId).single(),
4063
4078
  supabase.from(lineMetricsTable).select("*").in("line_id", lineIdsToQuery).eq("shift_id", queryShiftId).eq("date", queryDate)
4064
4079
  ]);
4065
4080
  if (line1Result.error) throw line1Result.error;
@@ -4115,6 +4130,7 @@ var dashboardService = {
4115
4130
  date: queryDate,
4116
4131
  shift_id: queryShiftId,
4117
4132
  monitoring_mode: line1Data.monitoring_mode ?? void 0,
4133
+ recent_flow_window_minutes: line1Data.recent_flow_window_minutes ?? 7,
4118
4134
  metrics: {
4119
4135
  avg_efficiency: avgEfficiency,
4120
4136
  avg_cycle_time: combinedMetricsData.avg_cycle_time / numLines,
@@ -4138,7 +4154,7 @@ var dashboardService = {
4138
4154
  throw new Error("Company ID must be configured for detailed line requests.");
4139
4155
  }
4140
4156
  const [lineResult, metricsResult] = await Promise.all([
4141
- supabase.from(linesTable).select("id, line_name, factory_id, monitoring_mode, assembly, video_grid_metric_mode, factories!lines_factory_id_fkey(factory_name), company_id, companies!lines_company_id_fkey(company_name:name)").eq("id", lineIdToQuery).single(),
4157
+ supabase.from(linesTable).select("id, line_name, factory_id, monitoring_mode, assembly, video_grid_metric_mode, recent_flow_window_minutes, factories!lines_factory_id_fkey(factory_name), company_id, companies!lines_company_id_fkey(company_name:name)").eq("id", lineIdToQuery).single(),
4142
4158
  supabase.from(lineMetricsTable).select("*").eq("line_id", lineIdToQuery).eq("shift_id", queryShiftId).eq("date", queryDate)
4143
4159
  ]);
4144
4160
  if (lineResult.error) throw lineResult.error;
@@ -4163,6 +4179,7 @@ var dashboardService = {
4163
4179
  date: queryDate,
4164
4180
  shift_id: queryShiftId,
4165
4181
  monitoring_mode: lineData.monitoring_mode ?? void 0,
4182
+ recent_flow_window_minutes: lineData.recent_flow_window_minutes ?? 7,
4166
4183
  metrics: {
4167
4184
  avg_efficiency: metrics2?.avg_efficiency ?? 0,
4168
4185
  avg_cycle_time: metrics2?.avg_cycle_time || 0,
@@ -8496,7 +8513,8 @@ var LinesService = class {
8496
8513
  videoGridMetricMode: normalizeVideoGridMetricMode(
8497
8514
  line.video_grid_metric_mode,
8498
8515
  line.assembly ?? false
8499
- )
8516
+ ),
8517
+ recentFlowWindowMinutes: line.recent_flow_window_minutes ?? 7
8500
8518
  }));
8501
8519
  } catch (error) {
8502
8520
  console.error("Error fetching lines:", error);
@@ -8544,7 +8562,8 @@ var LinesService = class {
8544
8562
  videoGridMetricMode: normalizeVideoGridMetricMode(
8545
8563
  line.video_grid_metric_mode,
8546
8564
  line.assembly ?? false
8547
- )
8565
+ ),
8566
+ recentFlowWindowMinutes: line.recent_flow_window_minutes ?? 7
8548
8567
  }));
8549
8568
  } catch (error) {
8550
8569
  console.error("Error fetching all lines:", error);
@@ -8601,7 +8620,8 @@ var LinesService = class {
8601
8620
  videoGridMetricMode: normalizeVideoGridMetricMode(
8602
8621
  data.video_grid_metric_mode,
8603
8622
  data.assembly ?? false
8604
- )
8623
+ ),
8624
+ recentFlowWindowMinutes: data.recent_flow_window_minutes ?? 7
8605
8625
  };
8606
8626
  } catch (error) {
8607
8627
  console.error("Error fetching line:", error);
@@ -13355,6 +13375,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13355
13375
  actionName: item.action_name
13356
13376
  }),
13357
13377
  recent_flow_percent: item.recent_flow_percent ?? null,
13378
+ recent_flow_window_minutes: item.recent_flow_window_minutes ?? null,
13358
13379
  recent_flow_effective_end_at: item.recent_flow_effective_end_at ?? null,
13359
13380
  recent_flow_computed_at: item.recent_flow_computed_at ?? null,
13360
13381
  incoming_wip_current: item.incoming_wip_current ?? null,
@@ -32203,6 +32224,7 @@ var LineChartComponent = ({
32203
32224
  xAxisLabel,
32204
32225
  xAxisTickFormatter,
32205
32226
  // Pass through for X-axis tick formatting
32227
+ xAxisInterval,
32206
32228
  yAxisLabel,
32207
32229
  yAxisUnit,
32208
32230
  yAxisDomain,
@@ -32285,6 +32307,7 @@ var LineChartComponent = ({
32285
32307
  dataKey: xAxisDataKey,
32286
32308
  label: xAxisLabel ? { value: xAxisLabel, position: "insideBottom", offset: -10 } : void 0,
32287
32309
  tickFormatter: xAxisTickFormatter,
32310
+ interval: xAxisInterval,
32288
32311
  tick: { fontSize: 12, fill: axisTickFillColor },
32289
32312
  stroke: axisStrokeColor
32290
32313
  }
@@ -32609,6 +32632,212 @@ var CycleTimeChart = React141__namespace.default.memo(CycleTimeChartComponent, (
32609
32632
  });
32610
32633
  });
32611
32634
  CycleTimeChart.displayName = "CycleTimeChart";
32635
+
32636
+ // src/lib/utils/hourlyIdle.ts
32637
+ var DEFAULT_SHIFT_DURATION = 11;
32638
+ var normalizeMinuteSeries = (idleTimeHourly) => {
32639
+ if (!idleTimeHourly || typeof idleTimeHourly !== "object") {
32640
+ return {};
32641
+ }
32642
+ return Object.fromEntries(
32643
+ Object.entries(idleTimeHourly).map(([key, value]) => {
32644
+ if (Array.isArray(value)) {
32645
+ return [key, value];
32646
+ }
32647
+ if (value && Array.isArray(value.values)) {
32648
+ return [key, value.values];
32649
+ }
32650
+ return [key, []];
32651
+ })
32652
+ );
32653
+ };
32654
+ var parseTimeString = (timeValue) => {
32655
+ const [hoursPart, minutesPart] = timeValue.split(":");
32656
+ const hour = Number.parseInt(hoursPart, 10);
32657
+ const minute = Number.parseInt(minutesPart ?? "0", 10);
32658
+ const safeHour = Number.isFinite(hour) ? hour : 0;
32659
+ const safeMinute = Number.isFinite(minute) ? minute : 0;
32660
+ return {
32661
+ hour: safeHour,
32662
+ minute: safeMinute,
32663
+ decimalHour: safeHour + safeMinute / 60
32664
+ };
32665
+ };
32666
+ var buildShiftLayout = ({
32667
+ shiftStart,
32668
+ shiftEnd
32669
+ }) => {
32670
+ const shiftStartTime = parseTimeString(shiftStart);
32671
+ if (!shiftEnd) {
32672
+ return {
32673
+ shiftDuration: DEFAULT_SHIFT_DURATION,
32674
+ shiftStartTime,
32675
+ shiftEndTime: null,
32676
+ hasPartialLastHour: false
32677
+ };
32678
+ }
32679
+ const shiftEndTime = parseTimeString(shiftEnd);
32680
+ let duration = shiftEndTime.decimalHour - shiftStartTime.decimalHour;
32681
+ if (duration <= 0) {
32682
+ duration += 24;
32683
+ }
32684
+ const hasPartialLastHour = shiftEndTime.minute > 0 && shiftEndTime.minute < 60;
32685
+ const shiftDuration = hasPartialLastHour ? Math.ceil(duration) : Math.round(duration);
32686
+ return {
32687
+ shiftDuration,
32688
+ shiftStartTime,
32689
+ shiftEndTime,
32690
+ hasPartialLastHour
32691
+ };
32692
+ };
32693
+ var formatCompactTime = (hour, minute) => {
32694
+ const period = hour >= 12 ? "PM" : "AM";
32695
+ const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
32696
+ if (minute === 0) {
32697
+ return `${hour12}${period}`;
32698
+ }
32699
+ return `${hour12}:${minute.toString().padStart(2, "0")}${period}`;
32700
+ };
32701
+ var formatFullTime = (hour, minute) => {
32702
+ const period = hour >= 12 ? "PM" : "AM";
32703
+ const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
32704
+ return `${hour12}:${minute.toString().padStart(2, "0")} ${period}`;
32705
+ };
32706
+ var getSlotTimeBounds = (hourIndex, layout2) => {
32707
+ const isLastHour = hourIndex === layout2.shiftDuration - 1;
32708
+ const startDecimalHour = layout2.shiftStartTime.decimalHour + hourIndex;
32709
+ const startHour = Math.floor(startDecimalHour) % 24;
32710
+ const startMinute = Math.round(startDecimalHour % 1 * 60);
32711
+ let endHour;
32712
+ let endMinute;
32713
+ if (isLastHour && layout2.shiftEndTime) {
32714
+ endHour = layout2.shiftEndTime.hour;
32715
+ endMinute = layout2.shiftEndTime.minute;
32716
+ } else {
32717
+ const endDecimalHour = startDecimalHour + 1;
32718
+ endHour = Math.floor(endDecimalHour) % 24;
32719
+ endMinute = Math.round(endDecimalHour % 1 * 60);
32720
+ }
32721
+ return {
32722
+ startHour,
32723
+ startMinute,
32724
+ endHour,
32725
+ endMinute
32726
+ };
32727
+ };
32728
+ var getIdleArrayForHour = (hourIndex, idleTimeHourly, layout2) => {
32729
+ const actualHour = (layout2.shiftStartTime.hour + hourIndex) % 24;
32730
+ const startMinute = layout2.shiftStartTime.minute;
32731
+ if (startMinute > 0) {
32732
+ if (hourIndex === 0) {
32733
+ const firstHourData = idleTimeHourly[actualHour.toString()] || [];
32734
+ const nextHourData2 = idleTimeHourly[((actualHour + 1) % 24).toString()] || [];
32735
+ return [
32736
+ ...firstHourData.slice(startMinute),
32737
+ ...nextHourData2.slice(0, startMinute)
32738
+ ];
32739
+ }
32740
+ if (hourIndex < layout2.shiftDuration - 1) {
32741
+ const currentHourData3 = idleTimeHourly[actualHour.toString()] || [];
32742
+ const nextHourData2 = idleTimeHourly[((actualHour + 1) % 24).toString()] || [];
32743
+ return [
32744
+ ...currentHourData3.slice(startMinute),
32745
+ ...nextHourData2.slice(0, startMinute)
32746
+ ];
32747
+ }
32748
+ if (layout2.hasPartialLastHour && layout2.shiftEndTime) {
32749
+ const currentHourData3 = idleTimeHourly[actualHour.toString()] || [];
32750
+ const nextHourData2 = idleTimeHourly[((actualHour + 1) % 24).toString()] || [];
32751
+ return [
32752
+ ...currentHourData3.slice(startMinute),
32753
+ ...nextHourData2.slice(0, layout2.shiftEndTime.minute)
32754
+ ];
32755
+ }
32756
+ const currentHourData2 = idleTimeHourly[actualHour.toString()] || [];
32757
+ const nextHourData = idleTimeHourly[((actualHour + 1) % 24).toString()] || [];
32758
+ return [
32759
+ ...currentHourData2.slice(startMinute),
32760
+ ...nextHourData.slice(0, startMinute)
32761
+ ];
32762
+ }
32763
+ const currentHourData = idleTimeHourly[actualHour.toString()] || [];
32764
+ if (hourIndex === layout2.shiftDuration - 1 && layout2.hasPartialLastHour && layout2.shiftEndTime) {
32765
+ return currentHourData.slice(0, layout2.shiftEndTime.minute);
32766
+ }
32767
+ return currentHourData;
32768
+ };
32769
+ var buildHourlyIdleSlots = ({
32770
+ idleTimeHourly,
32771
+ shiftStart,
32772
+ shiftEnd
32773
+ }) => {
32774
+ const normalizedIdleTimeHourly = normalizeMinuteSeries(idleTimeHourly);
32775
+ const layout2 = buildShiftLayout({ shiftStart, shiftEnd });
32776
+ return Array.from({ length: layout2.shiftDuration }, (_, hourIndex) => {
32777
+ const { startHour, startMinute, endHour, endMinute } = getSlotTimeBounds(hourIndex, layout2);
32778
+ const idleArray = getIdleArrayForHour(hourIndex, normalizedIdleTimeHourly, layout2);
32779
+ const idleMinutes = idleArray.filter((value) => value === 1 || value === "1").length;
32780
+ return {
32781
+ hourIndex,
32782
+ hour: `${formatCompactTime(startHour, startMinute)}-${formatCompactTime(endHour, endMinute)}`,
32783
+ timeRange: `${formatFullTime(startHour, startMinute)} - ${formatFullTime(endHour, endMinute)}`,
32784
+ idleMinutes,
32785
+ idleArray
32786
+ };
32787
+ });
32788
+ };
32789
+ var formatIdleTimestamp = ({
32790
+ shiftStart,
32791
+ hourIndex,
32792
+ minuteIndex
32793
+ }) => {
32794
+ const shiftStartTime = parseTimeString(shiftStart);
32795
+ const totalMinutes = (shiftStartTime.hour + hourIndex) * 60 + shiftStartTime.minute + minuteIndex;
32796
+ const hour = Math.floor(totalMinutes / 60) % 24;
32797
+ const minute = totalMinutes % 60;
32798
+ return formatFullTime(hour, minute);
32799
+ };
32800
+ var getHourlyIdlePeriods = ({
32801
+ idleArray,
32802
+ shiftStart,
32803
+ hourIndex
32804
+ }) => {
32805
+ if (!Array.isArray(idleArray) || idleArray.length === 0) {
32806
+ return [];
32807
+ }
32808
+ const periods = [];
32809
+ let currentRange = null;
32810
+ idleArray.forEach((value, minuteIndex) => {
32811
+ if (value === 1 || value === "1") {
32812
+ if (!currentRange) {
32813
+ currentRange = { start: minuteIndex, end: minuteIndex };
32814
+ } else {
32815
+ currentRange.end = minuteIndex;
32816
+ }
32817
+ } else if (value !== "x" && currentRange) {
32818
+ periods.push(currentRange);
32819
+ currentRange = null;
32820
+ }
32821
+ });
32822
+ if (currentRange) {
32823
+ periods.push(currentRange);
32824
+ }
32825
+ return periods.map((period) => ({
32826
+ start: period.start,
32827
+ end: period.end,
32828
+ duration: period.end - period.start + 1,
32829
+ startTime: formatIdleTimestamp({
32830
+ shiftStart,
32831
+ hourIndex,
32832
+ minuteIndex: period.start
32833
+ }),
32834
+ endTime: formatIdleTimestamp({
32835
+ shiftStart,
32836
+ hourIndex,
32837
+ minuteIndex: period.end + 1
32838
+ })
32839
+ }));
32840
+ };
32612
32841
  var CycleTimeOverTimeChart = ({
32613
32842
  data,
32614
32843
  idealCycleTime,
@@ -32618,7 +32847,8 @@ var CycleTimeOverTimeChart = ({
32618
32847
  datasetKey,
32619
32848
  className = "",
32620
32849
  showIdleTime = false,
32621
- idleTimeData = []
32850
+ idleTimeData = [],
32851
+ idleTimeSlots = []
32622
32852
  }) => {
32623
32853
  const MAX_DATA_POINTS = 40;
32624
32854
  const containerRef = React141__namespace.default.useRef(null);
@@ -32721,50 +32951,60 @@ var CycleTimeOverTimeChart = ({
32721
32951
  if (!visibleEntries.length) {
32722
32952
  return null;
32723
32953
  }
32724
- return /* @__PURE__ */ jsxRuntime.jsxs(
32725
- "div",
32726
- {
32727
- style: {
32728
- backgroundColor: "white",
32729
- border: "none",
32730
- borderRadius: "8px",
32731
- boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
32732
- padding: "8px 12px",
32733
- fontSize: "13px"
32734
- },
32735
- children: [
32736
- /* @__PURE__ */ jsxRuntime.jsx(
32737
- "div",
32738
- {
32739
- style: {
32740
- color: "#374151",
32741
- fontWeight: 600,
32742
- marginBottom: "4px"
32743
- },
32744
- children: payload[0]?.payload?.tooltip || ""
32745
- }
32746
- ),
32747
- visibleEntries.map((entry) => {
32748
- const numericValue = getNumericValue(entry.value);
32749
- if (numericValue === null) {
32750
- return null;
32751
- }
32752
- return /* @__PURE__ */ jsxRuntime.jsx(
32753
- "div",
32754
- {
32755
- style: {
32756
- color: "#4B5563",
32757
- padding: "2px 0"
32758
- },
32759
- children: entry.name === "idleMinutes" ? `Idle Time: ${numericValue.toFixed(0)} minutes` : `Cycle Time: ${numericValue.toFixed(1)} seconds`
32760
- },
32761
- `${entry.name}-${numericValue}`
32762
- );
32763
- })
32764
- ]
32765
- }
32766
- );
32767
- }, [getNumericValue]);
32954
+ const dataPoint = payload[0]?.payload || {};
32955
+ const idlePeriods = showIdleTime && typeof dataPoint.idleMinutes === "number" && dataPoint.idleMinutes > 0 ? getHourlyIdlePeriods({
32956
+ idleArray: dataPoint.idleArray,
32957
+ shiftStart,
32958
+ hourIndex: Number.isFinite(dataPoint.hourIndex) ? dataPoint.hourIndex : 0
32959
+ }) : [];
32960
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-xl shadow-xl border border-gray-100 p-4 min-w-[220px]", children: [
32961
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold text-gray-900 text-sm", children: dataPoint.timeRange || dataPoint.tooltip || "" }) }),
32962
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
32963
+ visibleEntries.map((entry) => {
32964
+ const numericValue = getNumericValue(entry.value);
32965
+ if (numericValue === null) {
32966
+ return null;
32967
+ }
32968
+ if (entry.name === "idleMinutes") {
32969
+ if (!showIdleTime) return null;
32970
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-100 pt-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
32971
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: "Idle Time" }),
32972
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-orange-600 text-sm", children: [
32973
+ numericValue.toFixed(0),
32974
+ " minutes"
32975
+ ] })
32976
+ ] }) }, `${entry.name}-${numericValue}`);
32977
+ }
32978
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
32979
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: "Cycle Time" }),
32980
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-gray-900 text-sm", children: [
32981
+ numericValue.toFixed(1),
32982
+ " seconds"
32983
+ ] })
32984
+ ] }, `${entry.name}-${numericValue}`);
32985
+ }),
32986
+ idlePeriods.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 bg-gray-50 rounded-lg p-2.5", children: [
32987
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium text-gray-700 text-xs mb-2", children: "Idle periods:" }),
32988
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idlePeriods.map((period, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-gray-600 flex items-center gap-2 text-xs", children: [
32989
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0" }),
32990
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: period.duration === 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
32991
+ period.startTime,
32992
+ " (",
32993
+ period.duration,
32994
+ " min)"
32995
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
32996
+ period.startTime,
32997
+ " - ",
32998
+ period.endTime,
32999
+ " (",
33000
+ period.duration,
33001
+ " mins)"
33002
+ ] }) })
33003
+ ] }, index)) })
33004
+ ] })
33005
+ ] })
33006
+ ] });
33007
+ }, [getNumericValue, shiftStart, showIdleTime]);
32768
33008
  const renderCycleDot = React141__namespace.default.useCallback((props) => {
32769
33009
  const { cx: cx2, cy, payload } = props;
32770
33010
  const cycleTime = getNumericValue(payload?.cycleTime);
@@ -32833,14 +33073,18 @@ var CycleTimeOverTimeChart = ({
32833
33073
  r: 4,
32834
33074
  fill: "#f59e0b",
32835
33075
  stroke: "#fff",
32836
- strokeWidth: 1
33076
+ strokeWidth: 1,
33077
+ style: {
33078
+ opacity: showIdleTime ? 1 : 0,
33079
+ transition: "opacity 0.3s ease-in-out"
33080
+ }
32837
33081
  }
32838
33082
  );
32839
- }, [getNumericValue]);
33083
+ }, [getNumericValue, showIdleTime]);
32840
33084
  const renderIdleActiveDot = React141__namespace.default.useCallback((props) => {
32841
33085
  const { cx: cx2, cy, payload } = props;
32842
33086
  const idleMinutes = getNumericValue(payload?.idleMinutes);
32843
- if (idleMinutes === null) {
33087
+ if (idleMinutes === null || !showIdleTime) {
32844
33088
  return /* @__PURE__ */ jsxRuntime.jsx("g", {});
32845
33089
  }
32846
33090
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -32854,25 +33098,40 @@ var CycleTimeOverTimeChart = ({
32854
33098
  strokeWidth: 2
32855
33099
  }
32856
33100
  );
32857
- }, [getNumericValue]);
33101
+ }, [getNumericValue, showIdleTime]);
32858
33102
  const chartData = React141__namespace.default.useMemo(() => Array.from({ length: DURATION }, (_, i) => {
32859
33103
  const cycleTime = getNumericValue(finalData[i]);
32860
- const idleMinutes = showIdleTime ? getNumericValue(idleTimeData[i]) : null;
33104
+ const useIdleSlots = idleTimeSlots.length > 0;
33105
+ const idleSlot = useIdleSlots ? idleTimeSlots[i] ?? null : null;
33106
+ const idleMinutes = useIdleSlots ? idleSlot?.idleMinutes ?? null : getNumericValue(idleTimeData[i]);
32861
33107
  return {
32862
33108
  timeIndex: i,
32863
33109
  label: formatTimeLabel(i),
32864
- tooltip: formatTooltipTime(i),
33110
+ tooltip: idleSlot?.timeRange || formatTooltipTime(i),
33111
+ timeRange: idleSlot?.timeRange || formatTooltipTime(i),
33112
+ hourIndex: idleSlot?.hourIndex ?? i,
32865
33113
  cycleTime,
32866
33114
  idleMinutes,
33115
+ idleArray: idleSlot?.idleArray || [],
32867
33116
  color: cycleTime !== null && cycleTime <= idealCycleTime ? "#00AB45" : "#E34329"
32868
33117
  };
32869
- }), [DURATION, finalData, showIdleTime, idleTimeData, idealCycleTime, getNumericValue]);
33118
+ }), [DURATION, finalData, idleTimeData, idleTimeSlots, idealCycleTime, getNumericValue]);
32870
33119
  const renderLegend = () => {
32871
- if (!showIdleTime) return null;
32872
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-start text-[10px] font-bold text-gray-500 mb-6 tracking-[0.05em] gap-5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
32873
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2.5 h-2.5 rounded-full bg-[#f59e0b]" }),
32874
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Idle Time (min)" })
32875
- ] }) });
33120
+ return /* @__PURE__ */ jsxRuntime.jsx(
33121
+ "div",
33122
+ {
33123
+ className: "flex items-center justify-start text-[10px] font-bold text-gray-500 mb-6 tracking-[0.05em] gap-5",
33124
+ style: {
33125
+ opacity: showIdleTime ? 1 : 0,
33126
+ pointerEvents: showIdleTime ? "auto" : "none",
33127
+ transition: "opacity 0.3s ease-in-out"
33128
+ },
33129
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
33130
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2.5 h-2.5 rounded-full bg-[#f59e0b]" }),
33131
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Idle Time (min)" })
33132
+ ] })
33133
+ }
33134
+ );
32876
33135
  };
32877
33136
  return /* @__PURE__ */ jsxRuntime.jsxs(
32878
33137
  "div",
@@ -32943,7 +33202,7 @@ var CycleTimeOverTimeChart = ({
32943
33202
  }
32944
33203
  }
32945
33204
  ),
32946
- showIdleTime && /* @__PURE__ */ jsxRuntime.jsx(
33205
+ /* @__PURE__ */ jsxRuntime.jsx(
32947
33206
  recharts.YAxis,
32948
33207
  {
32949
33208
  yAxisId: "idle",
@@ -32952,7 +33211,11 @@ var CycleTimeOverTimeChart = ({
32952
33211
  width: 35,
32953
33212
  domain: [0, 60],
32954
33213
  tickFormatter: (value) => `${value}m`,
32955
- tick: { fontSize: 11, fill: "#f59e0b" },
33214
+ tick: {
33215
+ fontSize: 11,
33216
+ fill: showIdleTime ? "#f59e0b" : "transparent",
33217
+ style: { transition: "fill 0.3s ease-in-out" }
33218
+ },
32956
33219
  axisLine: false,
32957
33220
  tickLine: false
32958
33221
  }
@@ -33000,7 +33263,7 @@ var CycleTimeOverTimeChart = ({
33000
33263
  },
33001
33264
  `${effectiveDatasetKey}:cycle`
33002
33265
  ),
33003
- showIdleTime && /* @__PURE__ */ jsxRuntime.jsx(
33266
+ /* @__PURE__ */ jsxRuntime.jsx(
33004
33267
  recharts.Line,
33005
33268
  {
33006
33269
  type: "monotone",
@@ -33015,7 +33278,11 @@ var CycleTimeOverTimeChart = ({
33015
33278
  isAnimationActive: true,
33016
33279
  animationBegin: 300,
33017
33280
  animationDuration: 1500,
33018
- animationEasing: "ease-out"
33281
+ animationEasing: "ease-out",
33282
+ style: {
33283
+ strokeOpacity: showIdleTime ? 1 : 0,
33284
+ transition: "stroke-opacity 0.3s ease-in-out"
33285
+ }
33019
33286
  },
33020
33287
  `${effectiveDatasetKey}:idle`
33021
33288
  )
@@ -33170,89 +33437,20 @@ var HourlyOutputChartComponent = ({
33170
33437
  shiftEnd,
33171
33438
  showIdleTime = false,
33172
33439
  idleTimeHourly,
33173
- idleTimeClips,
33174
- idleTimeClipClassifications,
33175
- shiftDate,
33176
- timezone,
33177
33440
  className = ""
33178
33441
  }) => {
33179
33442
  const containerRef = React141__namespace.default.useRef(null);
33180
33443
  const [containerReady, setContainerReady] = React141__namespace.default.useState(false);
33181
33444
  const [containerWidth, setContainerWidth] = React141__namespace.default.useState(0);
33182
- const getTimeFromTimeString2 = (timeStr) => {
33183
- const [hours, minutes] = timeStr.split(":");
33184
- const hour = parseInt(hours);
33185
- const minute = parseInt(minutes || "0");
33186
- const decimalHour = hour + minute / 60;
33187
- return { hour, minute, decimalHour };
33188
- };
33189
- const shiftStartTime = getTimeFromTimeString2(shiftStart);
33190
- React141__namespace.default.useMemo(() => {
33191
- if (!shiftDate || !timezone) return null;
33192
- const hour = shiftStartTime.hour.toString().padStart(2, "0");
33193
- const minute = shiftStartTime.minute.toString().padStart(2, "0");
33194
- return dateFnsTz.fromZonedTime(`${shiftDate}T${hour}:${minute}:00`, timezone);
33195
- }, [shiftDate, timezone, shiftStartTime.hour, shiftStartTime.minute]);
33196
- const idleClipRanges = React141__namespace.default.useMemo(() => {
33197
- if (!idleTimeClips || idleTimeClips.length === 0) return [];
33198
- return idleTimeClips.map((clip) => ({
33199
- id: clip.id,
33200
- start: clip.idle_start_time ? new Date(clip.idle_start_time) : null,
33201
- end: clip.idle_end_time ? new Date(clip.idle_end_time) : null
33202
- })).filter((clip) => clip.start && clip.end);
33203
- }, [idleTimeClips]);
33204
- React141__namespace.default.useCallback((rangeStart, rangeEnd) => {
33205
- if (!rangeStart || !rangeEnd || idleClipRanges.length === 0) {
33206
- return "Reason unavailable";
33207
- }
33208
- const matchingClip = idleClipRanges.find((clip) => rangeStart >= clip.start && rangeEnd <= clip.end) || idleClipRanges.find((clip) => rangeStart < clip.end && rangeEnd > clip.start);
33209
- if (!matchingClip) {
33210
- return "Reason unavailable";
33211
- }
33212
- const classification = idleTimeClipClassifications?.[matchingClip.id];
33213
- if (!classification || classification.status === "processing") {
33214
- return "Analyzing...";
33215
- }
33216
- if (!classification.label) {
33217
- return "Reason unavailable";
33218
- }
33219
- return classification.displayName || classification.label.replace(/_/g, " ");
33220
- }, [idleClipRanges, idleTimeClipClassifications]);
33221
- const { shiftDuration, shiftEndTime, hasPartialLastHour } = React141__namespace.default.useMemo(() => {
33222
- console.log("[HourlyOutputChart] Calculating shift duration with:", {
33445
+ const idleSlots = React141__namespace.default.useMemo(
33446
+ () => buildHourlyIdleSlots({
33447
+ idleTimeHourly,
33223
33448
  shiftStart,
33224
- shiftEnd,
33225
- shiftStartTime
33226
- });
33227
- if (!shiftEnd) {
33228
- console.log("[HourlyOutputChart] No shiftEnd provided, using default 11 hours");
33229
- return {
33230
- shiftDuration: 11,
33231
- shiftEndTime: null,
33232
- hasPartialLastHour: false
33233
- };
33234
- }
33235
- const endTime = getTimeFromTimeString2(shiftEnd);
33236
- let duration = endTime.decimalHour - shiftStartTime.decimalHour;
33237
- if (duration <= 0) {
33238
- duration += 24;
33239
- }
33240
- const hasPartial = endTime.minute > 0 && endTime.minute < 60;
33241
- const hourCount = hasPartial ? Math.ceil(duration) : Math.round(duration);
33242
- console.log("[HourlyOutputChart] Shift calculation results:", {
33243
- endTime,
33244
- duration,
33245
- hasPartial,
33246
- hourCount
33247
- });
33248
- return {
33249
- shiftDuration: hourCount,
33250
- shiftEndTime: endTime,
33251
- hasPartialLastHour: hasPartial
33252
- };
33253
- }, [shiftEnd, shiftStartTime.decimalHour]);
33254
- const SHIFT_DURATION = shiftDuration;
33255
- shiftEndTime ? shiftEndTime.hour : (shiftStartTime.hour + SHIFT_DURATION) % 24;
33449
+ shiftEnd
33450
+ }),
33451
+ [idleTimeHourly, shiftStart, shiftEnd]
33452
+ );
33453
+ const SHIFT_DURATION = idleSlots.length;
33256
33454
  const [animatedData, setAnimatedData] = React141__namespace.default.useState(
33257
33455
  () => Array(SHIFT_DURATION).fill(0)
33258
33456
  );
@@ -33375,121 +33573,22 @@ var HourlyOutputChartComponent = ({
33375
33573
  }
33376
33574
  return { interval: 0, angle: -30, height: 64, tickFont: 9, tickMargin: 6 };
33377
33575
  }, [containerWidth]);
33378
- const formatHour = React141__namespace.default.useCallback((hourIndex) => {
33379
- const isLastHour = hourIndex === SHIFT_DURATION - 1;
33380
- const startDecimalHour = shiftStartTime.decimalHour + hourIndex;
33381
- const startHour = Math.floor(startDecimalHour) % 24;
33382
- const startMinute = Math.round(startDecimalHour % 1 * 60);
33383
- let endHour, endMinute;
33384
- if (isLastHour && shiftEndTime) {
33385
- endHour = shiftEndTime.hour;
33386
- endMinute = shiftEndTime.minute;
33387
- } else {
33388
- const endDecimalHour = startDecimalHour + 1;
33389
- endHour = Math.floor(endDecimalHour) % 24;
33390
- endMinute = Math.round(endDecimalHour % 1 * 60);
33391
- }
33392
- const formatTime5 = (h, m) => {
33393
- const period = h >= 12 ? "PM" : "AM";
33394
- const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
33395
- if (m === 0) {
33396
- return `${hour12}${period}`;
33397
- }
33398
- return `${hour12}:${m.toString().padStart(2, "0")}${period}`;
33399
- };
33400
- return `${formatTime5(startHour, startMinute)}-${formatTime5(endHour, endMinute)}`;
33401
- }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
33402
- const formatTimeRange2 = React141__namespace.default.useCallback((hourIndex) => {
33403
- const isLastHour = hourIndex === SHIFT_DURATION - 1;
33404
- const startDecimalHour = shiftStartTime.decimalHour + hourIndex;
33405
- const startHour = Math.floor(startDecimalHour) % 24;
33406
- const startMinute = Math.round(startDecimalHour % 1 * 60);
33407
- let endHour, endMinute;
33408
- if (isLastHour && shiftEndTime) {
33409
- endHour = shiftEndTime.hour;
33410
- endMinute = shiftEndTime.minute;
33411
- } else {
33412
- const endDecimalHour = startDecimalHour + 1;
33413
- endHour = Math.floor(endDecimalHour) % 24;
33414
- endMinute = Math.round(endDecimalHour % 1 * 60);
33415
- }
33416
- const formatTime5 = (h, m) => {
33417
- const period = h >= 12 ? "PM" : "AM";
33418
- const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
33419
- return `${hour12}:${m.toString().padStart(2, "0")} ${period}`;
33420
- };
33421
- return `${formatTime5(startHour, startMinute)} - ${formatTime5(endHour, endMinute)}`;
33422
- }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
33423
33576
  const chartData = React141__namespace.default.useMemo(() => {
33424
33577
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
33425
- const actualHour = (shiftStartTime.hour + i) % 24;
33426
- const startMinute = shiftStartTime.minute;
33427
- let idleArray = [];
33428
- let idleMinutes = 0;
33429
- if (idleTimeHourly) {
33430
- if (startMinute > 0) {
33431
- if (i === 0) {
33432
- const firstHourData = idleTimeHourly[actualHour.toString()] || [];
33433
- const nextHour = (actualHour + 1) % 24;
33434
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33435
- idleArray = [
33436
- ...firstHourData.slice(startMinute) || [],
33437
- ...nextHourData.slice(0, startMinute) || []
33438
- ];
33439
- } else if (i < SHIFT_DURATION - 1) {
33440
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33441
- const nextHour = (actualHour + 1) % 24;
33442
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33443
- idleArray = [
33444
- ...currentHourData.slice(startMinute) || [],
33445
- ...nextHourData.slice(0, startMinute) || []
33446
- ];
33447
- } else {
33448
- const hasPartialLastHour2 = shiftEndTime && shiftEndTime.minute > 0 && shiftEndTime.minute < 60;
33449
- if (hasPartialLastHour2) {
33450
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33451
- const nextHour = (actualHour + 1) % 24;
33452
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33453
- if (startMinute > 0) {
33454
- const firstPart = currentHourData.slice(startMinute) || [];
33455
- const secondPart = nextHourData.slice(0, shiftEndTime.minute) || [];
33456
- idleArray = [...firstPart, ...secondPart];
33457
- } else {
33458
- idleArray = currentHourData.slice(0, shiftEndTime.minute) || [];
33459
- }
33460
- } else {
33461
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33462
- const nextHour = (actualHour + 1) % 24;
33463
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33464
- idleArray = [
33465
- ...currentHourData.slice(startMinute) || [],
33466
- ...nextHourData.slice(0, startMinute) || []
33467
- ];
33468
- }
33469
- }
33470
- } else {
33471
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33472
- if (i === SHIFT_DURATION - 1 && shiftEndTime && shiftEndTime.minute > 0 && shiftEndTime.minute < 60) {
33473
- idleArray = currentHourData.slice(0, shiftEndTime.minute) || [];
33474
- } else {
33475
- idleArray = currentHourData || [];
33476
- }
33477
- }
33478
- }
33479
- idleMinutes = idleArray.filter((val) => val === "1" || val === 1).length;
33578
+ const idleSlot = idleSlots[i];
33480
33579
  return {
33481
- hourIndex: i,
33482
- hour: formatHour(i),
33483
- timeRange: formatTimeRange2(i),
33580
+ hourIndex: idleSlot?.hourIndex ?? i,
33581
+ hour: idleSlot?.hour || "",
33582
+ timeRange: idleSlot?.timeRange || "",
33484
33583
  output: animatedData[i] || 0,
33485
33584
  originalOutput: data[i] || 0,
33486
33585
  // Keep original data for labels
33487
33586
  color: (animatedData[i] || 0) >= Math.round(pphThreshold) ? "#00AB45" : "#E34329",
33488
- idleMinutes,
33489
- idleArray
33587
+ idleMinutes: idleSlot?.idleMinutes || 0,
33588
+ idleArray: idleSlot?.idleArray || []
33490
33589
  };
33491
33590
  });
33492
- }, [animatedData, data, pphThreshold, idleTimeHourly, shiftStartTime.hour, shiftStartTime.minute, shiftEndTime, formatHour, formatTimeRange2, SHIFT_DURATION]);
33591
+ }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION]);
33493
33592
  const IdleBar = React141__namespace.default.useMemo(() => {
33494
33593
  if (!idleBarState.visible) return null;
33495
33594
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -33651,36 +33750,11 @@ var HourlyOutputChartComponent = ({
33651
33750
  content: (props) => {
33652
33751
  if (!props.active || !props.payload || props.payload.length === 0) return null;
33653
33752
  const data2 = props.payload[0].payload;
33654
- const idleRanges = [];
33655
- if (showIdleTime && data2.idleArray) {
33656
- let currentRange = null;
33657
- data2.idleArray.forEach((val, idx) => {
33658
- if (val === "1" || val === 1) {
33659
- if (!currentRange) {
33660
- currentRange = { start: idx, end: idx };
33661
- } else {
33662
- currentRange.end = idx;
33663
- }
33664
- } else if (val !== "x" && currentRange) {
33665
- idleRanges.push(currentRange);
33666
- currentRange = null;
33667
- }
33668
- });
33669
- if (currentRange) {
33670
- idleRanges.push(currentRange);
33671
- }
33672
- }
33673
- const formatIdleTimestamp = (minuteIdx) => {
33674
- const fallbackIndex = chartData.findIndex((item) => item.timeRange === data2.timeRange);
33675
- const hourOffset = Number.isFinite(data2.hourIndex) ? data2.hourIndex : fallbackIndex;
33676
- const safeHourOffset = hourOffset >= 0 ? hourOffset : 0;
33677
- const totalMinutes = (shiftStartTime.hour + safeHourOffset) * 60 + shiftStartTime.minute + minuteIdx;
33678
- const hour = Math.floor(totalMinutes / 60) % 24;
33679
- const minute = totalMinutes % 60;
33680
- const period = hour >= 12 ? "PM" : "AM";
33681
- const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
33682
- return `${hour12}:${minute.toString().padStart(2, "0")} ${period}`;
33683
- };
33753
+ const idlePeriods = showIdleTime ? getHourlyIdlePeriods({
33754
+ idleArray: data2.idleArray,
33755
+ shiftStart,
33756
+ hourIndex: Number.isFinite(data2.hourIndex) ? data2.hourIndex : 0
33757
+ }) : [];
33684
33758
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-xl shadow-xl border border-gray-100 p-4 min-w-[220px]", children: [
33685
33759
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold text-gray-900 text-sm", children: data2.timeRange }) }),
33686
33760
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
@@ -33699,27 +33773,22 @@ var HourlyOutputChartComponent = ({
33699
33773
  " minutes"
33700
33774
  ] })
33701
33775
  ] }) }),
33702
- idleRanges.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 bg-gray-50 rounded-lg p-2.5", children: [
33776
+ idlePeriods.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 bg-gray-50 rounded-lg p-2.5", children: [
33703
33777
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium text-gray-700 text-xs mb-2", children: "Idle periods:" }),
33704
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idleRanges.map((range, index) => {
33705
- const duration = range.end - range.start + 1;
33706
- const startTime = formatIdleTimestamp(range.start);
33707
- const endTime = formatIdleTimestamp(range.end + 1);
33708
- const fallbackIndex = chartData.findIndex((item) => item.timeRange === data2.timeRange);
33709
- Number.isFinite(data2.hourIndex) ? data2.hourIndex : fallbackIndex;
33778
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idlePeriods.map((period, index) => {
33710
33779
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-gray-600 flex items-center gap-2 text-xs", children: [
33711
33780
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0" }),
33712
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: duration === 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
33713
- startTime,
33781
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: period.duration === 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
33782
+ period.startTime,
33714
33783
  " (",
33715
- duration,
33784
+ period.duration,
33716
33785
  " min)"
33717
33786
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
33718
- startTime,
33787
+ period.startTime,
33719
33788
  " - ",
33720
- endTime,
33789
+ period.endTime,
33721
33790
  " (",
33722
- duration,
33791
+ period.duration,
33723
33792
  " mins)"
33724
33793
  ] }) })
33725
33794
  ] }, index);
@@ -33874,9 +33943,8 @@ var HourlyOutputChart = React141__namespace.default.memo(HourlyOutputChartCompon
33874
33943
  HourlyOutputChart.displayName = "HourlyOutputChart";
33875
33944
 
33876
33945
  // src/components/dashboard/grid/videoGridMetricUtils.ts
33877
- var VIDEO_GRID_LEGEND_LABEL = "7 Minute Efficiency";
33878
33946
  var MAP_GRID_LEGEND_LABEL = "Efficiency";
33879
- var MIXED_VIDEO_GRID_LEGEND_LABEL = "Efficiency";
33947
+ var MIXED_VIDEO_GRID_LEGEND_LABEL = "Flow Efficiency";
33880
33948
  var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
33881
33949
  var isVideoGridRecentFlowEnabled = (workspace) => isRecentFlowVideoGridMetricMode(
33882
33950
  workspace.video_grid_metric_mode,
@@ -33975,8 +34043,15 @@ var getVideoGridLegendLabel = (workspaces) => {
33975
34043
  if (recentFlowEnabledCount === 0) {
33976
34044
  return MAP_GRID_LEGEND_LABEL;
33977
34045
  }
34046
+ const recentFlowWindows = new Set(
34047
+ visibleWorkspaces.filter(isVideoGridRecentFlowEnabled).map((workspace) => workspace.recent_flow_window_minutes ?? 7).filter((value) => typeof value === "number" && Number.isFinite(value))
34048
+ );
33978
34049
  if (recentFlowEnabledCount === visibleWorkspaces.length) {
33979
- return VIDEO_GRID_LEGEND_LABEL;
34050
+ if (recentFlowWindows.size === 1) {
34051
+ const [windowMinutes] = Array.from(recentFlowWindows);
34052
+ return `${windowMinutes} Minute Efficiency`;
34053
+ }
34054
+ return MIXED_VIDEO_GRID_LEGEND_LABEL;
33980
34055
  }
33981
34056
  return MIXED_VIDEO_GRID_LEGEND_LABEL;
33982
34057
  };
@@ -34028,8 +34103,9 @@ var VideoCard = React141__namespace.default.memo(({
34028
34103
  const isRecentFlowCard = isVideoGridRecentFlowEnabled(workspace);
34029
34104
  const hasDisplayMetric = typeof videoGridDisplayValue === "number" && Number.isFinite(videoGridDisplayValue);
34030
34105
  const hasBarMetric = typeof videoGridMetricValue === "number" && Number.isFinite(videoGridMetricValue);
34106
+ const shouldRenderMetricBadge = hasDisplayMetric;
34031
34107
  const badgeTitle = hasVideoGridRecentFlow(workspace) ? `Flow ${Math.round(videoGridDisplayValue ?? 0)}%` : isRecentFlowCard ? "Flow unavailable" : `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%`;
34032
- const badgeLabel = hasDisplayMetric ? `${Math.round(videoGridDisplayValue)}%` : "X";
34108
+ const badgeLabel = `${Math.round(videoGridDisplayValue ?? 0)}%`;
34033
34109
  const efficiencyOverlayClass = videoGridColorState === "green" ? "bg-[#00D654]/25" : videoGridColorState === "yellow" ? "bg-[#FFD700]/30" : videoGridColorState === "red" ? "bg-[#FF2D0A]/30" : "bg-transparent";
34034
34110
  const efficiencyBarClass = videoGridColorState === "green" ? "bg-[#00AB45]" : videoGridColorState === "yellow" ? "bg-[#FFB020]" : videoGridColorState === "red" ? "bg-[#E34329]" : "bg-gray-500/70";
34035
34111
  const efficiencyStatus = videoGridColorState === "green" ? "High" : videoGridColorState === "yellow" ? "Medium" : videoGridColorState === "red" ? "Low" : "Neutral";
@@ -34105,7 +34181,7 @@ var VideoCard = React141__namespace.default.memo(({
34105
34181
  lastSeenText
34106
34182
  ] })
34107
34183
  ] }) }),
34108
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute ${compact ? "top-1 right-1" : "top-2 right-2"} z-30`, children: /* @__PURE__ */ jsxRuntime.jsx(
34184
+ shouldRenderMetricBadge && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute ${compact ? "top-1 right-1" : "top-2 right-2"} z-30`, children: /* @__PURE__ */ jsxRuntime.jsx(
34109
34185
  "div",
34110
34186
  {
34111
34187
  "data-testid": "video-card-metric-badge",
@@ -35922,7 +35998,7 @@ var HourlyUptimeChartComponent = ({
35922
35998
  idleRanges.push(currentRange);
35923
35999
  }
35924
36000
  }
35925
- const formatIdleTimestamp = (minuteIdx) => {
36001
+ const formatIdleTimestamp2 = (minuteIdx) => {
35926
36002
  const totalMinutes = (shiftStartTime.hour + data.hourIndex) * 60 + shiftStartTime.minute + minuteIdx;
35927
36003
  const hour = Math.floor(totalMinutes / 60) % 24;
35928
36004
  const minute = totalMinutes % 60;
@@ -35952,8 +36028,8 @@ var HourlyUptimeChartComponent = ({
35952
36028
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium text-gray-700 text-xs mb-2", children: "Idle periods:" }),
35953
36029
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idleRanges.map((range, index) => {
35954
36030
  const duration = range.end - range.start + 1;
35955
- const startTime = formatIdleTimestamp(range.start);
35956
- const endTime = formatIdleTimestamp(range.end + 1);
36031
+ const startTime = formatIdleTimestamp2(range.start);
36032
+ const endTime = formatIdleTimestamp2(range.end + 1);
35957
36033
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-gray-600 flex items-center gap-2 text-xs", children: [
35958
36034
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0" }),
35959
36035
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: duration === 1 ? `${startTime} (${duration} min)` : `${startTime} - ${endTime} (${duration} mins)` })
@@ -49832,8 +49908,7 @@ var WorkspaceCycleTimeMetricCards = ({
49832
49908
  {
49833
49909
  data: idleTimeData.chartData,
49834
49910
  isLoading: idleTimeData.isLoading,
49835
- error: idleTimeData.error,
49836
- variant: "bar"
49911
+ error: idleTimeData.error
49837
49912
  }
49838
49913
  ) })
49839
49914
  ] })
@@ -69201,56 +69276,43 @@ var WorkspaceDetailView = ({
69201
69276
  const canToggleChartIdleTime = !isUptimeMode && !shouldShowCycleTimeLoadingState && !shouldShowCycleTimeUnavailableState;
69202
69277
  const idleClipDate = date || workspace?.date || calculatedOperationalDate || getOperationalDate(timezone);
69203
69278
  const idleClipShiftId = parsedShiftId ?? workspace?.shift_id;
69204
- const rawHourlyIdleMinutes = React141.useMemo(() => {
69205
- if (!shouldShowCycleTimeChart || !workspace?.idle_time_hourly || !workspace?.shift_start) return [];
69206
- const parseTimeToMinutes4 = (time2) => {
69207
- const [h, m] = time2.split(":").map(Number);
69208
- if (!Number.isFinite(h) || !Number.isFinite(m)) return 0;
69209
- return h * 60 + m;
69210
- };
69211
- const startTotal = parseTimeToMinutes4(workspace.shift_start);
69212
- const endTotalRaw = workspace.shift_end ? parseTimeToMinutes4(workspace.shift_end) : startTotal + 11 * 60;
69213
- const endTotal = endTotalRaw <= startTotal ? endTotalRaw + 24 * 60 : endTotalRaw;
69214
- const shiftDuration = Math.max(60, endTotal - startTotal);
69215
- const totalHourSlots = Math.max(1, Math.ceil(shiftDuration / 60));
69216
- const idleTimeHourlyObj = workspace.idle_time_hourly || {};
69217
- const result = Array(totalHourSlots).fill(0);
69218
- for (let i = 0; i < totalHourSlots; i++) {
69219
- const startSlotMins = startTotal + i * 60;
69220
- let endSlotMins = startSlotMins + 60;
69221
- if (endSlotMins > endTotal) endSlotMins = endTotal;
69222
- let idleCount = 0;
69223
- for (let m = startSlotMins; m < endSlotMins; m++) {
69224
- const hourKey = Math.floor(m / 60) % 24;
69225
- const minuteKey = m % 60;
69226
- const hourData = idleTimeHourlyObj[hourKey.toString()] || [];
69227
- if (Array.isArray(hourData)) {
69228
- const val = hourData[minuteKey];
69229
- if (val === 1 || val === "1") {
69230
- idleCount++;
69231
- }
69232
- }
69233
- }
69234
- result[i] = idleCount;
69235
- }
69236
- return result;
69237
- }, [shouldShowCycleTimeChart, workspace?.idle_time_hourly, workspace?.shift_start, workspace?.shift_end]);
69279
+ const rawHourlyIdleSlots = React141.useMemo(
69280
+ () => !shouldShowCycleTimeChart || !authoritativeCycleMetrics?.shift_start ? [] : buildHourlyIdleSlots({
69281
+ idleTimeHourly: authoritativeCycleMetrics.idle_time_hourly,
69282
+ shiftStart: authoritativeCycleMetrics.shift_start,
69283
+ shiftEnd: authoritativeCycleMetrics.shift_end
69284
+ }),
69285
+ [
69286
+ shouldShowCycleTimeChart,
69287
+ authoritativeCycleMetrics?.idle_time_hourly,
69288
+ authoritativeCycleMetrics?.shift_start,
69289
+ authoritativeCycleMetrics?.shift_end
69290
+ ]
69291
+ );
69292
+ const rawHourlyIdleMinutes = React141.useMemo(
69293
+ () => rawHourlyIdleSlots.map((slot) => slot.idleMinutes),
69294
+ [rawHourlyIdleSlots]
69295
+ );
69238
69296
  const hourlyIdleMinutes = React141.useMemo(
69239
69297
  () => maskFutureHourlySeries({
69240
69298
  data: rawHourlyIdleMinutes,
69241
- shiftStart: workspace?.shift_start,
69242
- shiftEnd: workspace?.shift_end,
69299
+ shiftStart: authoritativeCycleMetrics?.shift_start,
69300
+ shiftEnd: authoritativeCycleMetrics?.shift_end,
69243
69301
  shiftDate: idleClipDate,
69244
69302
  timezone: effectiveCycleTimeTimezone
69245
69303
  }),
69246
69304
  [
69247
69305
  rawHourlyIdleMinutes,
69248
- workspace?.shift_start,
69249
- workspace?.shift_end,
69306
+ authoritativeCycleMetrics?.shift_start,
69307
+ authoritativeCycleMetrics?.shift_end,
69250
69308
  idleClipDate,
69251
69309
  effectiveCycleTimeTimezone
69252
69310
  ]
69253
69311
  );
69312
+ const hourlyIdleSlots = React141.useMemo(
69313
+ () => rawHourlyIdleSlots.map((slot, index) => hourlyIdleMinutes[index] === null ? null : slot),
69314
+ [rawHourlyIdleSlots, hourlyIdleMinutes]
69315
+ );
69254
69316
  const cycleTimeUnavailableView = React141.useMemo(() => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full h-full rounded-lg border border-amber-200 bg-amber-50/70 px-6 py-5 text-center flex flex-col items-center justify-center", children: [
69255
69317
  /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-base font-semibold text-amber-900", children: "Cycle data unavailable" }),
69256
69318
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 max-w-md text-sm text-amber-800", children: "This workstation has cycle-time metrics for the selected shift, but no matching `cycle_completion` clips were found for the chart." }),
@@ -69806,7 +69868,8 @@ var WorkspaceDetailView = ({
69806
69868
  xAxisMode: "hourly",
69807
69869
  datasetKey: cycleTimeDatasetKey,
69808
69870
  showIdleTime: showChartIdleTime,
69809
- idleTimeData: hourlyIdleMinutes
69871
+ idleTimeData: hourlyIdleMinutes,
69872
+ idleTimeSlots: hourlyIdleSlots
69810
69873
  }
69811
69874
  ) : null : /* @__PURE__ */ jsxRuntime.jsx(
69812
69875
  HourlyOutputChart2,
@@ -69935,7 +69998,8 @@ var WorkspaceDetailView = ({
69935
69998
  xAxisMode: "hourly",
69936
69999
  datasetKey: cycleTimeDatasetKey,
69937
70000
  showIdleTime: showChartIdleTime,
69938
- idleTimeData: hourlyIdleMinutes
70001
+ idleTimeData: hourlyIdleMinutes,
70002
+ idleTimeSlots: hourlyIdleSlots
69939
70003
  }
69940
70004
  ) : null : /* @__PURE__ */ jsxRuntime.jsx(
69941
70005
  HourlyOutputChart2,
@@ -76453,7 +76517,7 @@ var formatComparisonWindow = ({
76453
76517
  shiftMode
76454
76518
  }) => {
76455
76519
  if (comparisonStrategy === "previous_full_week") return "last week";
76456
- if (comparisonStrategy === "matched_range" && shiftMode !== "all" && currentDayCount === 1 && previousDayCount === 1) {
76520
+ if (comparisonStrategy === "matched_range" && currentDayCount === 1 && previousDayCount === 1) {
76457
76521
  return "previous day";
76458
76522
  }
76459
76523
  if (!previousDayCount || !Number.isFinite(previousDayCount)) return "previous range";
@@ -76476,8 +76540,14 @@ var buildDeltaBadge = (delta, options) => {
76476
76540
  };
76477
76541
  };
76478
76542
  var normalizeShiftLabel = (shiftName, shiftMode) => {
76543
+ if (shiftMode === "all") {
76544
+ return "All Shifts";
76545
+ }
76479
76546
  const trimmedName = shiftName?.trim();
76480
76547
  if (trimmedName) {
76548
+ const normalizedName = trimmedName.toLowerCase();
76549
+ if (normalizedName === "day") return "Day Shift";
76550
+ if (normalizedName === "night") return "Night Shift";
76481
76551
  return /shift/i.test(trimmedName) ? trimmedName : `${trimmedName} Shift`;
76482
76552
  }
76483
76553
  if (shiftMode === "night") return "Night Shift";
@@ -76486,6 +76556,9 @@ var normalizeShiftLabel = (shiftName, shiftMode) => {
76486
76556
  var getShiftIcon = (shiftName, shiftMode) => {
76487
76557
  const normalizedName = (shiftName || "").toLowerCase();
76488
76558
  const normalizedMode = shiftMode || "day";
76559
+ if (normalizedMode === "all") {
76560
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) });
76561
+ }
76489
76562
  if (normalizedName.includes("day") || normalizedName.includes("morning") || normalizedMode === "day") {
76490
76563
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" }) });
76491
76564
  }
@@ -76564,6 +76637,7 @@ var OperationsOverviewHeader = React141__namespace.default.memo(({
76564
76637
  }) => {
76565
76638
  bumpRenderCounter();
76566
76639
  const subtitleRange = displayDateRange || dateRange;
76640
+ const showLiveShiftMeta = isLiveScope && trendMode !== "all";
76567
76641
  const liveShiftLabel = React141__namespace.default.useMemo(
76568
76642
  () => normalizeShiftLabel(liveShiftName, trendMode),
76569
76643
  [liveShiftName, trendMode]
@@ -76701,8 +76775,8 @@ var OperationsOverviewHeader = React141__namespace.default.memo(({
76701
76775
  ] }),
76702
76776
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex flex-wrap items-center justify-center gap-x-2 gap-y-1 text-[11px] font-medium text-slate-500 min-w-0", children: [
76703
76777
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-center", children: mobileSubtitle }),
76704
- isLiveScope ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-300", children: "|" }) : null,
76705
- isLiveScope ? /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "operations-overview-live-meta", className: "inline-flex items-center gap-1.5 text-gray-600 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 justify-center truncate", children: [
76778
+ showLiveShiftMeta ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-300", children: "|" }) : null,
76779
+ showLiveShiftMeta ? /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "operations-overview-live-meta", className: "inline-flex items-center gap-1.5 text-gray-600 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 justify-center truncate", children: [
76706
76780
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-75 shrink-0", children: liveShiftIcon }),
76707
76781
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: liveShiftLabel })
76708
76782
  ] }) }) : null
@@ -76749,7 +76823,7 @@ var OperationsOverviewHeader = React141__namespace.default.memo(({
76749
76823
  ] }),
76750
76824
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center justify-center gap-x-3 gap-y-1 text-xs sm:text-sm font-medium text-slate-500 text-center", children: [
76751
76825
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: desktopSubtitle }),
76752
- isLiveScope ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
76826
+ showLiveShiftMeta ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
76753
76827
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-300", children: "|" }),
76754
76828
  /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "operations-overview-live-meta", className: "inline-flex items-center gap-1.5 text-gray-600", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1.5 justify-center", children: [
76755
76829
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-75", children: liveShiftIcon }),
@@ -77323,18 +77397,18 @@ var EfficiencyTrendCard = React141__namespace.default.memo(({
77323
77397
  return (trend.data.points || []).map((point, index) => ({
77324
77398
  name: (() => {
77325
77399
  const rawLabel = point.label?.trim() || "";
77326
- if (!hourlyLabelStartTime) {
77327
- return rawLabel || `Hour ${index + 1}`;
77328
- }
77329
- if (rawLabel && !/^Hour\s+\d+$/i.test(rawLabel)) {
77400
+ if (rawLabel) {
77330
77401
  return rawLabel;
77331
77402
  }
77403
+ if (!hourlyLabelStartTime) {
77404
+ return "";
77405
+ }
77332
77406
  const hourIndex = typeof point.hour_index === "number" ? point.hour_index : index;
77333
77407
  const [hoursPart, minutesPart] = hourlyLabelStartTime.split(":");
77334
77408
  const startHours = Number(hoursPart);
77335
77409
  const startMinutes = Number(minutesPart);
77336
77410
  if (!Number.isFinite(startHours) || !Number.isFinite(startMinutes)) {
77337
- return rawLabel || `Hour ${index + 1}`;
77411
+ return "";
77338
77412
  }
77339
77413
  const totalMinutes = startHours * 60 + startMinutes + hourIndex * 60;
77340
77414
  const hour24 = Math.floor(totalMinutes / 60) % 24;
@@ -77396,6 +77470,12 @@ var EfficiencyTrendCard = React141__namespace.default.memo(({
77396
77470
  if (!dayOfWeek || typeof label !== "string") return label;
77397
77471
  return `${label} (${dayOfWeek})`;
77398
77472
  }, [isHourlyTrend]);
77473
+ const trendXAxisTickFormatter = React141__namespace.default.useCallback((value, index) => {
77474
+ if (!isHourlyTrend) {
77475
+ return typeof value === "string" ? value : String(value ?? "");
77476
+ }
77477
+ return index % 2 === 0 ? typeof value === "string" ? value : String(value ?? "") : "";
77478
+ }, [isHourlyTrend]);
77399
77479
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-xl shadow-[0_2px_10px_-3px_rgba(6,81,237,0.1)] border border-slate-100 flex flex-col overflow-hidden text-left", children: [
77400
77480
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-5 flex-none flex justify-between items-center border-b border-slate-50/50", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-gray-700", children: "Efficiency Trend" }) }),
77401
77481
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[250px] w-full p-4 pt-4 relative", children: showInitialSkeleton ? /* @__PURE__ */ jsxRuntime.jsx(OverviewChartSkeleton, {}) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 pb-2 pr-4 pl-1", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -77404,6 +77484,8 @@ var EfficiencyTrendCard = React141__namespace.default.memo(({
77404
77484
  data: trendData,
77405
77485
  lines: efficiencyLineConfig,
77406
77486
  xAxisDataKey: "name",
77487
+ xAxisInterval: isHourlyTrend ? 0 : void 0,
77488
+ xAxisTickFormatter: trendXAxisTickFormatter,
77407
77489
  yAxisUnit: "%",
77408
77490
  yAxisDomain: [0, 100],
77409
77491
  showLegend: false,
@@ -77909,6 +77991,62 @@ var classifyShiftBucket = ({
77909
77991
  if (normalizedShiftId === 1) return "night";
77910
77992
  return null;
77911
77993
  };
77994
+ var getShiftWindowsForConfig = (shiftConfig, timezone) => {
77995
+ if (shiftConfig?.shifts && shiftConfig.shifts.length > 0) {
77996
+ return shiftConfig.shifts.map((shift) => ({
77997
+ shiftId: shift.shiftId,
77998
+ shiftName: shift.shiftName,
77999
+ startTime: shift.startTime,
78000
+ endTime: shift.endTime
78001
+ }));
78002
+ }
78003
+ const windows = [];
78004
+ if (shiftConfig?.dayShift) {
78005
+ windows.push({
78006
+ shiftId: shiftConfig.dayShift.id ?? 0,
78007
+ shiftName: shiftConfig.dayShift.name || "Day Shift",
78008
+ startTime: shiftConfig.dayShift.startTime || "06:00",
78009
+ endTime: shiftConfig.dayShift.endTime || "18:00"
78010
+ });
78011
+ }
78012
+ if (shiftConfig?.nightShift) {
78013
+ windows.push({
78014
+ shiftId: shiftConfig.nightShift.id ?? 1,
78015
+ shiftName: shiftConfig.nightShift.name || "Night Shift",
78016
+ startTime: shiftConfig.nightShift.startTime || "18:00",
78017
+ endTime: shiftConfig.nightShift.endTime || "06:00"
78018
+ });
78019
+ }
78020
+ if (windows.length > 0) {
78021
+ return windows;
78022
+ }
78023
+ return [
78024
+ {
78025
+ shiftId: 0,
78026
+ shiftName: "Day Shift",
78027
+ startTime: "06:00",
78028
+ endTime: "18:00"
78029
+ },
78030
+ {
78031
+ shiftId: 1,
78032
+ shiftName: "Night Shift",
78033
+ startTime: "18:00",
78034
+ endTime: "06:00"
78035
+ }
78036
+ ];
78037
+ };
78038
+ var normalizeShiftWindowMinutes = (startTime, endTime) => {
78039
+ const startMinutes = parseTimeToMinutes3(startTime);
78040
+ const endMinutesRaw = parseTimeToMinutes3(endTime);
78041
+ if (startMinutes === null || endMinutesRaw === null) {
78042
+ return null;
78043
+ }
78044
+ let endMinutes = endMinutesRaw;
78045
+ if (endMinutes <= startMinutes) {
78046
+ endMinutes += 24 * 60;
78047
+ }
78048
+ return { startMinutes, endMinutes };
78049
+ };
77912
78050
  var PlantHeadView = () => {
77913
78051
  const supabase = useSupabase();
77914
78052
  const entityConfig = useEntityConfig();
@@ -77934,6 +78072,7 @@ var PlantHeadView = () => {
77934
78072
  const [selectedSupervisorId, setSelectedSupervisorId] = React141__namespace.default.useState("all");
77935
78073
  const [selectedLineIds, setSelectedLineIds] = React141__namespace.default.useState([]);
77936
78074
  const [isInitialScopeReady, setIsInitialScopeReady] = React141__namespace.default.useState(false);
78075
+ const [shiftResolutionTick, setShiftResolutionTick] = React141__namespace.default.useState(0);
77937
78076
  const hasAutoInitializedScopeRef = React141__namespace.default.useRef(false);
77938
78077
  const hasUserAdjustedScopeRef = React141__namespace.default.useRef(false);
77939
78078
  React141__namespace.default.useEffect(() => {
@@ -78038,34 +78177,151 @@ var PlantHeadView = () => {
78038
78177
  shiftConfigMap,
78039
78178
  isLoading: isShiftConfigLoading
78040
78179
  } = useMultiLineShiftConfigs(scopedLineIds, staticShiftConfig);
78041
- const { shiftGroups, hasComputed: hasComputedShiftGroups } = useShiftGroups({
78042
- enabled: scopedLineIds.length > 0 && !isShiftConfigLoading,
78043
- shiftConfigMap,
78044
- timezone: appTimezone
78045
- });
78046
- const currentShiftScope = React141__namespace.default.useMemo(() => {
78047
- if (shiftGroups.length !== 1) return null;
78048
- const group = shiftGroups[0];
78049
- const referenceLineId = group.lineIds[0];
78050
- const referenceShiftConfig = referenceLineId ? shiftConfigMap.get(referenceLineId) : null;
78051
- const normalizedGroupShiftId = normalizeShiftId(group.shiftId);
78052
- const shiftDefinition = referenceShiftConfig?.shifts?.find((shift) => normalizeShiftId(shift.shiftId) === normalizedGroupShiftId);
78053
- const resolvedMode = classifyShiftBucket({
78054
- shiftName: group.shiftName,
78055
- shiftId: normalizedGroupShiftId,
78056
- startTime: shiftDefinition?.startTime,
78057
- endTime: shiftDefinition?.endTime
78058
- });
78059
- if (!resolvedMode || resolvedMode === "all") return null;
78060
- return {
78061
- date: group.date,
78062
- trendMode: resolvedMode,
78063
- shiftName: shiftDefinition?.shiftName || group.shiftName || null
78180
+ React141__namespace.default.useEffect(() => {
78181
+ if (scopedLineIds.length === 0 || isShiftConfigLoading) {
78182
+ return;
78183
+ }
78184
+ const intervalId = window.setInterval(() => {
78185
+ setShiftResolutionTick((previous) => previous + 1);
78186
+ }, 6e4);
78187
+ return () => {
78188
+ clearInterval(intervalId);
78064
78189
  };
78065
- }, [shiftConfigMap, shiftGroups]);
78190
+ }, [isShiftConfigLoading, scopedLineIds.length]);
78191
+ const shiftResolutionNow = React141__namespace.default.useMemo(
78192
+ () => /* @__PURE__ */ new Date(),
78193
+ [shiftResolutionTick]
78194
+ );
78195
+ const earliestDayShiftStartTime = React141__namespace.default.useMemo(() => {
78196
+ const candidateStarts = [];
78197
+ scopedLineIds.forEach((lineId) => {
78198
+ const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
78199
+ getShiftWindowsForConfig(shiftConfig).forEach((shift) => {
78200
+ const bucket = classifyShiftBucket({
78201
+ shiftName: shift.shiftName,
78202
+ shiftId: shift.shiftId,
78203
+ startTime: shift.startTime,
78204
+ endTime: shift.endTime
78205
+ });
78206
+ const startMinutes = parseTimeToMinutes3(shift.startTime);
78207
+ if (bucket === "day" && startMinutes !== null) {
78208
+ candidateStarts.push(startMinutes);
78209
+ }
78210
+ });
78211
+ });
78212
+ if (candidateStarts.length === 0) {
78213
+ scopedLineIds.forEach((lineId) => {
78214
+ const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
78215
+ getShiftWindowsForConfig(shiftConfig).forEach((shift) => {
78216
+ const startMinutes = parseTimeToMinutes3(shift.startTime);
78217
+ if (startMinutes !== null) {
78218
+ candidateStarts.push(startMinutes);
78219
+ }
78220
+ });
78221
+ });
78222
+ }
78223
+ if (candidateStarts.length === 0) {
78224
+ return "06:00";
78225
+ }
78226
+ const earliestMinutes = Math.min(...candidateStarts);
78227
+ const hours = Math.floor(earliestMinutes / 60);
78228
+ const minutes = earliestMinutes % 60;
78229
+ return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
78230
+ }, [appTimezone, scopedLineIds, shiftConfigMap, staticShiftConfig]);
78231
+ const resolvedOperationalToday = React141__namespace.default.useMemo(
78232
+ () => getOperationalDate(appTimezone, shiftResolutionNow, earliestDayShiftStartTime),
78233
+ [appTimezone, earliestDayShiftStartTime, shiftResolutionNow]
78234
+ );
78235
+ const activeLineShiftStates = React141__namespace.default.useMemo(() => {
78236
+ return scopedLineIds.flatMap((lineId) => {
78237
+ const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
78238
+ const activeShift = getActiveShift(appTimezone, shiftConfig, shiftResolutionNow);
78239
+ if (!activeShift) {
78240
+ return [];
78241
+ }
78242
+ const trendBucket = classifyShiftBucket({
78243
+ shiftName: activeShift.shiftName,
78244
+ shiftId: activeShift.shiftId,
78245
+ startTime: activeShift.startTime,
78246
+ endTime: activeShift.endTime
78247
+ });
78248
+ if (!trendBucket || trendBucket === "all") {
78249
+ return [];
78250
+ }
78251
+ return [{
78252
+ lineId,
78253
+ trendMode: trendBucket,
78254
+ shiftId: activeShift.shiftId,
78255
+ shiftName: activeShift.shiftName || null,
78256
+ startTime: activeShift.startTime || null,
78257
+ endTime: activeShift.endTime || null,
78258
+ date: activeShift.date
78259
+ }];
78260
+ });
78261
+ }, [appTimezone, scopedLineIds, shiftConfigMap, shiftResolutionNow, staticShiftConfig]);
78262
+ const hasActiveDayShiftLine = React141__namespace.default.useMemo(
78263
+ () => activeLineShiftStates.some((shift) => shift.trendMode === "day" && shift.date === resolvedOperationalToday),
78264
+ [activeLineShiftStates, resolvedOperationalToday]
78265
+ );
78266
+ const hasActiveNightShiftLine = React141__namespace.default.useMemo(
78267
+ () => activeLineShiftStates.some((shift) => shift.trendMode === "night" && shift.date === resolvedOperationalToday),
78268
+ [activeLineShiftStates, resolvedOperationalToday]
78269
+ );
78270
+ const resolvedTrendMode = isInitialScopeReady ? trendMode : "all";
78271
+ const hourlyWindowStartTime = React141__namespace.default.useMemo(() => {
78272
+ if (scopedLineIds.length === 0) {
78273
+ return null;
78274
+ }
78275
+ const startCandidates = [];
78276
+ const endCandidates = [];
78277
+ scopedLineIds.forEach((lineId) => {
78278
+ const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
78279
+ getShiftWindowsForConfig(shiftConfig).forEach((shift) => {
78280
+ const bucket = classifyShiftBucket({
78281
+ shiftName: shift.shiftName,
78282
+ shiftId: shift.shiftId,
78283
+ startTime: shift.startTime,
78284
+ endTime: shift.endTime
78285
+ });
78286
+ if (resolvedTrendMode !== "all" && bucket !== resolvedTrendMode) {
78287
+ return;
78288
+ }
78289
+ const normalizedWindow = normalizeShiftWindowMinutes(shift.startTime, shift.endTime);
78290
+ if (!normalizedWindow) {
78291
+ return;
78292
+ }
78293
+ startCandidates.push(normalizedWindow.startMinutes);
78294
+ endCandidates.push(normalizedWindow.endMinutes);
78295
+ });
78296
+ });
78297
+ if (resolvedTrendMode === "all") {
78298
+ const dayStartCandidates = startCandidates.length > 0 ? scopedLineIds.flatMap((lineId) => {
78299
+ const shiftConfig = shiftConfigMap.get(lineId) || staticShiftConfig;
78300
+ return getShiftWindowsForConfig(shiftConfig).map((shift) => {
78301
+ const bucket = classifyShiftBucket({
78302
+ shiftName: shift.shiftName,
78303
+ shiftId: shift.shiftId,
78304
+ startTime: shift.startTime,
78305
+ endTime: shift.endTime
78306
+ });
78307
+ return bucket === "day" ? parseTimeToMinutes3(shift.startTime) : null;
78308
+ }).filter((value) => value !== null);
78309
+ }) : [];
78310
+ if (dayStartCandidates.length > 0) {
78311
+ startCandidates.splice(0, startCandidates.length, ...dayStartCandidates);
78312
+ }
78313
+ }
78314
+ if (startCandidates.length === 0 || endCandidates.length === 0) {
78315
+ return null;
78316
+ }
78317
+ const earliestMinutes = Math.min(...startCandidates);
78318
+ const hours = Math.floor(earliestMinutes / 60);
78319
+ const minutes = earliestMinutes % 60;
78320
+ return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
78321
+ }, [appTimezone, resolvedTrendMode, scopedLineIds, shiftConfigMap, staticShiftConfig]);
78066
78322
  const isShiftScopeResolved = React141__namespace.default.useMemo(
78067
- () => !isShiftConfigLoading && hasComputedShiftGroups,
78068
- [hasComputedShiftGroups, isShiftConfigLoading]
78323
+ () => !isShiftConfigLoading,
78324
+ [isShiftConfigLoading]
78069
78325
  );
78070
78326
  const initializedTimezoneRef = React141__namespace.default.useRef(appTimezone);
78071
78327
  React141__namespace.default.useEffect(() => {
@@ -78092,7 +78348,7 @@ var PlantHeadView = () => {
78092
78348
  return;
78093
78349
  }
78094
78350
  setDateRange((previous) => {
78095
- const nextStartKey = currentShiftScope?.date || fallbackOperationalDate;
78351
+ const nextStartKey = resolvedOperationalToday || fallbackOperationalDate;
78096
78352
  if (previous.startKey === nextStartKey && previous.endKey === nextStartKey) {
78097
78353
  return previous;
78098
78354
  }
@@ -78101,11 +78357,11 @@ var PlantHeadView = () => {
78101
78357
  endKey: nextStartKey
78102
78358
  };
78103
78359
  });
78104
- setTrendMode(currentShiftScope?.trendMode || "all");
78360
+ setTrendMode("all");
78105
78361
  setUsesThisWeekComparison(false);
78106
78362
  hasAutoInitializedScopeRef.current = true;
78107
78363
  setIsInitialScopeReady(true);
78108
- }, [currentShiftScope, fallbackOperationalDate, isShiftScopeResolved, scopedLineIds.length]);
78364
+ }, [fallbackOperationalDate, isShiftScopeResolved, resolvedOperationalToday, scopedLineIds.length]);
78109
78365
  const handleDateRangeChange = React141__namespace.default.useCallback((range, meta) => {
78110
78366
  hasUserAdjustedScopeRef.current = true;
78111
78367
  setIsInitialScopeReady(true);
@@ -78182,45 +78438,36 @@ var PlantHeadView = () => {
78182
78438
  if (isInitialScopeReady) {
78183
78439
  return dateRange;
78184
78440
  }
78185
- const nextStartKey = currentShiftScope?.date || fallbackOperationalDate;
78441
+ const nextStartKey = resolvedOperationalToday || fallbackOperationalDate;
78186
78442
  return {
78187
78443
  startKey: nextStartKey,
78188
78444
  endKey: nextStartKey
78189
78445
  };
78190
- }, [currentShiftScope, dateRange, fallbackOperationalDate, isInitialScopeReady]);
78446
+ }, [dateRange, fallbackOperationalDate, isInitialScopeReady, resolvedOperationalToday]);
78191
78447
  const effectiveTrendMode = React141__namespace.default.useMemo(
78192
- () => isInitialScopeReady ? trendMode : currentShiftScope?.trendMode || "all",
78193
- [currentShiftScope, isInitialScopeReady, trendMode]
78448
+ () => resolvedTrendMode,
78449
+ [resolvedTrendMode]
78194
78450
  );
78195
78451
  const hourlyLabelStartTime = React141__namespace.default.useMemo(() => {
78196
- if (effectiveTrendMode === "all" || scopedLineIds.length === 0) {
78197
- return null;
78198
- }
78199
- const shiftStartTimes = /* @__PURE__ */ new Set();
78200
- scopedLineIds.forEach((lineId) => {
78201
- const shiftConfig = shiftConfigMap.get(lineId);
78202
- const matchingShift = shiftConfig?.shifts?.find((shift) => classifyShiftBucket({
78203
- shiftName: shift.shiftName,
78204
- shiftId: shift.shiftId,
78205
- startTime: shift.startTime,
78206
- endTime: shift.endTime
78207
- }) === effectiveTrendMode);
78208
- if (matchingShift?.startTime) {
78209
- shiftStartTimes.add(matchingShift.startTime);
78210
- }
78211
- });
78212
- if (shiftStartTimes.size !== 1) {
78452
+ if (scopedLineIds.length === 0) {
78213
78453
  return null;
78214
78454
  }
78215
- return Array.from(shiftStartTimes)[0] || null;
78216
- }, [effectiveTrendMode, scopedLineIds, shiftConfigMap]);
78217
- const isSingleDayShiftScope = React141__namespace.default.useMemo(
78218
- () => effectiveDateRange.startKey === effectiveDateRange.endKey && effectiveTrendMode !== "all",
78219
- [effectiveDateRange.endKey, effectiveDateRange.startKey, effectiveTrendMode]
78455
+ return hourlyWindowStartTime;
78456
+ }, [hourlyWindowStartTime, scopedLineIds.length]);
78457
+ const isSingleDayScope = React141__namespace.default.useMemo(
78458
+ () => effectiveDateRange.startKey === effectiveDateRange.endKey,
78459
+ [effectiveDateRange.endKey, effectiveDateRange.startKey]
78220
78460
  );
78221
78461
  const isLiveScope = React141__namespace.default.useMemo(
78222
- () => isSingleDayShiftScope && currentShiftScope !== null && effectiveDateRange.startKey === currentShiftScope.date && effectiveTrendMode === currentShiftScope.trendMode,
78223
- [currentShiftScope, effectiveDateRange.startKey, effectiveTrendMode, isSingleDayShiftScope]
78462
+ () => isSingleDayScope && effectiveDateRange.startKey === resolvedOperationalToday && (effectiveTrendMode === "all" || effectiveTrendMode === "day" && hasActiveDayShiftLine || effectiveTrendMode === "night" && hasActiveNightShiftLine),
78463
+ [
78464
+ effectiveDateRange.startKey,
78465
+ effectiveTrendMode,
78466
+ hasActiveDayShiftLine,
78467
+ hasActiveNightShiftLine,
78468
+ isSingleDayScope,
78469
+ resolvedOperationalToday
78470
+ ]
78224
78471
  );
78225
78472
  const handleOpenLineDetails = React141__namespace.default.useCallback((lineId, lineName) => {
78226
78473
  trackCoreEvent("Operations Overview Line Clicked", {
@@ -78255,7 +78502,7 @@ var PlantHeadView = () => {
78255
78502
  displayDateRange: headerDateRange,
78256
78503
  trendMode,
78257
78504
  isLiveScope,
78258
- liveShiftName: currentShiftScope?.shiftName || null,
78505
+ liveShiftName: isLiveScope && trendMode !== "all" ? trendMode : null,
78259
78506
  lineOptions,
78260
78507
  supervisorOptions,
78261
78508
  selectedSupervisorId,
@@ -79086,6 +79333,7 @@ exports.formatRelativeTime = formatRelativeTime;
79086
79333
  exports.formatTimeInZone = formatTimeInZone;
79087
79334
  exports.fromUrlFriendlyName = fromUrlFriendlyName;
79088
79335
  exports.getActionDisplayName = getActionDisplayName;
79336
+ exports.getActiveShift = getActiveShift;
79089
79337
  exports.getAllLineDisplayNames = getAllLineDisplayNames;
79090
79338
  exports.getAllThreadMessages = getAllThreadMessages;
79091
79339
  exports.getAllWorkspaceDisplayNamesAsync = getAllWorkspaceDisplayNamesAsync;