@optifye/dashboard-core 6.11.17 → 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
@@ -4010,7 +4010,8 @@ var dashboardService = {
4010
4010
  enable,
4011
4011
  monitoring_mode,
4012
4012
  assembly,
4013
- video_grid_metric_mode
4013
+ video_grid_metric_mode,
4014
+ recent_flow_window_minutes
4014
4015
  `).eq("enable", true);
4015
4016
  if (companyId) {
4016
4017
  query = query.eq("company_id", companyId);
@@ -4035,7 +4036,8 @@ var dashboardService = {
4035
4036
  video_grid_metric_mode: normalizeVideoGridMetricMode(
4036
4037
  line.video_grid_metric_mode,
4037
4038
  line.assembly ?? false
4038
- )
4039
+ ),
4040
+ recent_flow_window_minutes: line.recent_flow_window_minutes ?? 7
4039
4041
  }));
4040
4042
  return transformedLines;
4041
4043
  } catch (err) {
@@ -4072,7 +4074,7 @@ var dashboardService = {
4072
4074
  }
4073
4075
  const lineIdsToQuery = configuredLineIds;
4074
4076
  const [line1Result, metricsResult2] = await Promise.all([
4075
- 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(),
4076
4078
  supabase.from(lineMetricsTable).select("*").in("line_id", lineIdsToQuery).eq("shift_id", queryShiftId).eq("date", queryDate)
4077
4079
  ]);
4078
4080
  if (line1Result.error) throw line1Result.error;
@@ -4128,6 +4130,7 @@ var dashboardService = {
4128
4130
  date: queryDate,
4129
4131
  shift_id: queryShiftId,
4130
4132
  monitoring_mode: line1Data.monitoring_mode ?? void 0,
4133
+ recent_flow_window_minutes: line1Data.recent_flow_window_minutes ?? 7,
4131
4134
  metrics: {
4132
4135
  avg_efficiency: avgEfficiency,
4133
4136
  avg_cycle_time: combinedMetricsData.avg_cycle_time / numLines,
@@ -4151,7 +4154,7 @@ var dashboardService = {
4151
4154
  throw new Error("Company ID must be configured for detailed line requests.");
4152
4155
  }
4153
4156
  const [lineResult, metricsResult] = await Promise.all([
4154
- 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(),
4155
4158
  supabase.from(lineMetricsTable).select("*").eq("line_id", lineIdToQuery).eq("shift_id", queryShiftId).eq("date", queryDate)
4156
4159
  ]);
4157
4160
  if (lineResult.error) throw lineResult.error;
@@ -4176,6 +4179,7 @@ var dashboardService = {
4176
4179
  date: queryDate,
4177
4180
  shift_id: queryShiftId,
4178
4181
  monitoring_mode: lineData.monitoring_mode ?? void 0,
4182
+ recent_flow_window_minutes: lineData.recent_flow_window_minutes ?? 7,
4179
4183
  metrics: {
4180
4184
  avg_efficiency: metrics2?.avg_efficiency ?? 0,
4181
4185
  avg_cycle_time: metrics2?.avg_cycle_time || 0,
@@ -8509,7 +8513,8 @@ var LinesService = class {
8509
8513
  videoGridMetricMode: normalizeVideoGridMetricMode(
8510
8514
  line.video_grid_metric_mode,
8511
8515
  line.assembly ?? false
8512
- )
8516
+ ),
8517
+ recentFlowWindowMinutes: line.recent_flow_window_minutes ?? 7
8513
8518
  }));
8514
8519
  } catch (error) {
8515
8520
  console.error("Error fetching lines:", error);
@@ -8557,7 +8562,8 @@ var LinesService = class {
8557
8562
  videoGridMetricMode: normalizeVideoGridMetricMode(
8558
8563
  line.video_grid_metric_mode,
8559
8564
  line.assembly ?? false
8560
- )
8565
+ ),
8566
+ recentFlowWindowMinutes: line.recent_flow_window_minutes ?? 7
8561
8567
  }));
8562
8568
  } catch (error) {
8563
8569
  console.error("Error fetching all lines:", error);
@@ -8614,7 +8620,8 @@ var LinesService = class {
8614
8620
  videoGridMetricMode: normalizeVideoGridMetricMode(
8615
8621
  data.video_grid_metric_mode,
8616
8622
  data.assembly ?? false
8617
- )
8623
+ ),
8624
+ recentFlowWindowMinutes: data.recent_flow_window_minutes ?? 7
8618
8625
  };
8619
8626
  } catch (error) {
8620
8627
  console.error("Error fetching line:", error);
@@ -13368,6 +13375,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13368
13375
  actionName: item.action_name
13369
13376
  }),
13370
13377
  recent_flow_percent: item.recent_flow_percent ?? null,
13378
+ recent_flow_window_minutes: item.recent_flow_window_minutes ?? null,
13371
13379
  recent_flow_effective_end_at: item.recent_flow_effective_end_at ?? null,
13372
13380
  recent_flow_computed_at: item.recent_flow_computed_at ?? null,
13373
13381
  incoming_wip_current: item.incoming_wip_current ?? null,
@@ -32624,6 +32632,212 @@ var CycleTimeChart = React141__namespace.default.memo(CycleTimeChartComponent, (
32624
32632
  });
32625
32633
  });
32626
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
+ };
32627
32841
  var CycleTimeOverTimeChart = ({
32628
32842
  data,
32629
32843
  idealCycleTime,
@@ -32633,7 +32847,8 @@ var CycleTimeOverTimeChart = ({
32633
32847
  datasetKey,
32634
32848
  className = "",
32635
32849
  showIdleTime = false,
32636
- idleTimeData = []
32850
+ idleTimeData = [],
32851
+ idleTimeSlots = []
32637
32852
  }) => {
32638
32853
  const MAX_DATA_POINTS = 40;
32639
32854
  const containerRef = React141__namespace.default.useRef(null);
@@ -32736,50 +32951,60 @@ var CycleTimeOverTimeChart = ({
32736
32951
  if (!visibleEntries.length) {
32737
32952
  return null;
32738
32953
  }
32739
- return /* @__PURE__ */ jsxRuntime.jsxs(
32740
- "div",
32741
- {
32742
- style: {
32743
- backgroundColor: "white",
32744
- border: "none",
32745
- borderRadius: "8px",
32746
- boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
32747
- padding: "8px 12px",
32748
- fontSize: "13px"
32749
- },
32750
- children: [
32751
- /* @__PURE__ */ jsxRuntime.jsx(
32752
- "div",
32753
- {
32754
- style: {
32755
- color: "#374151",
32756
- fontWeight: 600,
32757
- marginBottom: "4px"
32758
- },
32759
- children: payload[0]?.payload?.tooltip || ""
32760
- }
32761
- ),
32762
- visibleEntries.map((entry) => {
32763
- const numericValue = getNumericValue(entry.value);
32764
- if (numericValue === null) {
32765
- return null;
32766
- }
32767
- return /* @__PURE__ */ jsxRuntime.jsx(
32768
- "div",
32769
- {
32770
- style: {
32771
- color: "#4B5563",
32772
- padding: "2px 0"
32773
- },
32774
- children: entry.name === "idleMinutes" ? `Idle Time: ${numericValue.toFixed(0)} minutes` : `Cycle Time: ${numericValue.toFixed(1)} seconds`
32775
- },
32776
- `${entry.name}-${numericValue}`
32777
- );
32778
- })
32779
- ]
32780
- }
32781
- );
32782
- }, [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]);
32783
33008
  const renderCycleDot = React141__namespace.default.useCallback((props) => {
32784
33009
  const { cx: cx2, cy, payload } = props;
32785
33010
  const cycleTime = getNumericValue(payload?.cycleTime);
@@ -32848,14 +33073,18 @@ var CycleTimeOverTimeChart = ({
32848
33073
  r: 4,
32849
33074
  fill: "#f59e0b",
32850
33075
  stroke: "#fff",
32851
- strokeWidth: 1
33076
+ strokeWidth: 1,
33077
+ style: {
33078
+ opacity: showIdleTime ? 1 : 0,
33079
+ transition: "opacity 0.3s ease-in-out"
33080
+ }
32852
33081
  }
32853
33082
  );
32854
- }, [getNumericValue]);
33083
+ }, [getNumericValue, showIdleTime]);
32855
33084
  const renderIdleActiveDot = React141__namespace.default.useCallback((props) => {
32856
33085
  const { cx: cx2, cy, payload } = props;
32857
33086
  const idleMinutes = getNumericValue(payload?.idleMinutes);
32858
- if (idleMinutes === null) {
33087
+ if (idleMinutes === null || !showIdleTime) {
32859
33088
  return /* @__PURE__ */ jsxRuntime.jsx("g", {});
32860
33089
  }
32861
33090
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -32869,25 +33098,40 @@ var CycleTimeOverTimeChart = ({
32869
33098
  strokeWidth: 2
32870
33099
  }
32871
33100
  );
32872
- }, [getNumericValue]);
33101
+ }, [getNumericValue, showIdleTime]);
32873
33102
  const chartData = React141__namespace.default.useMemo(() => Array.from({ length: DURATION }, (_, i) => {
32874
33103
  const cycleTime = getNumericValue(finalData[i]);
32875
- 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]);
32876
33107
  return {
32877
33108
  timeIndex: i,
32878
33109
  label: formatTimeLabel(i),
32879
- tooltip: formatTooltipTime(i),
33110
+ tooltip: idleSlot?.timeRange || formatTooltipTime(i),
33111
+ timeRange: idleSlot?.timeRange || formatTooltipTime(i),
33112
+ hourIndex: idleSlot?.hourIndex ?? i,
32880
33113
  cycleTime,
32881
33114
  idleMinutes,
33115
+ idleArray: idleSlot?.idleArray || [],
32882
33116
  color: cycleTime !== null && cycleTime <= idealCycleTime ? "#00AB45" : "#E34329"
32883
33117
  };
32884
- }), [DURATION, finalData, showIdleTime, idleTimeData, idealCycleTime, getNumericValue]);
33118
+ }), [DURATION, finalData, idleTimeData, idleTimeSlots, idealCycleTime, getNumericValue]);
32885
33119
  const renderLegend = () => {
32886
- if (!showIdleTime) return null;
32887
- 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: [
32888
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2.5 h-2.5 rounded-full bg-[#f59e0b]" }),
32889
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Idle Time (min)" })
32890
- ] }) });
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
+ );
32891
33135
  };
32892
33136
  return /* @__PURE__ */ jsxRuntime.jsxs(
32893
33137
  "div",
@@ -32958,7 +33202,7 @@ var CycleTimeOverTimeChart = ({
32958
33202
  }
32959
33203
  }
32960
33204
  ),
32961
- showIdleTime && /* @__PURE__ */ jsxRuntime.jsx(
33205
+ /* @__PURE__ */ jsxRuntime.jsx(
32962
33206
  recharts.YAxis,
32963
33207
  {
32964
33208
  yAxisId: "idle",
@@ -32967,7 +33211,11 @@ var CycleTimeOverTimeChart = ({
32967
33211
  width: 35,
32968
33212
  domain: [0, 60],
32969
33213
  tickFormatter: (value) => `${value}m`,
32970
- 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
+ },
32971
33219
  axisLine: false,
32972
33220
  tickLine: false
32973
33221
  }
@@ -33015,7 +33263,7 @@ var CycleTimeOverTimeChart = ({
33015
33263
  },
33016
33264
  `${effectiveDatasetKey}:cycle`
33017
33265
  ),
33018
- showIdleTime && /* @__PURE__ */ jsxRuntime.jsx(
33266
+ /* @__PURE__ */ jsxRuntime.jsx(
33019
33267
  recharts.Line,
33020
33268
  {
33021
33269
  type: "monotone",
@@ -33030,7 +33278,11 @@ var CycleTimeOverTimeChart = ({
33030
33278
  isAnimationActive: true,
33031
33279
  animationBegin: 300,
33032
33280
  animationDuration: 1500,
33033
- animationEasing: "ease-out"
33281
+ animationEasing: "ease-out",
33282
+ style: {
33283
+ strokeOpacity: showIdleTime ? 1 : 0,
33284
+ transition: "stroke-opacity 0.3s ease-in-out"
33285
+ }
33034
33286
  },
33035
33287
  `${effectiveDatasetKey}:idle`
33036
33288
  )
@@ -33185,89 +33437,20 @@ var HourlyOutputChartComponent = ({
33185
33437
  shiftEnd,
33186
33438
  showIdleTime = false,
33187
33439
  idleTimeHourly,
33188
- idleTimeClips,
33189
- idleTimeClipClassifications,
33190
- shiftDate,
33191
- timezone,
33192
33440
  className = ""
33193
33441
  }) => {
33194
33442
  const containerRef = React141__namespace.default.useRef(null);
33195
33443
  const [containerReady, setContainerReady] = React141__namespace.default.useState(false);
33196
33444
  const [containerWidth, setContainerWidth] = React141__namespace.default.useState(0);
33197
- const getTimeFromTimeString2 = (timeStr) => {
33198
- const [hours, minutes] = timeStr.split(":");
33199
- const hour = parseInt(hours);
33200
- const minute = parseInt(minutes || "0");
33201
- const decimalHour = hour + minute / 60;
33202
- return { hour, minute, decimalHour };
33203
- };
33204
- const shiftStartTime = getTimeFromTimeString2(shiftStart);
33205
- React141__namespace.default.useMemo(() => {
33206
- if (!shiftDate || !timezone) return null;
33207
- const hour = shiftStartTime.hour.toString().padStart(2, "0");
33208
- const minute = shiftStartTime.minute.toString().padStart(2, "0");
33209
- return dateFnsTz.fromZonedTime(`${shiftDate}T${hour}:${minute}:00`, timezone);
33210
- }, [shiftDate, timezone, shiftStartTime.hour, shiftStartTime.minute]);
33211
- const idleClipRanges = React141__namespace.default.useMemo(() => {
33212
- if (!idleTimeClips || idleTimeClips.length === 0) return [];
33213
- return idleTimeClips.map((clip) => ({
33214
- id: clip.id,
33215
- start: clip.idle_start_time ? new Date(clip.idle_start_time) : null,
33216
- end: clip.idle_end_time ? new Date(clip.idle_end_time) : null
33217
- })).filter((clip) => clip.start && clip.end);
33218
- }, [idleTimeClips]);
33219
- React141__namespace.default.useCallback((rangeStart, rangeEnd) => {
33220
- if (!rangeStart || !rangeEnd || idleClipRanges.length === 0) {
33221
- return "Reason unavailable";
33222
- }
33223
- const matchingClip = idleClipRanges.find((clip) => rangeStart >= clip.start && rangeEnd <= clip.end) || idleClipRanges.find((clip) => rangeStart < clip.end && rangeEnd > clip.start);
33224
- if (!matchingClip) {
33225
- return "Reason unavailable";
33226
- }
33227
- const classification = idleTimeClipClassifications?.[matchingClip.id];
33228
- if (!classification || classification.status === "processing") {
33229
- return "Analyzing...";
33230
- }
33231
- if (!classification.label) {
33232
- return "Reason unavailable";
33233
- }
33234
- return classification.displayName || classification.label.replace(/_/g, " ");
33235
- }, [idleClipRanges, idleTimeClipClassifications]);
33236
- const { shiftDuration, shiftEndTime, hasPartialLastHour } = React141__namespace.default.useMemo(() => {
33237
- console.log("[HourlyOutputChart] Calculating shift duration with:", {
33445
+ const idleSlots = React141__namespace.default.useMemo(
33446
+ () => buildHourlyIdleSlots({
33447
+ idleTimeHourly,
33238
33448
  shiftStart,
33239
- shiftEnd,
33240
- shiftStartTime
33241
- });
33242
- if (!shiftEnd) {
33243
- console.log("[HourlyOutputChart] No shiftEnd provided, using default 11 hours");
33244
- return {
33245
- shiftDuration: 11,
33246
- shiftEndTime: null,
33247
- hasPartialLastHour: false
33248
- };
33249
- }
33250
- const endTime = getTimeFromTimeString2(shiftEnd);
33251
- let duration = endTime.decimalHour - shiftStartTime.decimalHour;
33252
- if (duration <= 0) {
33253
- duration += 24;
33254
- }
33255
- const hasPartial = endTime.minute > 0 && endTime.minute < 60;
33256
- const hourCount = hasPartial ? Math.ceil(duration) : Math.round(duration);
33257
- console.log("[HourlyOutputChart] Shift calculation results:", {
33258
- endTime,
33259
- duration,
33260
- hasPartial,
33261
- hourCount
33262
- });
33263
- return {
33264
- shiftDuration: hourCount,
33265
- shiftEndTime: endTime,
33266
- hasPartialLastHour: hasPartial
33267
- };
33268
- }, [shiftEnd, shiftStartTime.decimalHour]);
33269
- const SHIFT_DURATION = shiftDuration;
33270
- shiftEndTime ? shiftEndTime.hour : (shiftStartTime.hour + SHIFT_DURATION) % 24;
33449
+ shiftEnd
33450
+ }),
33451
+ [idleTimeHourly, shiftStart, shiftEnd]
33452
+ );
33453
+ const SHIFT_DURATION = idleSlots.length;
33271
33454
  const [animatedData, setAnimatedData] = React141__namespace.default.useState(
33272
33455
  () => Array(SHIFT_DURATION).fill(0)
33273
33456
  );
@@ -33390,121 +33573,22 @@ var HourlyOutputChartComponent = ({
33390
33573
  }
33391
33574
  return { interval: 0, angle: -30, height: 64, tickFont: 9, tickMargin: 6 };
33392
33575
  }, [containerWidth]);
33393
- const formatHour = React141__namespace.default.useCallback((hourIndex) => {
33394
- const isLastHour = hourIndex === SHIFT_DURATION - 1;
33395
- const startDecimalHour = shiftStartTime.decimalHour + hourIndex;
33396
- const startHour = Math.floor(startDecimalHour) % 24;
33397
- const startMinute = Math.round(startDecimalHour % 1 * 60);
33398
- let endHour, endMinute;
33399
- if (isLastHour && shiftEndTime) {
33400
- endHour = shiftEndTime.hour;
33401
- endMinute = shiftEndTime.minute;
33402
- } else {
33403
- const endDecimalHour = startDecimalHour + 1;
33404
- endHour = Math.floor(endDecimalHour) % 24;
33405
- endMinute = Math.round(endDecimalHour % 1 * 60);
33406
- }
33407
- const formatTime5 = (h, m) => {
33408
- const period = h >= 12 ? "PM" : "AM";
33409
- const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
33410
- if (m === 0) {
33411
- return `${hour12}${period}`;
33412
- }
33413
- return `${hour12}:${m.toString().padStart(2, "0")}${period}`;
33414
- };
33415
- return `${formatTime5(startHour, startMinute)}-${formatTime5(endHour, endMinute)}`;
33416
- }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
33417
- const formatTimeRange2 = React141__namespace.default.useCallback((hourIndex) => {
33418
- const isLastHour = hourIndex === SHIFT_DURATION - 1;
33419
- const startDecimalHour = shiftStartTime.decimalHour + hourIndex;
33420
- const startHour = Math.floor(startDecimalHour) % 24;
33421
- const startMinute = Math.round(startDecimalHour % 1 * 60);
33422
- let endHour, endMinute;
33423
- if (isLastHour && shiftEndTime) {
33424
- endHour = shiftEndTime.hour;
33425
- endMinute = shiftEndTime.minute;
33426
- } else {
33427
- const endDecimalHour = startDecimalHour + 1;
33428
- endHour = Math.floor(endDecimalHour) % 24;
33429
- endMinute = Math.round(endDecimalHour % 1 * 60);
33430
- }
33431
- const formatTime5 = (h, m) => {
33432
- const period = h >= 12 ? "PM" : "AM";
33433
- const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
33434
- return `${hour12}:${m.toString().padStart(2, "0")} ${period}`;
33435
- };
33436
- return `${formatTime5(startHour, startMinute)} - ${formatTime5(endHour, endMinute)}`;
33437
- }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
33438
33576
  const chartData = React141__namespace.default.useMemo(() => {
33439
33577
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
33440
- const actualHour = (shiftStartTime.hour + i) % 24;
33441
- const startMinute = shiftStartTime.minute;
33442
- let idleArray = [];
33443
- let idleMinutes = 0;
33444
- if (idleTimeHourly) {
33445
- if (startMinute > 0) {
33446
- if (i === 0) {
33447
- const firstHourData = idleTimeHourly[actualHour.toString()] || [];
33448
- const nextHour = (actualHour + 1) % 24;
33449
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33450
- idleArray = [
33451
- ...firstHourData.slice(startMinute) || [],
33452
- ...nextHourData.slice(0, startMinute) || []
33453
- ];
33454
- } else if (i < SHIFT_DURATION - 1) {
33455
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33456
- const nextHour = (actualHour + 1) % 24;
33457
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33458
- idleArray = [
33459
- ...currentHourData.slice(startMinute) || [],
33460
- ...nextHourData.slice(0, startMinute) || []
33461
- ];
33462
- } else {
33463
- const hasPartialLastHour2 = shiftEndTime && shiftEndTime.minute > 0 && shiftEndTime.minute < 60;
33464
- if (hasPartialLastHour2) {
33465
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33466
- const nextHour = (actualHour + 1) % 24;
33467
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33468
- if (startMinute > 0) {
33469
- const firstPart = currentHourData.slice(startMinute) || [];
33470
- const secondPart = nextHourData.slice(0, shiftEndTime.minute) || [];
33471
- idleArray = [...firstPart, ...secondPart];
33472
- } else {
33473
- idleArray = currentHourData.slice(0, shiftEndTime.minute) || [];
33474
- }
33475
- } else {
33476
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33477
- const nextHour = (actualHour + 1) % 24;
33478
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33479
- idleArray = [
33480
- ...currentHourData.slice(startMinute) || [],
33481
- ...nextHourData.slice(0, startMinute) || []
33482
- ];
33483
- }
33484
- }
33485
- } else {
33486
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33487
- if (i === SHIFT_DURATION - 1 && shiftEndTime && shiftEndTime.minute > 0 && shiftEndTime.minute < 60) {
33488
- idleArray = currentHourData.slice(0, shiftEndTime.minute) || [];
33489
- } else {
33490
- idleArray = currentHourData || [];
33491
- }
33492
- }
33493
- }
33494
- idleMinutes = idleArray.filter((val) => val === "1" || val === 1).length;
33578
+ const idleSlot = idleSlots[i];
33495
33579
  return {
33496
- hourIndex: i,
33497
- hour: formatHour(i),
33498
- timeRange: formatTimeRange2(i),
33580
+ hourIndex: idleSlot?.hourIndex ?? i,
33581
+ hour: idleSlot?.hour || "",
33582
+ timeRange: idleSlot?.timeRange || "",
33499
33583
  output: animatedData[i] || 0,
33500
33584
  originalOutput: data[i] || 0,
33501
33585
  // Keep original data for labels
33502
33586
  color: (animatedData[i] || 0) >= Math.round(pphThreshold) ? "#00AB45" : "#E34329",
33503
- idleMinutes,
33504
- idleArray
33587
+ idleMinutes: idleSlot?.idleMinutes || 0,
33588
+ idleArray: idleSlot?.idleArray || []
33505
33589
  };
33506
33590
  });
33507
- }, [animatedData, data, pphThreshold, idleTimeHourly, shiftStartTime.hour, shiftStartTime.minute, shiftEndTime, formatHour, formatTimeRange2, SHIFT_DURATION]);
33591
+ }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION]);
33508
33592
  const IdleBar = React141__namespace.default.useMemo(() => {
33509
33593
  if (!idleBarState.visible) return null;
33510
33594
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -33666,36 +33750,11 @@ var HourlyOutputChartComponent = ({
33666
33750
  content: (props) => {
33667
33751
  if (!props.active || !props.payload || props.payload.length === 0) return null;
33668
33752
  const data2 = props.payload[0].payload;
33669
- const idleRanges = [];
33670
- if (showIdleTime && data2.idleArray) {
33671
- let currentRange = null;
33672
- data2.idleArray.forEach((val, idx) => {
33673
- if (val === "1" || val === 1) {
33674
- if (!currentRange) {
33675
- currentRange = { start: idx, end: idx };
33676
- } else {
33677
- currentRange.end = idx;
33678
- }
33679
- } else if (val !== "x" && currentRange) {
33680
- idleRanges.push(currentRange);
33681
- currentRange = null;
33682
- }
33683
- });
33684
- if (currentRange) {
33685
- idleRanges.push(currentRange);
33686
- }
33687
- }
33688
- const formatIdleTimestamp = (minuteIdx) => {
33689
- const fallbackIndex = chartData.findIndex((item) => item.timeRange === data2.timeRange);
33690
- const hourOffset = Number.isFinite(data2.hourIndex) ? data2.hourIndex : fallbackIndex;
33691
- const safeHourOffset = hourOffset >= 0 ? hourOffset : 0;
33692
- const totalMinutes = (shiftStartTime.hour + safeHourOffset) * 60 + shiftStartTime.minute + minuteIdx;
33693
- const hour = Math.floor(totalMinutes / 60) % 24;
33694
- const minute = totalMinutes % 60;
33695
- const period = hour >= 12 ? "PM" : "AM";
33696
- const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
33697
- return `${hour12}:${minute.toString().padStart(2, "0")} ${period}`;
33698
- };
33753
+ const idlePeriods = showIdleTime ? getHourlyIdlePeriods({
33754
+ idleArray: data2.idleArray,
33755
+ shiftStart,
33756
+ hourIndex: Number.isFinite(data2.hourIndex) ? data2.hourIndex : 0
33757
+ }) : [];
33699
33758
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-xl shadow-xl border border-gray-100 p-4 min-w-[220px]", children: [
33700
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 }) }),
33701
33760
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
@@ -33714,27 +33773,22 @@ var HourlyOutputChartComponent = ({
33714
33773
  " minutes"
33715
33774
  ] })
33716
33775
  ] }) }),
33717
- 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: [
33718
33777
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium text-gray-700 text-xs mb-2", children: "Idle periods:" }),
33719
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idleRanges.map((range, index) => {
33720
- const duration = range.end - range.start + 1;
33721
- const startTime = formatIdleTimestamp(range.start);
33722
- const endTime = formatIdleTimestamp(range.end + 1);
33723
- const fallbackIndex = chartData.findIndex((item) => item.timeRange === data2.timeRange);
33724
- 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) => {
33725
33779
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-gray-600 flex items-center gap-2 text-xs", children: [
33726
33780
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0" }),
33727
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: duration === 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
33728
- startTime,
33781
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: period.duration === 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
33782
+ period.startTime,
33729
33783
  " (",
33730
- duration,
33784
+ period.duration,
33731
33785
  " min)"
33732
33786
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
33733
- startTime,
33787
+ period.startTime,
33734
33788
  " - ",
33735
- endTime,
33789
+ period.endTime,
33736
33790
  " (",
33737
- duration,
33791
+ period.duration,
33738
33792
  " mins)"
33739
33793
  ] }) })
33740
33794
  ] }, index);
@@ -33889,9 +33943,8 @@ var HourlyOutputChart = React141__namespace.default.memo(HourlyOutputChartCompon
33889
33943
  HourlyOutputChart.displayName = "HourlyOutputChart";
33890
33944
 
33891
33945
  // src/components/dashboard/grid/videoGridMetricUtils.ts
33892
- var VIDEO_GRID_LEGEND_LABEL = "7 Minute Efficiency";
33893
33946
  var MAP_GRID_LEGEND_LABEL = "Efficiency";
33894
- var MIXED_VIDEO_GRID_LEGEND_LABEL = "Efficiency";
33947
+ var MIXED_VIDEO_GRID_LEGEND_LABEL = "Flow Efficiency";
33895
33948
  var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
33896
33949
  var isVideoGridRecentFlowEnabled = (workspace) => isRecentFlowVideoGridMetricMode(
33897
33950
  workspace.video_grid_metric_mode,
@@ -33990,8 +34043,15 @@ var getVideoGridLegendLabel = (workspaces) => {
33990
34043
  if (recentFlowEnabledCount === 0) {
33991
34044
  return MAP_GRID_LEGEND_LABEL;
33992
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
+ );
33993
34049
  if (recentFlowEnabledCount === visibleWorkspaces.length) {
33994
- 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;
33995
34055
  }
33996
34056
  return MIXED_VIDEO_GRID_LEGEND_LABEL;
33997
34057
  };
@@ -35938,7 +35998,7 @@ var HourlyUptimeChartComponent = ({
35938
35998
  idleRanges.push(currentRange);
35939
35999
  }
35940
36000
  }
35941
- const formatIdleTimestamp = (minuteIdx) => {
36001
+ const formatIdleTimestamp2 = (minuteIdx) => {
35942
36002
  const totalMinutes = (shiftStartTime.hour + data.hourIndex) * 60 + shiftStartTime.minute + minuteIdx;
35943
36003
  const hour = Math.floor(totalMinutes / 60) % 24;
35944
36004
  const minute = totalMinutes % 60;
@@ -35968,8 +36028,8 @@ var HourlyUptimeChartComponent = ({
35968
36028
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium text-gray-700 text-xs mb-2", children: "Idle periods:" }),
35969
36029
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idleRanges.map((range, index) => {
35970
36030
  const duration = range.end - range.start + 1;
35971
- const startTime = formatIdleTimestamp(range.start);
35972
- const endTime = formatIdleTimestamp(range.end + 1);
36031
+ const startTime = formatIdleTimestamp2(range.start);
36032
+ const endTime = formatIdleTimestamp2(range.end + 1);
35973
36033
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-gray-600 flex items-center gap-2 text-xs", children: [
35974
36034
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0" }),
35975
36035
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: duration === 1 ? `${startTime} (${duration} min)` : `${startTime} - ${endTime} (${duration} mins)` })
@@ -69216,56 +69276,43 @@ var WorkspaceDetailView = ({
69216
69276
  const canToggleChartIdleTime = !isUptimeMode && !shouldShowCycleTimeLoadingState && !shouldShowCycleTimeUnavailableState;
69217
69277
  const idleClipDate = date || workspace?.date || calculatedOperationalDate || getOperationalDate(timezone);
69218
69278
  const idleClipShiftId = parsedShiftId ?? workspace?.shift_id;
69219
- const rawHourlyIdleMinutes = React141.useMemo(() => {
69220
- if (!shouldShowCycleTimeChart || !workspace?.idle_time_hourly || !workspace?.shift_start) return [];
69221
- const parseTimeToMinutes4 = (time2) => {
69222
- const [h, m] = time2.split(":").map(Number);
69223
- if (!Number.isFinite(h) || !Number.isFinite(m)) return 0;
69224
- return h * 60 + m;
69225
- };
69226
- const startTotal = parseTimeToMinutes4(workspace.shift_start);
69227
- const endTotalRaw = workspace.shift_end ? parseTimeToMinutes4(workspace.shift_end) : startTotal + 11 * 60;
69228
- const endTotal = endTotalRaw <= startTotal ? endTotalRaw + 24 * 60 : endTotalRaw;
69229
- const shiftDuration = Math.max(60, endTotal - startTotal);
69230
- const totalHourSlots = Math.max(1, Math.ceil(shiftDuration / 60));
69231
- const idleTimeHourlyObj = workspace.idle_time_hourly || {};
69232
- const result = Array(totalHourSlots).fill(0);
69233
- for (let i = 0; i < totalHourSlots; i++) {
69234
- const startSlotMins = startTotal + i * 60;
69235
- let endSlotMins = startSlotMins + 60;
69236
- if (endSlotMins > endTotal) endSlotMins = endTotal;
69237
- let idleCount = 0;
69238
- for (let m = startSlotMins; m < endSlotMins; m++) {
69239
- const hourKey = Math.floor(m / 60) % 24;
69240
- const minuteKey = m % 60;
69241
- const hourData = idleTimeHourlyObj[hourKey.toString()] || [];
69242
- if (Array.isArray(hourData)) {
69243
- const val = hourData[minuteKey];
69244
- if (val === 1 || val === "1") {
69245
- idleCount++;
69246
- }
69247
- }
69248
- }
69249
- result[i] = idleCount;
69250
- }
69251
- return result;
69252
- }, [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
+ );
69253
69296
  const hourlyIdleMinutes = React141.useMemo(
69254
69297
  () => maskFutureHourlySeries({
69255
69298
  data: rawHourlyIdleMinutes,
69256
- shiftStart: workspace?.shift_start,
69257
- shiftEnd: workspace?.shift_end,
69299
+ shiftStart: authoritativeCycleMetrics?.shift_start,
69300
+ shiftEnd: authoritativeCycleMetrics?.shift_end,
69258
69301
  shiftDate: idleClipDate,
69259
69302
  timezone: effectiveCycleTimeTimezone
69260
69303
  }),
69261
69304
  [
69262
69305
  rawHourlyIdleMinutes,
69263
- workspace?.shift_start,
69264
- workspace?.shift_end,
69306
+ authoritativeCycleMetrics?.shift_start,
69307
+ authoritativeCycleMetrics?.shift_end,
69265
69308
  idleClipDate,
69266
69309
  effectiveCycleTimeTimezone
69267
69310
  ]
69268
69311
  );
69312
+ const hourlyIdleSlots = React141.useMemo(
69313
+ () => rawHourlyIdleSlots.map((slot, index) => hourlyIdleMinutes[index] === null ? null : slot),
69314
+ [rawHourlyIdleSlots, hourlyIdleMinutes]
69315
+ );
69269
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: [
69270
69317
  /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-base font-semibold text-amber-900", children: "Cycle data unavailable" }),
69271
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." }),
@@ -69821,7 +69868,8 @@ var WorkspaceDetailView = ({
69821
69868
  xAxisMode: "hourly",
69822
69869
  datasetKey: cycleTimeDatasetKey,
69823
69870
  showIdleTime: showChartIdleTime,
69824
- idleTimeData: hourlyIdleMinutes
69871
+ idleTimeData: hourlyIdleMinutes,
69872
+ idleTimeSlots: hourlyIdleSlots
69825
69873
  }
69826
69874
  ) : null : /* @__PURE__ */ jsxRuntime.jsx(
69827
69875
  HourlyOutputChart2,
@@ -69950,7 +69998,8 @@ var WorkspaceDetailView = ({
69950
69998
  xAxisMode: "hourly",
69951
69999
  datasetKey: cycleTimeDatasetKey,
69952
70000
  showIdleTime: showChartIdleTime,
69953
- idleTimeData: hourlyIdleMinutes
70001
+ idleTimeData: hourlyIdleMinutes,
70002
+ idleTimeSlots: hourlyIdleSlots
69954
70003
  }
69955
70004
  ) : null : /* @__PURE__ */ jsxRuntime.jsx(
69956
70005
  HourlyOutputChart2,