@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.mjs CHANGED
@@ -3,7 +3,7 @@ import React141__default, { createContext, useRef, useCallback, useState, useMem
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { useRouter } from 'next/router';
5
5
  import { toast } from 'sonner';
6
- import { fromZonedTime, formatInTimeZone, toZonedTime } from 'date-fns-tz';
6
+ import { formatInTimeZone, fromZonedTime, toZonedTime } from 'date-fns-tz';
7
7
  import { format, addDays, subMonths, endOfMonth, startOfMonth, endOfDay, eachDayOfInterval, getDay, isSameDay, isWithinInterval, startOfDay, parseISO, subDays, differenceInMinutes, addMinutes, addMonths, isValid, formatDistanceToNow, isToday, isFuture, isBefore } from 'date-fns';
8
8
  import mixpanel from 'mixpanel-browser';
9
9
  import { EventEmitter } from 'events';
@@ -3981,7 +3981,8 @@ var dashboardService = {
3981
3981
  enable,
3982
3982
  monitoring_mode,
3983
3983
  assembly,
3984
- video_grid_metric_mode
3984
+ video_grid_metric_mode,
3985
+ recent_flow_window_minutes
3985
3986
  `).eq("enable", true);
3986
3987
  if (companyId) {
3987
3988
  query = query.eq("company_id", companyId);
@@ -4006,7 +4007,8 @@ var dashboardService = {
4006
4007
  video_grid_metric_mode: normalizeVideoGridMetricMode(
4007
4008
  line.video_grid_metric_mode,
4008
4009
  line.assembly ?? false
4009
- )
4010
+ ),
4011
+ recent_flow_window_minutes: line.recent_flow_window_minutes ?? 7
4010
4012
  }));
4011
4013
  return transformedLines;
4012
4014
  } catch (err) {
@@ -4043,7 +4045,7 @@ var dashboardService = {
4043
4045
  }
4044
4046
  const lineIdsToQuery = configuredLineIds;
4045
4047
  const [line1Result, metricsResult2] = await Promise.all([
4046
- 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(),
4048
+ 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(),
4047
4049
  supabase.from(lineMetricsTable).select("*").in("line_id", lineIdsToQuery).eq("shift_id", queryShiftId).eq("date", queryDate)
4048
4050
  ]);
4049
4051
  if (line1Result.error) throw line1Result.error;
@@ -4099,6 +4101,7 @@ var dashboardService = {
4099
4101
  date: queryDate,
4100
4102
  shift_id: queryShiftId,
4101
4103
  monitoring_mode: line1Data.monitoring_mode ?? void 0,
4104
+ recent_flow_window_minutes: line1Data.recent_flow_window_minutes ?? 7,
4102
4105
  metrics: {
4103
4106
  avg_efficiency: avgEfficiency,
4104
4107
  avg_cycle_time: combinedMetricsData.avg_cycle_time / numLines,
@@ -4122,7 +4125,7 @@ var dashboardService = {
4122
4125
  throw new Error("Company ID must be configured for detailed line requests.");
4123
4126
  }
4124
4127
  const [lineResult, metricsResult] = await Promise.all([
4125
- 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(),
4128
+ 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(),
4126
4129
  supabase.from(lineMetricsTable).select("*").eq("line_id", lineIdToQuery).eq("shift_id", queryShiftId).eq("date", queryDate)
4127
4130
  ]);
4128
4131
  if (lineResult.error) throw lineResult.error;
@@ -4147,6 +4150,7 @@ var dashboardService = {
4147
4150
  date: queryDate,
4148
4151
  shift_id: queryShiftId,
4149
4152
  monitoring_mode: lineData.monitoring_mode ?? void 0,
4153
+ recent_flow_window_minutes: lineData.recent_flow_window_minutes ?? 7,
4150
4154
  metrics: {
4151
4155
  avg_efficiency: metrics2?.avg_efficiency ?? 0,
4152
4156
  avg_cycle_time: metrics2?.avg_cycle_time || 0,
@@ -8480,7 +8484,8 @@ var LinesService = class {
8480
8484
  videoGridMetricMode: normalizeVideoGridMetricMode(
8481
8485
  line.video_grid_metric_mode,
8482
8486
  line.assembly ?? false
8483
- )
8487
+ ),
8488
+ recentFlowWindowMinutes: line.recent_flow_window_minutes ?? 7
8484
8489
  }));
8485
8490
  } catch (error) {
8486
8491
  console.error("Error fetching lines:", error);
@@ -8528,7 +8533,8 @@ var LinesService = class {
8528
8533
  videoGridMetricMode: normalizeVideoGridMetricMode(
8529
8534
  line.video_grid_metric_mode,
8530
8535
  line.assembly ?? false
8531
- )
8536
+ ),
8537
+ recentFlowWindowMinutes: line.recent_flow_window_minutes ?? 7
8532
8538
  }));
8533
8539
  } catch (error) {
8534
8540
  console.error("Error fetching all lines:", error);
@@ -8585,7 +8591,8 @@ var LinesService = class {
8585
8591
  videoGridMetricMode: normalizeVideoGridMetricMode(
8586
8592
  data.video_grid_metric_mode,
8587
8593
  data.assembly ?? false
8588
- )
8594
+ ),
8595
+ recentFlowWindowMinutes: data.recent_flow_window_minutes ?? 7
8589
8596
  };
8590
8597
  } catch (error) {
8591
8598
  console.error("Error fetching line:", error);
@@ -13339,6 +13346,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13339
13346
  actionName: item.action_name
13340
13347
  }),
13341
13348
  recent_flow_percent: item.recent_flow_percent ?? null,
13349
+ recent_flow_window_minutes: item.recent_flow_window_minutes ?? null,
13342
13350
  recent_flow_effective_end_at: item.recent_flow_effective_end_at ?? null,
13343
13351
  recent_flow_computed_at: item.recent_flow_computed_at ?? null,
13344
13352
  incoming_wip_current: item.incoming_wip_current ?? null,
@@ -32595,6 +32603,212 @@ var CycleTimeChart = React141__default.memo(CycleTimeChartComponent, (prevProps,
32595
32603
  });
32596
32604
  });
32597
32605
  CycleTimeChart.displayName = "CycleTimeChart";
32606
+
32607
+ // src/lib/utils/hourlyIdle.ts
32608
+ var DEFAULT_SHIFT_DURATION = 11;
32609
+ var normalizeMinuteSeries = (idleTimeHourly) => {
32610
+ if (!idleTimeHourly || typeof idleTimeHourly !== "object") {
32611
+ return {};
32612
+ }
32613
+ return Object.fromEntries(
32614
+ Object.entries(idleTimeHourly).map(([key, value]) => {
32615
+ if (Array.isArray(value)) {
32616
+ return [key, value];
32617
+ }
32618
+ if (value && Array.isArray(value.values)) {
32619
+ return [key, value.values];
32620
+ }
32621
+ return [key, []];
32622
+ })
32623
+ );
32624
+ };
32625
+ var parseTimeString = (timeValue) => {
32626
+ const [hoursPart, minutesPart] = timeValue.split(":");
32627
+ const hour = Number.parseInt(hoursPart, 10);
32628
+ const minute = Number.parseInt(minutesPart ?? "0", 10);
32629
+ const safeHour = Number.isFinite(hour) ? hour : 0;
32630
+ const safeMinute = Number.isFinite(minute) ? minute : 0;
32631
+ return {
32632
+ hour: safeHour,
32633
+ minute: safeMinute,
32634
+ decimalHour: safeHour + safeMinute / 60
32635
+ };
32636
+ };
32637
+ var buildShiftLayout = ({
32638
+ shiftStart,
32639
+ shiftEnd
32640
+ }) => {
32641
+ const shiftStartTime = parseTimeString(shiftStart);
32642
+ if (!shiftEnd) {
32643
+ return {
32644
+ shiftDuration: DEFAULT_SHIFT_DURATION,
32645
+ shiftStartTime,
32646
+ shiftEndTime: null,
32647
+ hasPartialLastHour: false
32648
+ };
32649
+ }
32650
+ const shiftEndTime = parseTimeString(shiftEnd);
32651
+ let duration = shiftEndTime.decimalHour - shiftStartTime.decimalHour;
32652
+ if (duration <= 0) {
32653
+ duration += 24;
32654
+ }
32655
+ const hasPartialLastHour = shiftEndTime.minute > 0 && shiftEndTime.minute < 60;
32656
+ const shiftDuration = hasPartialLastHour ? Math.ceil(duration) : Math.round(duration);
32657
+ return {
32658
+ shiftDuration,
32659
+ shiftStartTime,
32660
+ shiftEndTime,
32661
+ hasPartialLastHour
32662
+ };
32663
+ };
32664
+ var formatCompactTime = (hour, minute) => {
32665
+ const period = hour >= 12 ? "PM" : "AM";
32666
+ const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
32667
+ if (minute === 0) {
32668
+ return `${hour12}${period}`;
32669
+ }
32670
+ return `${hour12}:${minute.toString().padStart(2, "0")}${period}`;
32671
+ };
32672
+ var formatFullTime = (hour, minute) => {
32673
+ const period = hour >= 12 ? "PM" : "AM";
32674
+ const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
32675
+ return `${hour12}:${minute.toString().padStart(2, "0")} ${period}`;
32676
+ };
32677
+ var getSlotTimeBounds = (hourIndex, layout2) => {
32678
+ const isLastHour = hourIndex === layout2.shiftDuration - 1;
32679
+ const startDecimalHour = layout2.shiftStartTime.decimalHour + hourIndex;
32680
+ const startHour = Math.floor(startDecimalHour) % 24;
32681
+ const startMinute = Math.round(startDecimalHour % 1 * 60);
32682
+ let endHour;
32683
+ let endMinute;
32684
+ if (isLastHour && layout2.shiftEndTime) {
32685
+ endHour = layout2.shiftEndTime.hour;
32686
+ endMinute = layout2.shiftEndTime.minute;
32687
+ } else {
32688
+ const endDecimalHour = startDecimalHour + 1;
32689
+ endHour = Math.floor(endDecimalHour) % 24;
32690
+ endMinute = Math.round(endDecimalHour % 1 * 60);
32691
+ }
32692
+ return {
32693
+ startHour,
32694
+ startMinute,
32695
+ endHour,
32696
+ endMinute
32697
+ };
32698
+ };
32699
+ var getIdleArrayForHour = (hourIndex, idleTimeHourly, layout2) => {
32700
+ const actualHour = (layout2.shiftStartTime.hour + hourIndex) % 24;
32701
+ const startMinute = layout2.shiftStartTime.minute;
32702
+ if (startMinute > 0) {
32703
+ if (hourIndex === 0) {
32704
+ const firstHourData = idleTimeHourly[actualHour.toString()] || [];
32705
+ const nextHourData2 = idleTimeHourly[((actualHour + 1) % 24).toString()] || [];
32706
+ return [
32707
+ ...firstHourData.slice(startMinute),
32708
+ ...nextHourData2.slice(0, startMinute)
32709
+ ];
32710
+ }
32711
+ if (hourIndex < layout2.shiftDuration - 1) {
32712
+ const currentHourData3 = idleTimeHourly[actualHour.toString()] || [];
32713
+ const nextHourData2 = idleTimeHourly[((actualHour + 1) % 24).toString()] || [];
32714
+ return [
32715
+ ...currentHourData3.slice(startMinute),
32716
+ ...nextHourData2.slice(0, startMinute)
32717
+ ];
32718
+ }
32719
+ if (layout2.hasPartialLastHour && layout2.shiftEndTime) {
32720
+ const currentHourData3 = idleTimeHourly[actualHour.toString()] || [];
32721
+ const nextHourData2 = idleTimeHourly[((actualHour + 1) % 24).toString()] || [];
32722
+ return [
32723
+ ...currentHourData3.slice(startMinute),
32724
+ ...nextHourData2.slice(0, layout2.shiftEndTime.minute)
32725
+ ];
32726
+ }
32727
+ const currentHourData2 = idleTimeHourly[actualHour.toString()] || [];
32728
+ const nextHourData = idleTimeHourly[((actualHour + 1) % 24).toString()] || [];
32729
+ return [
32730
+ ...currentHourData2.slice(startMinute),
32731
+ ...nextHourData.slice(0, startMinute)
32732
+ ];
32733
+ }
32734
+ const currentHourData = idleTimeHourly[actualHour.toString()] || [];
32735
+ if (hourIndex === layout2.shiftDuration - 1 && layout2.hasPartialLastHour && layout2.shiftEndTime) {
32736
+ return currentHourData.slice(0, layout2.shiftEndTime.minute);
32737
+ }
32738
+ return currentHourData;
32739
+ };
32740
+ var buildHourlyIdleSlots = ({
32741
+ idleTimeHourly,
32742
+ shiftStart,
32743
+ shiftEnd
32744
+ }) => {
32745
+ const normalizedIdleTimeHourly = normalizeMinuteSeries(idleTimeHourly);
32746
+ const layout2 = buildShiftLayout({ shiftStart, shiftEnd });
32747
+ return Array.from({ length: layout2.shiftDuration }, (_, hourIndex) => {
32748
+ const { startHour, startMinute, endHour, endMinute } = getSlotTimeBounds(hourIndex, layout2);
32749
+ const idleArray = getIdleArrayForHour(hourIndex, normalizedIdleTimeHourly, layout2);
32750
+ const idleMinutes = idleArray.filter((value) => value === 1 || value === "1").length;
32751
+ return {
32752
+ hourIndex,
32753
+ hour: `${formatCompactTime(startHour, startMinute)}-${formatCompactTime(endHour, endMinute)}`,
32754
+ timeRange: `${formatFullTime(startHour, startMinute)} - ${formatFullTime(endHour, endMinute)}`,
32755
+ idleMinutes,
32756
+ idleArray
32757
+ };
32758
+ });
32759
+ };
32760
+ var formatIdleTimestamp = ({
32761
+ shiftStart,
32762
+ hourIndex,
32763
+ minuteIndex
32764
+ }) => {
32765
+ const shiftStartTime = parseTimeString(shiftStart);
32766
+ const totalMinutes = (shiftStartTime.hour + hourIndex) * 60 + shiftStartTime.minute + minuteIndex;
32767
+ const hour = Math.floor(totalMinutes / 60) % 24;
32768
+ const minute = totalMinutes % 60;
32769
+ return formatFullTime(hour, minute);
32770
+ };
32771
+ var getHourlyIdlePeriods = ({
32772
+ idleArray,
32773
+ shiftStart,
32774
+ hourIndex
32775
+ }) => {
32776
+ if (!Array.isArray(idleArray) || idleArray.length === 0) {
32777
+ return [];
32778
+ }
32779
+ const periods = [];
32780
+ let currentRange = null;
32781
+ idleArray.forEach((value, minuteIndex) => {
32782
+ if (value === 1 || value === "1") {
32783
+ if (!currentRange) {
32784
+ currentRange = { start: minuteIndex, end: minuteIndex };
32785
+ } else {
32786
+ currentRange.end = minuteIndex;
32787
+ }
32788
+ } else if (value !== "x" && currentRange) {
32789
+ periods.push(currentRange);
32790
+ currentRange = null;
32791
+ }
32792
+ });
32793
+ if (currentRange) {
32794
+ periods.push(currentRange);
32795
+ }
32796
+ return periods.map((period) => ({
32797
+ start: period.start,
32798
+ end: period.end,
32799
+ duration: period.end - period.start + 1,
32800
+ startTime: formatIdleTimestamp({
32801
+ shiftStart,
32802
+ hourIndex,
32803
+ minuteIndex: period.start
32804
+ }),
32805
+ endTime: formatIdleTimestamp({
32806
+ shiftStart,
32807
+ hourIndex,
32808
+ minuteIndex: period.end + 1
32809
+ })
32810
+ }));
32811
+ };
32598
32812
  var CycleTimeOverTimeChart = ({
32599
32813
  data,
32600
32814
  idealCycleTime,
@@ -32604,7 +32818,8 @@ var CycleTimeOverTimeChart = ({
32604
32818
  datasetKey,
32605
32819
  className = "",
32606
32820
  showIdleTime = false,
32607
- idleTimeData = []
32821
+ idleTimeData = [],
32822
+ idleTimeSlots = []
32608
32823
  }) => {
32609
32824
  const MAX_DATA_POINTS = 40;
32610
32825
  const containerRef = React141__default.useRef(null);
@@ -32707,50 +32922,60 @@ var CycleTimeOverTimeChart = ({
32707
32922
  if (!visibleEntries.length) {
32708
32923
  return null;
32709
32924
  }
32710
- return /* @__PURE__ */ jsxs(
32711
- "div",
32712
- {
32713
- style: {
32714
- backgroundColor: "white",
32715
- border: "none",
32716
- borderRadius: "8px",
32717
- boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
32718
- padding: "8px 12px",
32719
- fontSize: "13px"
32720
- },
32721
- children: [
32722
- /* @__PURE__ */ jsx(
32723
- "div",
32724
- {
32725
- style: {
32726
- color: "#374151",
32727
- fontWeight: 600,
32728
- marginBottom: "4px"
32729
- },
32730
- children: payload[0]?.payload?.tooltip || ""
32731
- }
32732
- ),
32733
- visibleEntries.map((entry) => {
32734
- const numericValue = getNumericValue(entry.value);
32735
- if (numericValue === null) {
32736
- return null;
32737
- }
32738
- return /* @__PURE__ */ jsx(
32739
- "div",
32740
- {
32741
- style: {
32742
- color: "#4B5563",
32743
- padding: "2px 0"
32744
- },
32745
- children: entry.name === "idleMinutes" ? `Idle Time: ${numericValue.toFixed(0)} minutes` : `Cycle Time: ${numericValue.toFixed(1)} seconds`
32746
- },
32747
- `${entry.name}-${numericValue}`
32748
- );
32749
- })
32750
- ]
32751
- }
32752
- );
32753
- }, [getNumericValue]);
32925
+ const dataPoint = payload[0]?.payload || {};
32926
+ const idlePeriods = showIdleTime && typeof dataPoint.idleMinutes === "number" && dataPoint.idleMinutes > 0 ? getHourlyIdlePeriods({
32927
+ idleArray: dataPoint.idleArray,
32928
+ shiftStart,
32929
+ hourIndex: Number.isFinite(dataPoint.hourIndex) ? dataPoint.hourIndex : 0
32930
+ }) : [];
32931
+ return /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-xl shadow-xl border border-gray-100 p-4 min-w-[220px]", children: [
32932
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ jsx("p", { className: "font-semibold text-gray-900 text-sm", children: dataPoint.timeRange || dataPoint.tooltip || "" }) }),
32933
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
32934
+ visibleEntries.map((entry) => {
32935
+ const numericValue = getNumericValue(entry.value);
32936
+ if (numericValue === null) {
32937
+ return null;
32938
+ }
32939
+ if (entry.name === "idleMinutes") {
32940
+ if (!showIdleTime) return null;
32941
+ return /* @__PURE__ */ jsx("div", { className: "border-t border-gray-100 pt-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
32942
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Idle Time" }),
32943
+ /* @__PURE__ */ jsxs("span", { className: "font-semibold text-orange-600 text-sm", children: [
32944
+ numericValue.toFixed(0),
32945
+ " minutes"
32946
+ ] })
32947
+ ] }) }, `${entry.name}-${numericValue}`);
32948
+ }
32949
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
32950
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Cycle Time" }),
32951
+ /* @__PURE__ */ jsxs("span", { className: "font-semibold text-gray-900 text-sm", children: [
32952
+ numericValue.toFixed(1),
32953
+ " seconds"
32954
+ ] })
32955
+ ] }, `${entry.name}-${numericValue}`);
32956
+ }),
32957
+ idlePeriods.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 bg-gray-50 rounded-lg p-2.5", children: [
32958
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-gray-700 text-xs mb-2", children: "Idle periods:" }),
32959
+ /* @__PURE__ */ jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idlePeriods.map((period, index) => /* @__PURE__ */ jsxs("div", { className: "text-gray-600 flex items-center gap-2 text-xs", children: [
32960
+ /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0" }),
32961
+ /* @__PURE__ */ jsx("span", { children: period.duration === 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
32962
+ period.startTime,
32963
+ " (",
32964
+ period.duration,
32965
+ " min)"
32966
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
32967
+ period.startTime,
32968
+ " - ",
32969
+ period.endTime,
32970
+ " (",
32971
+ period.duration,
32972
+ " mins)"
32973
+ ] }) })
32974
+ ] }, index)) })
32975
+ ] })
32976
+ ] })
32977
+ ] });
32978
+ }, [getNumericValue, shiftStart, showIdleTime]);
32754
32979
  const renderCycleDot = React141__default.useCallback((props) => {
32755
32980
  const { cx: cx2, cy, payload } = props;
32756
32981
  const cycleTime = getNumericValue(payload?.cycleTime);
@@ -32819,14 +33044,18 @@ var CycleTimeOverTimeChart = ({
32819
33044
  r: 4,
32820
33045
  fill: "#f59e0b",
32821
33046
  stroke: "#fff",
32822
- strokeWidth: 1
33047
+ strokeWidth: 1,
33048
+ style: {
33049
+ opacity: showIdleTime ? 1 : 0,
33050
+ transition: "opacity 0.3s ease-in-out"
33051
+ }
32823
33052
  }
32824
33053
  );
32825
- }, [getNumericValue]);
33054
+ }, [getNumericValue, showIdleTime]);
32826
33055
  const renderIdleActiveDot = React141__default.useCallback((props) => {
32827
33056
  const { cx: cx2, cy, payload } = props;
32828
33057
  const idleMinutes = getNumericValue(payload?.idleMinutes);
32829
- if (idleMinutes === null) {
33058
+ if (idleMinutes === null || !showIdleTime) {
32830
33059
  return /* @__PURE__ */ jsx("g", {});
32831
33060
  }
32832
33061
  return /* @__PURE__ */ jsx(
@@ -32840,25 +33069,40 @@ var CycleTimeOverTimeChart = ({
32840
33069
  strokeWidth: 2
32841
33070
  }
32842
33071
  );
32843
- }, [getNumericValue]);
33072
+ }, [getNumericValue, showIdleTime]);
32844
33073
  const chartData = React141__default.useMemo(() => Array.from({ length: DURATION }, (_, i) => {
32845
33074
  const cycleTime = getNumericValue(finalData[i]);
32846
- const idleMinutes = showIdleTime ? getNumericValue(idleTimeData[i]) : null;
33075
+ const useIdleSlots = idleTimeSlots.length > 0;
33076
+ const idleSlot = useIdleSlots ? idleTimeSlots[i] ?? null : null;
33077
+ const idleMinutes = useIdleSlots ? idleSlot?.idleMinutes ?? null : getNumericValue(idleTimeData[i]);
32847
33078
  return {
32848
33079
  timeIndex: i,
32849
33080
  label: formatTimeLabel(i),
32850
- tooltip: formatTooltipTime(i),
33081
+ tooltip: idleSlot?.timeRange || formatTooltipTime(i),
33082
+ timeRange: idleSlot?.timeRange || formatTooltipTime(i),
33083
+ hourIndex: idleSlot?.hourIndex ?? i,
32851
33084
  cycleTime,
32852
33085
  idleMinutes,
33086
+ idleArray: idleSlot?.idleArray || [],
32853
33087
  color: cycleTime !== null && cycleTime <= idealCycleTime ? "#00AB45" : "#E34329"
32854
33088
  };
32855
- }), [DURATION, finalData, showIdleTime, idleTimeData, idealCycleTime, getNumericValue]);
33089
+ }), [DURATION, finalData, idleTimeData, idleTimeSlots, idealCycleTime, getNumericValue]);
32856
33090
  const renderLegend = () => {
32857
- if (!showIdleTime) return null;
32858
- return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-start text-[10px] font-bold text-gray-500 mb-6 tracking-[0.05em] gap-5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
32859
- /* @__PURE__ */ jsx("div", { className: "w-2.5 h-2.5 rounded-full bg-[#f59e0b]" }),
32860
- /* @__PURE__ */ jsx("span", { children: "Idle Time (min)" })
32861
- ] }) });
33091
+ return /* @__PURE__ */ jsx(
33092
+ "div",
33093
+ {
33094
+ className: "flex items-center justify-start text-[10px] font-bold text-gray-500 mb-6 tracking-[0.05em] gap-5",
33095
+ style: {
33096
+ opacity: showIdleTime ? 1 : 0,
33097
+ pointerEvents: showIdleTime ? "auto" : "none",
33098
+ transition: "opacity 0.3s ease-in-out"
33099
+ },
33100
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
33101
+ /* @__PURE__ */ jsx("div", { className: "w-2.5 h-2.5 rounded-full bg-[#f59e0b]" }),
33102
+ /* @__PURE__ */ jsx("span", { children: "Idle Time (min)" })
33103
+ ] })
33104
+ }
33105
+ );
32862
33106
  };
32863
33107
  return /* @__PURE__ */ jsxs(
32864
33108
  "div",
@@ -32929,7 +33173,7 @@ var CycleTimeOverTimeChart = ({
32929
33173
  }
32930
33174
  }
32931
33175
  ),
32932
- showIdleTime && /* @__PURE__ */ jsx(
33176
+ /* @__PURE__ */ jsx(
32933
33177
  YAxis,
32934
33178
  {
32935
33179
  yAxisId: "idle",
@@ -32938,7 +33182,11 @@ var CycleTimeOverTimeChart = ({
32938
33182
  width: 35,
32939
33183
  domain: [0, 60],
32940
33184
  tickFormatter: (value) => `${value}m`,
32941
- tick: { fontSize: 11, fill: "#f59e0b" },
33185
+ tick: {
33186
+ fontSize: 11,
33187
+ fill: showIdleTime ? "#f59e0b" : "transparent",
33188
+ style: { transition: "fill 0.3s ease-in-out" }
33189
+ },
32942
33190
  axisLine: false,
32943
33191
  tickLine: false
32944
33192
  }
@@ -32986,7 +33234,7 @@ var CycleTimeOverTimeChart = ({
32986
33234
  },
32987
33235
  `${effectiveDatasetKey}:cycle`
32988
33236
  ),
32989
- showIdleTime && /* @__PURE__ */ jsx(
33237
+ /* @__PURE__ */ jsx(
32990
33238
  Line,
32991
33239
  {
32992
33240
  type: "monotone",
@@ -33001,7 +33249,11 @@ var CycleTimeOverTimeChart = ({
33001
33249
  isAnimationActive: true,
33002
33250
  animationBegin: 300,
33003
33251
  animationDuration: 1500,
33004
- animationEasing: "ease-out"
33252
+ animationEasing: "ease-out",
33253
+ style: {
33254
+ strokeOpacity: showIdleTime ? 1 : 0,
33255
+ transition: "stroke-opacity 0.3s ease-in-out"
33256
+ }
33005
33257
  },
33006
33258
  `${effectiveDatasetKey}:idle`
33007
33259
  )
@@ -33156,89 +33408,20 @@ var HourlyOutputChartComponent = ({
33156
33408
  shiftEnd,
33157
33409
  showIdleTime = false,
33158
33410
  idleTimeHourly,
33159
- idleTimeClips,
33160
- idleTimeClipClassifications,
33161
- shiftDate,
33162
- timezone,
33163
33411
  className = ""
33164
33412
  }) => {
33165
33413
  const containerRef = React141__default.useRef(null);
33166
33414
  const [containerReady, setContainerReady] = React141__default.useState(false);
33167
33415
  const [containerWidth, setContainerWidth] = React141__default.useState(0);
33168
- const getTimeFromTimeString2 = (timeStr) => {
33169
- const [hours, minutes] = timeStr.split(":");
33170
- const hour = parseInt(hours);
33171
- const minute = parseInt(minutes || "0");
33172
- const decimalHour = hour + minute / 60;
33173
- return { hour, minute, decimalHour };
33174
- };
33175
- const shiftStartTime = getTimeFromTimeString2(shiftStart);
33176
- React141__default.useMemo(() => {
33177
- if (!shiftDate || !timezone) return null;
33178
- const hour = shiftStartTime.hour.toString().padStart(2, "0");
33179
- const minute = shiftStartTime.minute.toString().padStart(2, "0");
33180
- return fromZonedTime(`${shiftDate}T${hour}:${minute}:00`, timezone);
33181
- }, [shiftDate, timezone, shiftStartTime.hour, shiftStartTime.minute]);
33182
- const idleClipRanges = React141__default.useMemo(() => {
33183
- if (!idleTimeClips || idleTimeClips.length === 0) return [];
33184
- return idleTimeClips.map((clip) => ({
33185
- id: clip.id,
33186
- start: clip.idle_start_time ? new Date(clip.idle_start_time) : null,
33187
- end: clip.idle_end_time ? new Date(clip.idle_end_time) : null
33188
- })).filter((clip) => clip.start && clip.end);
33189
- }, [idleTimeClips]);
33190
- React141__default.useCallback((rangeStart, rangeEnd) => {
33191
- if (!rangeStart || !rangeEnd || idleClipRanges.length === 0) {
33192
- return "Reason unavailable";
33193
- }
33194
- const matchingClip = idleClipRanges.find((clip) => rangeStart >= clip.start && rangeEnd <= clip.end) || idleClipRanges.find((clip) => rangeStart < clip.end && rangeEnd > clip.start);
33195
- if (!matchingClip) {
33196
- return "Reason unavailable";
33197
- }
33198
- const classification = idleTimeClipClassifications?.[matchingClip.id];
33199
- if (!classification || classification.status === "processing") {
33200
- return "Analyzing...";
33201
- }
33202
- if (!classification.label) {
33203
- return "Reason unavailable";
33204
- }
33205
- return classification.displayName || classification.label.replace(/_/g, " ");
33206
- }, [idleClipRanges, idleTimeClipClassifications]);
33207
- const { shiftDuration, shiftEndTime, hasPartialLastHour } = React141__default.useMemo(() => {
33208
- console.log("[HourlyOutputChart] Calculating shift duration with:", {
33416
+ const idleSlots = React141__default.useMemo(
33417
+ () => buildHourlyIdleSlots({
33418
+ idleTimeHourly,
33209
33419
  shiftStart,
33210
- shiftEnd,
33211
- shiftStartTime
33212
- });
33213
- if (!shiftEnd) {
33214
- console.log("[HourlyOutputChart] No shiftEnd provided, using default 11 hours");
33215
- return {
33216
- shiftDuration: 11,
33217
- shiftEndTime: null,
33218
- hasPartialLastHour: false
33219
- };
33220
- }
33221
- const endTime = getTimeFromTimeString2(shiftEnd);
33222
- let duration = endTime.decimalHour - shiftStartTime.decimalHour;
33223
- if (duration <= 0) {
33224
- duration += 24;
33225
- }
33226
- const hasPartial = endTime.minute > 0 && endTime.minute < 60;
33227
- const hourCount = hasPartial ? Math.ceil(duration) : Math.round(duration);
33228
- console.log("[HourlyOutputChart] Shift calculation results:", {
33229
- endTime,
33230
- duration,
33231
- hasPartial,
33232
- hourCount
33233
- });
33234
- return {
33235
- shiftDuration: hourCount,
33236
- shiftEndTime: endTime,
33237
- hasPartialLastHour: hasPartial
33238
- };
33239
- }, [shiftEnd, shiftStartTime.decimalHour]);
33240
- const SHIFT_DURATION = shiftDuration;
33241
- shiftEndTime ? shiftEndTime.hour : (shiftStartTime.hour + SHIFT_DURATION) % 24;
33420
+ shiftEnd
33421
+ }),
33422
+ [idleTimeHourly, shiftStart, shiftEnd]
33423
+ );
33424
+ const SHIFT_DURATION = idleSlots.length;
33242
33425
  const [animatedData, setAnimatedData] = React141__default.useState(
33243
33426
  () => Array(SHIFT_DURATION).fill(0)
33244
33427
  );
@@ -33361,121 +33544,22 @@ var HourlyOutputChartComponent = ({
33361
33544
  }
33362
33545
  return { interval: 0, angle: -30, height: 64, tickFont: 9, tickMargin: 6 };
33363
33546
  }, [containerWidth]);
33364
- const formatHour = React141__default.useCallback((hourIndex) => {
33365
- const isLastHour = hourIndex === SHIFT_DURATION - 1;
33366
- const startDecimalHour = shiftStartTime.decimalHour + hourIndex;
33367
- const startHour = Math.floor(startDecimalHour) % 24;
33368
- const startMinute = Math.round(startDecimalHour % 1 * 60);
33369
- let endHour, endMinute;
33370
- if (isLastHour && shiftEndTime) {
33371
- endHour = shiftEndTime.hour;
33372
- endMinute = shiftEndTime.minute;
33373
- } else {
33374
- const endDecimalHour = startDecimalHour + 1;
33375
- endHour = Math.floor(endDecimalHour) % 24;
33376
- endMinute = Math.round(endDecimalHour % 1 * 60);
33377
- }
33378
- const formatTime5 = (h, m) => {
33379
- const period = h >= 12 ? "PM" : "AM";
33380
- const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
33381
- if (m === 0) {
33382
- return `${hour12}${period}`;
33383
- }
33384
- return `${hour12}:${m.toString().padStart(2, "0")}${period}`;
33385
- };
33386
- return `${formatTime5(startHour, startMinute)}-${formatTime5(endHour, endMinute)}`;
33387
- }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
33388
- const formatTimeRange2 = React141__default.useCallback((hourIndex) => {
33389
- const isLastHour = hourIndex === SHIFT_DURATION - 1;
33390
- const startDecimalHour = shiftStartTime.decimalHour + hourIndex;
33391
- const startHour = Math.floor(startDecimalHour) % 24;
33392
- const startMinute = Math.round(startDecimalHour % 1 * 60);
33393
- let endHour, endMinute;
33394
- if (isLastHour && shiftEndTime) {
33395
- endHour = shiftEndTime.hour;
33396
- endMinute = shiftEndTime.minute;
33397
- } else {
33398
- const endDecimalHour = startDecimalHour + 1;
33399
- endHour = Math.floor(endDecimalHour) % 24;
33400
- endMinute = Math.round(endDecimalHour % 1 * 60);
33401
- }
33402
- const formatTime5 = (h, m) => {
33403
- const period = h >= 12 ? "PM" : "AM";
33404
- const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
33405
- return `${hour12}:${m.toString().padStart(2, "0")} ${period}`;
33406
- };
33407
- return `${formatTime5(startHour, startMinute)} - ${formatTime5(endHour, endMinute)}`;
33408
- }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
33409
33547
  const chartData = React141__default.useMemo(() => {
33410
33548
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
33411
- const actualHour = (shiftStartTime.hour + i) % 24;
33412
- const startMinute = shiftStartTime.minute;
33413
- let idleArray = [];
33414
- let idleMinutes = 0;
33415
- if (idleTimeHourly) {
33416
- if (startMinute > 0) {
33417
- if (i === 0) {
33418
- const firstHourData = idleTimeHourly[actualHour.toString()] || [];
33419
- const nextHour = (actualHour + 1) % 24;
33420
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33421
- idleArray = [
33422
- ...firstHourData.slice(startMinute) || [],
33423
- ...nextHourData.slice(0, startMinute) || []
33424
- ];
33425
- } else if (i < SHIFT_DURATION - 1) {
33426
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33427
- const nextHour = (actualHour + 1) % 24;
33428
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33429
- idleArray = [
33430
- ...currentHourData.slice(startMinute) || [],
33431
- ...nextHourData.slice(0, startMinute) || []
33432
- ];
33433
- } else {
33434
- const hasPartialLastHour2 = shiftEndTime && shiftEndTime.minute > 0 && shiftEndTime.minute < 60;
33435
- if (hasPartialLastHour2) {
33436
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33437
- const nextHour = (actualHour + 1) % 24;
33438
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33439
- if (startMinute > 0) {
33440
- const firstPart = currentHourData.slice(startMinute) || [];
33441
- const secondPart = nextHourData.slice(0, shiftEndTime.minute) || [];
33442
- idleArray = [...firstPart, ...secondPart];
33443
- } else {
33444
- idleArray = currentHourData.slice(0, shiftEndTime.minute) || [];
33445
- }
33446
- } else {
33447
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33448
- const nextHour = (actualHour + 1) % 24;
33449
- const nextHourData = idleTimeHourly[nextHour.toString()] || [];
33450
- idleArray = [
33451
- ...currentHourData.slice(startMinute) || [],
33452
- ...nextHourData.slice(0, startMinute) || []
33453
- ];
33454
- }
33455
- }
33456
- } else {
33457
- const currentHourData = idleTimeHourly[actualHour.toString()] || [];
33458
- if (i === SHIFT_DURATION - 1 && shiftEndTime && shiftEndTime.minute > 0 && shiftEndTime.minute < 60) {
33459
- idleArray = currentHourData.slice(0, shiftEndTime.minute) || [];
33460
- } else {
33461
- idleArray = currentHourData || [];
33462
- }
33463
- }
33464
- }
33465
- idleMinutes = idleArray.filter((val) => val === "1" || val === 1).length;
33549
+ const idleSlot = idleSlots[i];
33466
33550
  return {
33467
- hourIndex: i,
33468
- hour: formatHour(i),
33469
- timeRange: formatTimeRange2(i),
33551
+ hourIndex: idleSlot?.hourIndex ?? i,
33552
+ hour: idleSlot?.hour || "",
33553
+ timeRange: idleSlot?.timeRange || "",
33470
33554
  output: animatedData[i] || 0,
33471
33555
  originalOutput: data[i] || 0,
33472
33556
  // Keep original data for labels
33473
33557
  color: (animatedData[i] || 0) >= Math.round(pphThreshold) ? "#00AB45" : "#E34329",
33474
- idleMinutes,
33475
- idleArray
33558
+ idleMinutes: idleSlot?.idleMinutes || 0,
33559
+ idleArray: idleSlot?.idleArray || []
33476
33560
  };
33477
33561
  });
33478
- }, [animatedData, data, pphThreshold, idleTimeHourly, shiftStartTime.hour, shiftStartTime.minute, shiftEndTime, formatHour, formatTimeRange2, SHIFT_DURATION]);
33562
+ }, [animatedData, data, pphThreshold, idleSlots, SHIFT_DURATION]);
33479
33563
  const IdleBar = React141__default.useMemo(() => {
33480
33564
  if (!idleBarState.visible) return null;
33481
33565
  return /* @__PURE__ */ jsx(
@@ -33637,36 +33721,11 @@ var HourlyOutputChartComponent = ({
33637
33721
  content: (props) => {
33638
33722
  if (!props.active || !props.payload || props.payload.length === 0) return null;
33639
33723
  const data2 = props.payload[0].payload;
33640
- const idleRanges = [];
33641
- if (showIdleTime && data2.idleArray) {
33642
- let currentRange = null;
33643
- data2.idleArray.forEach((val, idx) => {
33644
- if (val === "1" || val === 1) {
33645
- if (!currentRange) {
33646
- currentRange = { start: idx, end: idx };
33647
- } else {
33648
- currentRange.end = idx;
33649
- }
33650
- } else if (val !== "x" && currentRange) {
33651
- idleRanges.push(currentRange);
33652
- currentRange = null;
33653
- }
33654
- });
33655
- if (currentRange) {
33656
- idleRanges.push(currentRange);
33657
- }
33658
- }
33659
- const formatIdleTimestamp = (minuteIdx) => {
33660
- const fallbackIndex = chartData.findIndex((item) => item.timeRange === data2.timeRange);
33661
- const hourOffset = Number.isFinite(data2.hourIndex) ? data2.hourIndex : fallbackIndex;
33662
- const safeHourOffset = hourOffset >= 0 ? hourOffset : 0;
33663
- const totalMinutes = (shiftStartTime.hour + safeHourOffset) * 60 + shiftStartTime.minute + minuteIdx;
33664
- const hour = Math.floor(totalMinutes / 60) % 24;
33665
- const minute = totalMinutes % 60;
33666
- const period = hour >= 12 ? "PM" : "AM";
33667
- const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
33668
- return `${hour12}:${minute.toString().padStart(2, "0")} ${period}`;
33669
- };
33724
+ const idlePeriods = showIdleTime ? getHourlyIdlePeriods({
33725
+ idleArray: data2.idleArray,
33726
+ shiftStart,
33727
+ hourIndex: Number.isFinite(data2.hourIndex) ? data2.hourIndex : 0
33728
+ }) : [];
33670
33729
  return /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-xl shadow-xl border border-gray-100 p-4 min-w-[220px]", children: [
33671
33730
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ jsx("p", { className: "font-semibold text-gray-900 text-sm", children: data2.timeRange }) }),
33672
33731
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
@@ -33685,27 +33744,22 @@ var HourlyOutputChartComponent = ({
33685
33744
  " minutes"
33686
33745
  ] })
33687
33746
  ] }) }),
33688
- idleRanges.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 bg-gray-50 rounded-lg p-2.5", children: [
33747
+ idlePeriods.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 bg-gray-50 rounded-lg p-2.5", children: [
33689
33748
  /* @__PURE__ */ jsx("p", { className: "font-medium text-gray-700 text-xs mb-2", children: "Idle periods:" }),
33690
- /* @__PURE__ */ jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idleRanges.map((range, index) => {
33691
- const duration = range.end - range.start + 1;
33692
- const startTime = formatIdleTimestamp(range.start);
33693
- const endTime = formatIdleTimestamp(range.end + 1);
33694
- const fallbackIndex = chartData.findIndex((item) => item.timeRange === data2.timeRange);
33695
- Number.isFinite(data2.hourIndex) ? data2.hourIndex : fallbackIndex;
33749
+ /* @__PURE__ */ jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idlePeriods.map((period, index) => {
33696
33750
  return /* @__PURE__ */ jsxs("div", { className: "text-gray-600 flex items-center gap-2 text-xs", children: [
33697
33751
  /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0" }),
33698
- /* @__PURE__ */ jsx("span", { children: duration === 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
33699
- startTime,
33752
+ /* @__PURE__ */ jsx("span", { children: period.duration === 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
33753
+ period.startTime,
33700
33754
  " (",
33701
- duration,
33755
+ period.duration,
33702
33756
  " min)"
33703
33757
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
33704
- startTime,
33758
+ period.startTime,
33705
33759
  " - ",
33706
- endTime,
33760
+ period.endTime,
33707
33761
  " (",
33708
- duration,
33762
+ period.duration,
33709
33763
  " mins)"
33710
33764
  ] }) })
33711
33765
  ] }, index);
@@ -33860,9 +33914,8 @@ var HourlyOutputChart = React141__default.memo(HourlyOutputChartComponent, (prev
33860
33914
  HourlyOutputChart.displayName = "HourlyOutputChart";
33861
33915
 
33862
33916
  // src/components/dashboard/grid/videoGridMetricUtils.ts
33863
- var VIDEO_GRID_LEGEND_LABEL = "7 Minute Efficiency";
33864
33917
  var MAP_GRID_LEGEND_LABEL = "Efficiency";
33865
- var MIXED_VIDEO_GRID_LEGEND_LABEL = "Efficiency";
33918
+ var MIXED_VIDEO_GRID_LEGEND_LABEL = "Flow Efficiency";
33866
33919
  var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
33867
33920
  var isVideoGridRecentFlowEnabled = (workspace) => isRecentFlowVideoGridMetricMode(
33868
33921
  workspace.video_grid_metric_mode,
@@ -33961,8 +34014,15 @@ var getVideoGridLegendLabel = (workspaces) => {
33961
34014
  if (recentFlowEnabledCount === 0) {
33962
34015
  return MAP_GRID_LEGEND_LABEL;
33963
34016
  }
34017
+ const recentFlowWindows = new Set(
34018
+ visibleWorkspaces.filter(isVideoGridRecentFlowEnabled).map((workspace) => workspace.recent_flow_window_minutes ?? 7).filter((value) => typeof value === "number" && Number.isFinite(value))
34019
+ );
33964
34020
  if (recentFlowEnabledCount === visibleWorkspaces.length) {
33965
- return VIDEO_GRID_LEGEND_LABEL;
34021
+ if (recentFlowWindows.size === 1) {
34022
+ const [windowMinutes] = Array.from(recentFlowWindows);
34023
+ return `${windowMinutes} Minute Efficiency`;
34024
+ }
34025
+ return MIXED_VIDEO_GRID_LEGEND_LABEL;
33966
34026
  }
33967
34027
  return MIXED_VIDEO_GRID_LEGEND_LABEL;
33968
34028
  };
@@ -35909,7 +35969,7 @@ var HourlyUptimeChartComponent = ({
35909
35969
  idleRanges.push(currentRange);
35910
35970
  }
35911
35971
  }
35912
- const formatIdleTimestamp = (minuteIdx) => {
35972
+ const formatIdleTimestamp2 = (minuteIdx) => {
35913
35973
  const totalMinutes = (shiftStartTime.hour + data.hourIndex) * 60 + shiftStartTime.minute + minuteIdx;
35914
35974
  const hour = Math.floor(totalMinutes / 60) % 24;
35915
35975
  const minute = totalMinutes % 60;
@@ -35939,8 +35999,8 @@ var HourlyUptimeChartComponent = ({
35939
35999
  /* @__PURE__ */ jsx("p", { className: "font-medium text-gray-700 text-xs mb-2", children: "Idle periods:" }),
35940
36000
  /* @__PURE__ */ jsx("div", { className: "space-y-1 max-h-32 overflow-y-auto pr-1", children: idleRanges.map((range, index) => {
35941
36001
  const duration = range.end - range.start + 1;
35942
- const startTime = formatIdleTimestamp(range.start);
35943
- const endTime = formatIdleTimestamp(range.end + 1);
36002
+ const startTime = formatIdleTimestamp2(range.start);
36003
+ const endTime = formatIdleTimestamp2(range.end + 1);
35944
36004
  return /* @__PURE__ */ jsxs("div", { className: "text-gray-600 flex items-center gap-2 text-xs", children: [
35945
36005
  /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-1.5 bg-orange-400 rounded-full flex-shrink-0" }),
35946
36006
  /* @__PURE__ */ jsx("span", { children: duration === 1 ? `${startTime} (${duration} min)` : `${startTime} - ${endTime} (${duration} mins)` })
@@ -69187,56 +69247,43 @@ var WorkspaceDetailView = ({
69187
69247
  const canToggleChartIdleTime = !isUptimeMode && !shouldShowCycleTimeLoadingState && !shouldShowCycleTimeUnavailableState;
69188
69248
  const idleClipDate = date || workspace?.date || calculatedOperationalDate || getOperationalDate(timezone);
69189
69249
  const idleClipShiftId = parsedShiftId ?? workspace?.shift_id;
69190
- const rawHourlyIdleMinutes = useMemo(() => {
69191
- if (!shouldShowCycleTimeChart || !workspace?.idle_time_hourly || !workspace?.shift_start) return [];
69192
- const parseTimeToMinutes4 = (time2) => {
69193
- const [h, m] = time2.split(":").map(Number);
69194
- if (!Number.isFinite(h) || !Number.isFinite(m)) return 0;
69195
- return h * 60 + m;
69196
- };
69197
- const startTotal = parseTimeToMinutes4(workspace.shift_start);
69198
- const endTotalRaw = workspace.shift_end ? parseTimeToMinutes4(workspace.shift_end) : startTotal + 11 * 60;
69199
- const endTotal = endTotalRaw <= startTotal ? endTotalRaw + 24 * 60 : endTotalRaw;
69200
- const shiftDuration = Math.max(60, endTotal - startTotal);
69201
- const totalHourSlots = Math.max(1, Math.ceil(shiftDuration / 60));
69202
- const idleTimeHourlyObj = workspace.idle_time_hourly || {};
69203
- const result = Array(totalHourSlots).fill(0);
69204
- for (let i = 0; i < totalHourSlots; i++) {
69205
- const startSlotMins = startTotal + i * 60;
69206
- let endSlotMins = startSlotMins + 60;
69207
- if (endSlotMins > endTotal) endSlotMins = endTotal;
69208
- let idleCount = 0;
69209
- for (let m = startSlotMins; m < endSlotMins; m++) {
69210
- const hourKey = Math.floor(m / 60) % 24;
69211
- const minuteKey = m % 60;
69212
- const hourData = idleTimeHourlyObj[hourKey.toString()] || [];
69213
- if (Array.isArray(hourData)) {
69214
- const val = hourData[minuteKey];
69215
- if (val === 1 || val === "1") {
69216
- idleCount++;
69217
- }
69218
- }
69219
- }
69220
- result[i] = idleCount;
69221
- }
69222
- return result;
69223
- }, [shouldShowCycleTimeChart, workspace?.idle_time_hourly, workspace?.shift_start, workspace?.shift_end]);
69250
+ const rawHourlyIdleSlots = useMemo(
69251
+ () => !shouldShowCycleTimeChart || !authoritativeCycleMetrics?.shift_start ? [] : buildHourlyIdleSlots({
69252
+ idleTimeHourly: authoritativeCycleMetrics.idle_time_hourly,
69253
+ shiftStart: authoritativeCycleMetrics.shift_start,
69254
+ shiftEnd: authoritativeCycleMetrics.shift_end
69255
+ }),
69256
+ [
69257
+ shouldShowCycleTimeChart,
69258
+ authoritativeCycleMetrics?.idle_time_hourly,
69259
+ authoritativeCycleMetrics?.shift_start,
69260
+ authoritativeCycleMetrics?.shift_end
69261
+ ]
69262
+ );
69263
+ const rawHourlyIdleMinutes = useMemo(
69264
+ () => rawHourlyIdleSlots.map((slot) => slot.idleMinutes),
69265
+ [rawHourlyIdleSlots]
69266
+ );
69224
69267
  const hourlyIdleMinutes = useMemo(
69225
69268
  () => maskFutureHourlySeries({
69226
69269
  data: rawHourlyIdleMinutes,
69227
- shiftStart: workspace?.shift_start,
69228
- shiftEnd: workspace?.shift_end,
69270
+ shiftStart: authoritativeCycleMetrics?.shift_start,
69271
+ shiftEnd: authoritativeCycleMetrics?.shift_end,
69229
69272
  shiftDate: idleClipDate,
69230
69273
  timezone: effectiveCycleTimeTimezone
69231
69274
  }),
69232
69275
  [
69233
69276
  rawHourlyIdleMinutes,
69234
- workspace?.shift_start,
69235
- workspace?.shift_end,
69277
+ authoritativeCycleMetrics?.shift_start,
69278
+ authoritativeCycleMetrics?.shift_end,
69236
69279
  idleClipDate,
69237
69280
  effectiveCycleTimeTimezone
69238
69281
  ]
69239
69282
  );
69283
+ const hourlyIdleSlots = useMemo(
69284
+ () => rawHourlyIdleSlots.map((slot, index) => hourlyIdleMinutes[index] === null ? null : slot),
69285
+ [rawHourlyIdleSlots, hourlyIdleMinutes]
69286
+ );
69240
69287
  const cycleTimeUnavailableView = useMemo(() => /* @__PURE__ */ 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: [
69241
69288
  /* @__PURE__ */ jsx("h4", { className: "text-base font-semibold text-amber-900", children: "Cycle data unavailable" }),
69242
69289
  /* @__PURE__ */ 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." }),
@@ -69792,7 +69839,8 @@ var WorkspaceDetailView = ({
69792
69839
  xAxisMode: "hourly",
69793
69840
  datasetKey: cycleTimeDatasetKey,
69794
69841
  showIdleTime: showChartIdleTime,
69795
- idleTimeData: hourlyIdleMinutes
69842
+ idleTimeData: hourlyIdleMinutes,
69843
+ idleTimeSlots: hourlyIdleSlots
69796
69844
  }
69797
69845
  ) : null : /* @__PURE__ */ jsx(
69798
69846
  HourlyOutputChart2,
@@ -69921,7 +69969,8 @@ var WorkspaceDetailView = ({
69921
69969
  xAxisMode: "hourly",
69922
69970
  datasetKey: cycleTimeDatasetKey,
69923
69971
  showIdleTime: showChartIdleTime,
69924
- idleTimeData: hourlyIdleMinutes
69972
+ idleTimeData: hourlyIdleMinutes,
69973
+ idleTimeSlots: hourlyIdleSlots
69925
69974
  }
69926
69975
  ) : null : /* @__PURE__ */ jsx(
69927
69976
  HourlyOutputChart2,