@optifye/dashboard-core 6.9.5 → 6.9.7

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
@@ -9603,8 +9603,13 @@ var getAllWorkspaceDisplayNamesAsync = async (companyId, lineId) => {
9603
9603
  return { ...runtimeWorkspaceDisplayNames[lineId] };
9604
9604
  }
9605
9605
  const allNames = {};
9606
- Object.values(runtimeWorkspaceDisplayNames).forEach((lineNames) => {
9607
- Object.assign(allNames, lineNames);
9606
+ Object.entries(runtimeWorkspaceDisplayNames).forEach(([lineId2, lineNames]) => {
9607
+ Object.entries(lineNames).forEach(([workspaceId, displayName]) => {
9608
+ allNames[`${lineId2}_${workspaceId}`] = displayName;
9609
+ if (!allNames[workspaceId]) {
9610
+ allNames[workspaceId] = displayName;
9611
+ }
9612
+ });
9608
9613
  });
9609
9614
  return allNames;
9610
9615
  };
@@ -24729,7 +24734,11 @@ var VideoGridView = React23__namespace.default.memo(({
24729
24734
  isVeryLowEfficiency,
24730
24735
  cropping: workspaceCropping,
24731
24736
  canvasFps: canvasConfig?.fps,
24732
- displayName: displayNames[workspace.workspace_name] || workspace.workspace_name,
24737
+ displayName: (
24738
+ // Create line-aware lookup key: lineId_workspaceName
24739
+ // This ensures correct mapping when multiple lines have same workspace names
24740
+ displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || displayNames[workspace.workspace_name] || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id)
24741
+ ),
24733
24742
  useRAF: canvasConfig?.useRAF,
24734
24743
  compact: !selectedLine,
24735
24744
  onMouseEnter: onWorkspaceHover ? () => onWorkspaceHover(workspaceId) : void 0,
@@ -24778,7 +24787,7 @@ var MapGridView = React23__namespace.default.memo(({
24778
24787
  efficiency: workspace.efficiency,
24779
24788
  action_count: workspace.action_count
24780
24789
  });
24781
- const displayName = displayNames[workspace.workspace_name] || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24790
+ const displayName = displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || displayNames[workspace.workspace_name] || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24782
24791
  const navParams = getWorkspaceNavigationParams(workspaceId, displayName, workspace.line_id);
24783
24792
  router$1.push(`/workspace/${workspaceId}${navParams}`);
24784
24793
  }, [router$1, displayNames]);
@@ -24807,7 +24816,7 @@ var MapGridView = React23__namespace.default.memo(({
24807
24816
  if (!workspace) return null;
24808
24817
  const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
24809
24818
  getPerformanceColor(workspace.efficiency);
24810
- const workspaceDisplayName = displayNames[workspace.workspace_name] || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24819
+ const workspaceDisplayName = displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || displayNames[workspace.workspace_name] || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24811
24820
  return /* @__PURE__ */ jsxRuntime.jsx(
24812
24821
  motion.div,
24813
24822
  {
@@ -25653,18 +25662,30 @@ var BreakNotificationPopup = ({
25653
25662
  className = "",
25654
25663
  lineNames = {}
25655
25664
  }) => {
25656
- const [isDismissed, setIsDismissed] = React23.useState(false);
25665
+ const [currentIndex, setCurrentIndex] = React23.useState(0);
25666
+ const [visibleBreaks, setVisibleBreaks] = React23.useState(activeBreaks);
25657
25667
  const [currentTime, setCurrentTime] = React23.useState(/* @__PURE__ */ new Date());
25668
+ React23.useEffect(() => {
25669
+ setVisibleBreaks(activeBreaks);
25670
+ if (currentIndex >= activeBreaks.length) {
25671
+ setCurrentIndex(Math.max(0, activeBreaks.length - 1));
25672
+ }
25673
+ }, [activeBreaks, currentIndex]);
25658
25674
  React23.useEffect(() => {
25659
25675
  const timer = setInterval(() => {
25660
25676
  setCurrentTime(/* @__PURE__ */ new Date());
25661
25677
  }, 6e4);
25662
25678
  return () => clearInterval(timer);
25663
25679
  }, []);
25664
- const handleDismiss = () => {
25665
- setIsDismissed(true);
25680
+ const handleDismissAll = () => {
25666
25681
  onDismiss?.();
25667
25682
  };
25683
+ const handleNext = () => {
25684
+ setCurrentIndex((prev) => (prev + 1) % visibleBreaks.length);
25685
+ };
25686
+ const handlePrevious = () => {
25687
+ setCurrentIndex((prev) => (prev - 1 + visibleBreaks.length) % visibleBreaks.length);
25688
+ };
25668
25689
  const formatTime3 = (minutes) => {
25669
25690
  const hours = Math.floor(minutes / 60);
25670
25691
  const mins = minutes % 60;
@@ -25673,69 +25694,130 @@ var BreakNotificationPopup = ({
25673
25694
  }
25674
25695
  return `${mins}m`;
25675
25696
  };
25676
- if (!isVisible || isDismissed || activeBreaks.length === 0) {
25697
+ const formatTo12Hour = (time24) => {
25698
+ const [hours, minutes] = time24.split(":").map(Number);
25699
+ if (isNaN(hours) || isNaN(minutes)) {
25700
+ return time24;
25701
+ }
25702
+ const period = hours >= 12 ? "PM" : "AM";
25703
+ const hours12 = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
25704
+ return `${hours12}:${minutes.toString().padStart(2, "0")} ${period}`;
25705
+ };
25706
+ if (!isVisible || visibleBreaks.length === 0) {
25677
25707
  return null;
25678
25708
  }
25679
- return /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { children: activeBreaks.map((breakItem, index) => /* @__PURE__ */ jsxRuntime.jsx(
25680
- motion.div,
25681
- {
25682
- initial: { opacity: 0, x: 100, y: -20 },
25683
- animate: { opacity: 1, x: 0, y: 0 },
25684
- exit: { opacity: 0, x: 100, y: -20 },
25685
- transition: { duration: 0.3, ease: "easeOut", delay: index * 0.1 },
25686
- className: `fixed right-4 z-50 max-w-xs w-full ${className}`,
25687
- style: { top: `${6 + index * 12}rem` },
25688
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white text-gray-900 rounded-lg border border-gray-200 shadow-lg overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between", children: [
25689
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start space-x-3 flex-1", children: [
25690
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(AxelOrb, { size: "md" }) }),
25691
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
25692
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [
25693
- /* @__PURE__ */ jsxRuntime.jsxs("h4", { className: "font-semibold text-sm text-gray-900", children: [
25694
- breakItem.remarks || "Break",
25695
- (activeBreaks.length > 1 || lineNames[breakItem.lineId]) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-gray-500 ml-1", children: [
25696
- "\u2022 ",
25697
- lineNames[breakItem.lineId] || `Line ${breakItem.lineId.substring(0, 8)}`
25698
- ] })
25709
+ const currentBreak = visibleBreaks[currentIndex];
25710
+ return /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `fixed right-4 top-24 z-50 max-w-xs w-full ${className}`, children: [
25711
+ visibleBreaks.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
25712
+ /* @__PURE__ */ jsxRuntime.jsx(
25713
+ "div",
25714
+ {
25715
+ className: "absolute inset-0 bg-white rounded-lg border border-gray-300 shadow-lg",
25716
+ style: {
25717
+ transform: "translateY(12px) scale(0.94)",
25718
+ opacity: 0.6,
25719
+ zIndex: -2
25720
+ }
25721
+ }
25722
+ ),
25723
+ visibleBreaks.length > 2 && /* @__PURE__ */ jsxRuntime.jsx(
25724
+ "div",
25725
+ {
25726
+ className: "absolute inset-0 bg-white rounded-lg border border-gray-200 shadow-md",
25727
+ style: {
25728
+ transform: "translateY(6px) scale(0.97)",
25729
+ opacity: 0.8,
25730
+ zIndex: -1
25731
+ }
25732
+ }
25733
+ )
25734
+ ] }),
25735
+ /* @__PURE__ */ jsxRuntime.jsx(
25736
+ motion.div,
25737
+ {
25738
+ initial: { opacity: 0, x: 100, y: -20 },
25739
+ animate: { opacity: 1, x: 0, y: 0 },
25740
+ exit: { opacity: 0, x: -100, y: -20 },
25741
+ transition: { duration: 0.3, ease: "easeOut" },
25742
+ className: "relative z-10",
25743
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white text-gray-900 rounded-lg border border-gray-200 shadow-xl overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between", children: [
25744
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start space-x-3 flex-1", children: [
25745
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(AxelOrb, { size: "md" }) }),
25746
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
25747
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [
25748
+ /* @__PURE__ */ jsxRuntime.jsxs("h4", { className: "font-semibold text-sm text-gray-900", children: [
25749
+ currentBreak.remarks || "Break",
25750
+ lineNames[currentBreak.lineId] && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-gray-500 ml-1", children: [
25751
+ "\u2022 ",
25752
+ lineNames[currentBreak.lineId]
25753
+ ] })
25754
+ ] }),
25755
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Coffee, { className: "w-4 h-4 text-amber-500" })
25699
25756
  ] }),
25700
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Coffee, { className: "w-4 h-4 text-amber-500" })
25701
- ] }),
25702
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-600 leading-relaxed mb-2", children: [
25703
- "Currently active from ",
25704
- breakItem.startTime,
25705
- " to ",
25706
- breakItem.endTime,
25707
- ". ",
25708
- formatTime3(breakItem.elapsedMinutes),
25709
- " elapsed of ",
25710
- formatTime3(breakItem.duration),
25711
- " total."
25712
- ] }),
25713
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-gray-200 rounded-full h-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(
25714
- "div",
25715
- {
25716
- className: "h-1.5 bg-blue-500 rounded-full transition-all duration-1000",
25717
- style: {
25718
- width: `${Math.min(100, Math.max(0, breakItem.elapsedMinutes / breakItem.duration * 100))}%`
25757
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-600 mb-1", children: [
25758
+ formatTo12Hour(currentBreak.startTime),
25759
+ " - ",
25760
+ formatTo12Hour(currentBreak.endTime)
25761
+ ] }),
25762
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 mb-2", children: [
25763
+ formatTime3(currentBreak.elapsedMinutes),
25764
+ " elapsed of ",
25765
+ formatTime3(currentBreak.duration),
25766
+ " total"
25767
+ ] }),
25768
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-gray-200 rounded-full h-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(
25769
+ "div",
25770
+ {
25771
+ className: "h-1.5 bg-blue-500 rounded-full transition-all duration-1000",
25772
+ style: {
25773
+ width: `${Math.min(100, Math.max(0, currentBreak.elapsedMinutes / currentBreak.duration * 100))}%`
25774
+ }
25719
25775
  }
25720
- }
25721
- ) }) })
25722
- ] })
25723
- ] }),
25724
- /* @__PURE__ */ jsxRuntime.jsx(
25725
- "button",
25726
- {
25727
- onClick: handleDismiss,
25728
- onTouchStart: () => {
25729
- },
25730
- className: "ml-2 text-gray-400 hover:text-gray-600 transition-colors p-2 sm:p-1 rounded-full hover:bg-gray-100 active:bg-gray-200 touch-manipulation min-h-[44px] min-w-[44px] sm:min-h-0 sm:min-w-0 flex items-center justify-center flex-shrink-0",
25731
- "aria-label": "Dismiss notification",
25732
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4 sm:w-3 sm:h-3" })
25733
- }
25734
- )
25735
- ] }) }) })
25736
- },
25737
- `${breakItem.lineId}-${breakItem.startTime}-${index}`
25738
- )) });
25776
+ ) }),
25777
+ visibleBreaks.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 pt-2 border-t border-gray-100", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
25778
+ /* @__PURE__ */ jsxRuntime.jsx(
25779
+ "button",
25780
+ {
25781
+ onClick: handlePrevious,
25782
+ className: "p-1 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full transition-colors",
25783
+ "aria-label": "Previous break",
25784
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "w-4 h-4" })
25785
+ }
25786
+ ),
25787
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500", children: [
25788
+ currentIndex + 1,
25789
+ " of ",
25790
+ visibleBreaks.length,
25791
+ " breaks"
25792
+ ] }),
25793
+ /* @__PURE__ */ jsxRuntime.jsx(
25794
+ "button",
25795
+ {
25796
+ onClick: handleNext,
25797
+ className: "p-1 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full transition-colors",
25798
+ "aria-label": "Next break",
25799
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "w-4 h-4" })
25800
+ }
25801
+ )
25802
+ ] }) })
25803
+ ] })
25804
+ ] }),
25805
+ /* @__PURE__ */ jsxRuntime.jsx(
25806
+ "button",
25807
+ {
25808
+ onClick: handleDismissAll,
25809
+ onTouchStart: () => {
25810
+ },
25811
+ className: "ml-2 text-gray-400 hover:text-gray-600 transition-colors p-2 sm:p-1 rounded-full hover:bg-gray-100 active:bg-gray-200 touch-manipulation min-h-[44px] min-w-[44px] sm:min-h-0 sm:min-w-0 flex items-center justify-center flex-shrink-0",
25812
+ "aria-label": "Dismiss all breaks",
25813
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-4 h-4 sm:w-3 sm:h-3" })
25814
+ }
25815
+ )
25816
+ ] }) }) })
25817
+ },
25818
+ currentIndex
25819
+ )
25820
+ ] }) });
25739
25821
  };
25740
25822
  async function fetchAxelNotifications() {
25741
25823
  try {
@@ -26699,8 +26781,11 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
26699
26781
  experimentalLLHLS: false,
26700
26782
  // Disable Low Latency HLS for VOD
26701
26783
  // Connection settings
26702
- enableWorker: true,
26703
- // Use web worker for better performance
26784
+ // Chrome 130+ started throwing "Cannot perform Construct on a detached ArrayBuffer"
26785
+ // whenever the transmux worker tried to rehydrate transferred buffers that originated
26786
+ // from Blob-based playlists. Disabling the worker keeps playback stable at the cost
26787
+ // of slightly higher main-thread usage, which is acceptable for the dashboard usage.
26788
+ enableWorker: false,
26704
26789
  progressive: true,
26705
26790
  // Progressive download
26706
26791
  // Adaptive bitrate settings (if multi-quality available)
@@ -27710,10 +27795,30 @@ function useWorkspaceCrop(workspaceId) {
27710
27795
  }, [workspaceId]);
27711
27796
  return { crop, isLoading, error };
27712
27797
  }
27713
- var getSeverityIcon = (severity, categoryId) => {
27798
+ var parseCycleTime = (value) => {
27799
+ if (typeof value === "number" && Number.isFinite(value)) {
27800
+ return value;
27801
+ }
27802
+ if (typeof value === "string") {
27803
+ const parsed = Number(value);
27804
+ return Number.isFinite(parsed) ? parsed : null;
27805
+ }
27806
+ return null;
27807
+ };
27808
+ var extractCycleTimeSeconds = (clip) => {
27809
+ return parseCycleTime(clip?.cycleTimeSeconds) ?? parseCycleTime(clip?.cycle_time_seconds) ?? parseCycleTime(clip?.duration) ?? parseCycleTime(clip?.original_task_metadata?.cycle_time) ?? null;
27810
+ };
27811
+ var getSeverityIcon = (severity, categoryId, cycleTimeSeconds, targetCycleTime) => {
27714
27812
  if (categoryId === "idle_time" || categoryId === "low_value" || categoryId === "longest-idles") {
27715
27813
  return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-3 w-3 text-red-500" });
27716
27814
  }
27815
+ if (categoryId === "cycle_completion" && targetCycleTime && targetCycleTime > 0 && cycleTimeSeconds !== null && cycleTimeSeconds !== void 0) {
27816
+ if (cycleTimeSeconds <= targetCycleTime) {
27817
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle, { className: "h-3 w-3 text-green-500" });
27818
+ } else {
27819
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-3 w-3 text-red-500" });
27820
+ }
27821
+ }
27717
27822
  switch (severity) {
27718
27823
  case "high":
27719
27824
  return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-3 w-3 text-red-500" });
@@ -27768,7 +27873,8 @@ var FileManagerFilters = ({
27768
27873
  workspaceId,
27769
27874
  date,
27770
27875
  shift,
27771
- className = ""
27876
+ className = "",
27877
+ targetCycleTime = null
27772
27878
  }) => {
27773
27879
  const [expandedNodes, setExpandedNodes] = React23.useState(/* @__PURE__ */ new Set());
27774
27880
  const [startTime, setStartTime] = React23.useState("");
@@ -27788,6 +27894,26 @@ var FileManagerFilters = ({
27788
27894
  const [percentileCounts, setPercentileCounts] = React23.useState({});
27789
27895
  const [percentileClips, setPercentileClips] = React23.useState({});
27790
27896
  const [loadingPercentile, setLoadingPercentile] = React23.useState(false);
27897
+ const resolvedTargetCycleTime = targetCycleTime && targetCycleTime > 0 ? targetCycleTime : null;
27898
+ const getClipBadge = React23.useCallback((node) => {
27899
+ if (node.categoryId === "idle_time" || node.categoryId === "low_value") {
27900
+ return { text: "Idle", className: "bg-red-100 text-red-700" };
27901
+ }
27902
+ if (node.categoryId === "cycle_completion" && resolvedTargetCycleTime && node.cycleTimeSeconds !== void 0 && node.cycleTimeSeconds !== null) {
27903
+ const isFast = node.cycleTimeSeconds <= resolvedTargetCycleTime;
27904
+ return {
27905
+ text: isFast ? "Fast" : "Slow",
27906
+ className: isFast ? "bg-green-100 text-green-700" : "bg-red-100 text-red-700"
27907
+ };
27908
+ }
27909
+ if (node.severity === "high") {
27910
+ return { text: "Slow", className: "bg-red-100 text-red-700" };
27911
+ }
27912
+ if (node.severity === "medium") {
27913
+ return { text: "Average", className: "bg-yellow-100 text-yellow-700" };
27914
+ }
27915
+ return { text: "Fast", className: "bg-green-100 text-green-700" };
27916
+ }, [resolvedTargetCycleTime]);
27791
27917
  const fetchClipMetadata = React23.useCallback(async (categoryId, page = 1) => {
27792
27918
  if (!workspaceId || !date || shift === void 0) {
27793
27919
  console.warn("[FileManager] Missing required params for clip metadata fetch");
@@ -28025,18 +28151,20 @@ var FileManagerFilters = ({
28025
28151
  timeZone: timezone
28026
28152
  // Use database timezone for display
28027
28153
  });
28154
+ const cycleTime = extractCycleTimeSeconds(clip);
28028
28155
  return {
28029
28156
  id: clip.id,
28030
28157
  label: `${timeString}${clip.duration && category.id !== "idle_time" ? ` - (${clip.duration.toFixed(1)}s)` : ""}`,
28031
28158
  type: "video",
28032
- icon: getSeverityIcon(clip.severity, category.id),
28159
+ icon: getSeverityIcon(clip.severity, category.id, cycleTime, resolvedTargetCycleTime),
28033
28160
  timestamp: clip.clip_timestamp,
28034
28161
  severity: clip.severity,
28035
28162
  clipId: clip.clipId,
28036
28163
  // Store stable UUID for navigation
28037
28164
  categoryId: category.id,
28038
- clipPosition: index + 1
28165
+ clipPosition: index + 1,
28039
28166
  // Store 1-based position
28167
+ cycleTimeSeconds: cycleTime
28040
28168
  };
28041
28169
  });
28042
28170
  regularCategoryNodes.push({
@@ -28075,15 +28203,17 @@ var FileManagerFilters = ({
28075
28203
  minute: "2-digit",
28076
28204
  timeZone: timezone
28077
28205
  });
28206
+ const cycleTime = extractCycleTimeSeconds(clip);
28078
28207
  return {
28079
28208
  id: clip.id,
28080
28209
  label: `${timeString}${clip.cycle_time_seconds ? ` - (${clip.cycle_time_seconds.toFixed(1)}s)` : ""}`,
28081
28210
  type: "video",
28082
- icon: getSeverityIcon(clip.severity, "fast-cycles"),
28211
+ icon: getSeverityIcon(clip.severity, "fast-cycles", cycleTime, resolvedTargetCycleTime),
28083
28212
  timestamp: clip.creation_timestamp,
28084
28213
  severity: clip.severity,
28085
28214
  clipId: clip.id,
28086
- categoryId: "fast-cycles"
28215
+ categoryId: "fast-cycles",
28216
+ cycleTimeSeconds: cycleTime
28087
28217
  };
28088
28218
  })
28089
28219
  },
@@ -28105,15 +28235,17 @@ var FileManagerFilters = ({
28105
28235
  minute: "2-digit",
28106
28236
  timeZone: timezone
28107
28237
  });
28238
+ const cycleTime = extractCycleTimeSeconds(clip);
28108
28239
  return {
28109
28240
  id: clip.id,
28110
28241
  label: `${timeString}${clip.cycle_time_seconds ? ` - (${clip.cycle_time_seconds.toFixed(1)}s)` : ""}`,
28111
28242
  type: "video",
28112
- icon: getSeverityIcon(clip.severity, "slow-cycles"),
28243
+ icon: getSeverityIcon(clip.severity, "slow-cycles", cycleTime, resolvedTargetCycleTime),
28113
28244
  timestamp: clip.creation_timestamp,
28114
28245
  severity: clip.severity,
28115
28246
  clipId: clip.id,
28116
- categoryId: "slow-cycles"
28247
+ categoryId: "slow-cycles",
28248
+ cycleTimeSeconds: cycleTime
28117
28249
  };
28118
28250
  })
28119
28251
  }
@@ -28234,7 +28366,10 @@ var FileManagerFilters = ({
28234
28366
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: `font-semibold tracking-tight ${node.type === "category" || node.type === "percentile-category" ? "text-slate-800 text-sm" : "text-slate-700 text-xs"} ${isCurrentVideo ? "text-blue-700 font-bold" : ""} group-hover:text-slate-900 transition-colors duration-200`, children: node.label }),
28235
28367
  node.type === "category" && categories.find((c) => c.id === node.id)?.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-slate-500 mt-0.5 font-normal", children: categories.find((c) => c.id === node.id)?.description }),
28236
28368
  node.type === "percentile-category" && node.subtitle && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-slate-500 mt-0.5 font-normal", children: node.subtitle }),
28237
- node.type === "video" && node.severity && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-slate-500 capitalize mt-0.5 font-medium", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center px-1.5 py-0.5 rounded-md text-xs font-medium ${node.categoryId === "idle_time" || node.categoryId === "low_value" ? "bg-red-100 text-red-700" : node.severity === "high" ? "bg-red-100 text-red-700" : node.severity === "medium" ? "bg-yellow-100 text-yellow-700" : "bg-green-100 text-green-700"}`, children: node.categoryId === "idle_time" || node.categoryId === "low_value" ? "Idle" : node.severity === "low" ? "Fast" : node.severity === "medium" ? "Average" : "Slow" }) })
28369
+ node.type === "video" && (node.severity || node.categoryId === "cycle_completion" || node.categoryId === "idle_time" || node.categoryId === "low_value") && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-slate-500 capitalize mt-0.5 font-medium", children: (() => {
28370
+ const badge = getClipBadge(node);
28371
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center px-1.5 py-0.5 rounded-md text-xs font-medium ${badge.className}`, children: badge.text });
28372
+ })() })
28238
28373
  ] }),
28239
28374
  node.count !== void 0 && (node.type === "category" || node.type === "percentile-category") && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center ml-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `px-2.5 py-1 text-sm font-bold rounded-lg shadow-sm border backdrop-blur-sm flex-shrink-0 group-hover:scale-105 transition-all duration-200 ${colorClasses ? `${colorClasses.bg} ${colorClasses.text} ${colorClasses.border} bg-opacity-80` : "bg-slate-100/80 text-slate-700 border-slate-200/60"}`, children: node.count }) })
28240
28375
  ] })
@@ -28901,6 +29036,19 @@ var BottlenecksContent = ({
28901
29036
  }
28902
29037
  }, [shift, date, timezone, shiftConfig, workspaceId]);
28903
29038
  const { crop: workspaceCrop} = useWorkspaceCrop(workspaceId);
29039
+ const { metrics: workspaceMetrics } = useWorkspaceDetailedMetrics(
29040
+ workspaceId,
29041
+ date,
29042
+ shift !== void 0 && shift !== null ? Number(shift) : void 0
29043
+ );
29044
+ const workspaceTargetCycleTimeRaw = workspaceMetrics?.ideal_cycle_time;
29045
+ const workspaceTargetCycleTime = (() => {
29046
+ if (workspaceTargetCycleTimeRaw === void 0 || workspaceTargetCycleTimeRaw === null) {
29047
+ return null;
29048
+ }
29049
+ const numericValue = typeof workspaceTargetCycleTimeRaw === "number" ? workspaceTargetCycleTimeRaw : Number(workspaceTargetCycleTimeRaw);
29050
+ return Number.isFinite(numericValue) ? numericValue : null;
29051
+ })();
28904
29052
  const videoRef = React23.useRef(null);
28905
29053
  const [initialFilter, setInitialFilter] = React23.useState("");
28906
29054
  const currentIndexRef = React23.useRef(0);
@@ -29843,21 +29991,33 @@ var BottlenecksContent = ({
29843
29991
  return () => window.removeEventListener("keydown", handleEscape);
29844
29992
  }
29845
29993
  }, [isFullscreen, exitFullscreen]);
29846
- const getClipTypeLabel = (video) => {
29994
+ const getClipTypeLabel = React23.useCallback((video) => {
29847
29995
  if (!video) return "";
29848
29996
  const currentFilter = activeFilterRef.current;
29997
+ const targetCycleTime = workspaceTargetCycleTime || 0;
29849
29998
  if (isPercentileCategory(currentFilter)) {
29850
29999
  const percentileValue = video.percentile?.toFixed(1);
29851
- switch (currentFilter) {
29852
- case "fast-cycles":
29853
- return `\u26A1 Fast Cycle ${percentileValue ? `(${percentileValue}%)` : ""}`;
29854
- case "slow-cycles":
29855
- return `\u{1F40C} Slow Cycle ${percentileValue ? `(${percentileValue}%)` : ""}`;
29856
- case "longest-idles":
29857
- return `\u23F0 Long Idle ${percentileValue ? `(${percentileValue}%)` : ""}`;
29858
- default:
29859
- return video.description || "Performance Clip";
30000
+ const cycleTime = video.cycle_time_seconds || 0;
30001
+ if (currentFilter === "fast-cycles" || currentFilter === "slow-cycles") {
30002
+ if (targetCycleTime > 0 && cycleTime > 0) {
30003
+ if (cycleTime < targetCycleTime) {
30004
+ return `\u26A1 Fast Cycle ${percentileValue ? `(${percentileValue}%)` : ""}`;
30005
+ } else {
30006
+ return `\u{1F40C} Slow Cycle ${percentileValue ? `(${percentileValue}%)` : ""}`;
30007
+ }
30008
+ } else {
30009
+ switch (currentFilter) {
30010
+ case "fast-cycles":
30011
+ return `\u26A1 Fast Cycle ${percentileValue ? `(${percentileValue}%)` : ""}`;
30012
+ case "slow-cycles":
30013
+ return `\u{1F40C} Slow Cycle ${percentileValue ? `(${percentileValue}%)` : ""}`;
30014
+ }
30015
+ }
29860
30016
  }
30017
+ if (currentFilter === "longest-idles") {
30018
+ return `\u23F0 Long Idle ${percentileValue ? `(${percentileValue}%)` : ""}`;
30019
+ }
30020
+ return video.description || "Performance Clip";
29861
30021
  }
29862
30022
  switch (video.type) {
29863
30023
  case "low_value":
@@ -29876,7 +30036,7 @@ var BottlenecksContent = ({
29876
30036
  default:
29877
30037
  return video.description || "";
29878
30038
  }
29879
- };
30039
+ }, [workspaceTargetCycleTime]);
29880
30040
  if (!dashboardConfig?.s3Config) {
29881
30041
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-grow p-4 flex flex-col items-center justify-center h-[calc(100vh-12rem)] text-center", children: [
29882
30042
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
@@ -30148,12 +30308,32 @@ var BottlenecksContent = ({
30148
30308
  badgeText = "Idle";
30149
30309
  badgeColor = "bg-red-100 text-red-700";
30150
30310
  } else {
30151
- if (clip.severity === "high" || clip.duration && clip.duration > 60) {
30152
- badgeText = "Slow";
30153
- badgeColor = "bg-red-100 text-red-700";
30311
+ const parseCycleTime2 = (value) => {
30312
+ if (typeof value === "number") return isNaN(value) ? null : value;
30313
+ if (typeof value === "string") {
30314
+ const parsed = parseFloat(value);
30315
+ return isNaN(parsed) ? null : parsed;
30316
+ }
30317
+ return null;
30318
+ };
30319
+ const clipCycleTime = parseCycleTime2(clip.cycle_time_seconds) ?? parseCycleTime2(clip.duration) ?? parseCycleTime2(clip.original_task_metadata?.cycle_time);
30320
+ const targetCycleTime = workspaceTargetCycleTime && workspaceTargetCycleTime > 0 ? workspaceTargetCycleTime : null;
30321
+ if (clipCycleTime && targetCycleTime) {
30322
+ if (clipCycleTime <= targetCycleTime) {
30323
+ badgeText = "Fast";
30324
+ badgeColor = "bg-green-100 text-green-700";
30325
+ } else {
30326
+ badgeText = "Slow";
30327
+ badgeColor = "bg-red-100 text-red-700";
30328
+ }
30154
30329
  } else {
30155
- badgeText = "Average";
30156
- badgeColor = "bg-yellow-100 text-yellow-700";
30330
+ if (clip.severity === "high" || clip.duration && clip.duration > 60) {
30331
+ badgeText = "Slow";
30332
+ badgeColor = "bg-red-100 text-red-700";
30333
+ } else {
30334
+ badgeText = "Average";
30335
+ badgeColor = "bg-yellow-100 text-yellow-700";
30336
+ }
30157
30337
  }
30158
30338
  }
30159
30339
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -30196,6 +30376,7 @@ var BottlenecksContent = ({
30196
30376
  workspaceId,
30197
30377
  date: date || getOperationalDate(timezone),
30198
30378
  shift: effectiveShift,
30379
+ targetCycleTime: workspaceTargetCycleTime,
30199
30380
  onFilterChange: (filterId) => {
30200
30381
  updateActiveFilter(filterId);
30201
30382
  const category = categoriesToShow.find((cat) => cat.type === filterId);
@@ -41483,7 +41664,6 @@ function HomeView({
41483
41664
  factoryName = "Simba Beer - Line 1"
41484
41665
  }) {
41485
41666
  const [isHydrated, setIsHydrated] = React23.useState(false);
41486
- const [selectedLineId, setSelectedLineId] = React23.useState(factoryViewId);
41487
41667
  const [isChangingFilter, setIsChangingFilter] = React23.useState(false);
41488
41668
  const [errorMessage, setErrorMessage] = React23.useState(null);
41489
41669
  const [displayNamesInitialized, setDisplayNamesInitialized] = React23.useState(false);
@@ -41500,9 +41680,36 @@ function HomeView({
41500
41680
  }
41501
41681
  return [factoryViewId, ...allLineIds];
41502
41682
  }, [factoryViewId, allLineIds, isSupervisor, hasMultipleLines]);
41683
+ const LINE_FILTER_STORAGE_KEY = "optifye_home_line_filter";
41684
+ const [selectedLineId, setSelectedLineId] = React23.useState(() => {
41685
+ if (typeof window === "undefined") {
41686
+ return factoryViewId;
41687
+ }
41688
+ try {
41689
+ const savedLineId = sessionStorage.getItem(LINE_FILTER_STORAGE_KEY);
41690
+ if (savedLineId) {
41691
+ const allAvailableIds = [factoryViewId, ...allLineIds];
41692
+ if (allAvailableIds.includes(savedLineId)) {
41693
+ return savedLineId;
41694
+ }
41695
+ }
41696
+ } catch (error) {
41697
+ console.warn("Failed to read line filter from sessionStorage:", error);
41698
+ }
41699
+ return factoryViewId;
41700
+ });
41503
41701
  React23.useEffect(() => {
41504
41702
  if (user) {
41505
41703
  if (isSupervisor && allLineIds.length > 0) {
41704
+ try {
41705
+ const savedLineId = sessionStorage.getItem(LINE_FILTER_STORAGE_KEY);
41706
+ if (savedLineId && allLineIds.includes(savedLineId)) {
41707
+ setSelectedLineId(savedLineId);
41708
+ return;
41709
+ }
41710
+ } catch (error) {
41711
+ console.warn("Failed to read line filter from sessionStorage:", error);
41712
+ }
41506
41713
  setSelectedLineId(allLineIds[0]);
41507
41714
  }
41508
41715
  }
@@ -41537,11 +41744,12 @@ function HomeView({
41537
41744
  };
41538
41745
  initDisplayNames();
41539
41746
  }, [selectedLineId, factoryViewId, allLineIds]);
41747
+ const displayNameLineId = selectedLineId === factoryViewId ? void 0 : selectedLineId;
41540
41748
  const {
41541
41749
  displayNames: workspaceDisplayNames,
41542
41750
  loading: displayNamesLoading,
41543
41751
  error: displayNamesError
41544
- } = useWorkspaceDisplayNames(selectedLineId, void 0);
41752
+ } = useWorkspaceDisplayNames(displayNameLineId, void 0);
41545
41753
  React23.useCallback(() => {
41546
41754
  console.log("Refetching KPIs after line metrics update");
41547
41755
  }, []);
@@ -41582,6 +41790,12 @@ function HomeView({
41582
41790
  }
41583
41791
  return allActiveBreaks.filter((breakItem) => breakItem.lineId === selectedLineId);
41584
41792
  }, [allActiveBreaks, selectedLineId, factoryViewId]);
41793
+ const [breakNotificationsDismissed, setBreakNotificationsDismissed] = React23.useState(false);
41794
+ React23.useEffect(() => {
41795
+ if (activeBreaks.length > 0) {
41796
+ setBreakNotificationsDismissed(false);
41797
+ }
41798
+ }, [activeBreaks.length]);
41585
41799
  const showBottleneckNotification = React23.useCallback(async (bottleneck) => {
41586
41800
  try {
41587
41801
  console.log("\u{1F514} [Notification] Raw bottleneck data:", bottleneck);
@@ -41880,7 +42094,12 @@ function HomeView({
41880
42094
  const handleLineChange = React23.useCallback((value) => {
41881
42095
  setIsChangingFilter(true);
41882
42096
  setSelectedLineId(value);
41883
- }, []);
42097
+ try {
42098
+ sessionStorage.setItem(LINE_FILTER_STORAGE_KEY, value);
42099
+ } catch (error) {
42100
+ console.warn("Failed to save line filter to sessionStorage:", error);
42101
+ }
42102
+ }, [LINE_FILTER_STORAGE_KEY]);
41884
42103
  React23.useEffect(() => {
41885
42104
  if (!metricsLoading && !kpisLoading && isChangingFilter) {
41886
42105
  if (workspaceMetrics.length > 0 || selectedLineId === factoryViewId) {
@@ -41905,7 +42124,7 @@ function HomeView({
41905
42124
  /* @__PURE__ */ jsxRuntime.jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: id3, children: lineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
41906
42125
  ] });
41907
42126
  }, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
41908
- const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized && displayNamesLoading;
42127
+ const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
41909
42128
  const isDataLoading = metricsLoading || kpisLoading;
41910
42129
  if (isInitialLoading) {
41911
42130
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingPageCmp, { message: "Loading Dashboard..." });
@@ -41993,7 +42212,8 @@ function HomeView({
41993
42212
  {
41994
42213
  activeBreaks,
41995
42214
  lineNames,
41996
- isVisible: !breaksLoading && !breaksError
42215
+ isVisible: !breaksLoading && !breaksError && !breakNotificationsDismissed,
42216
+ onDismiss: () => setBreakNotificationsDismissed(true)
41997
42217
  }
41998
42218
  ),
41999
42219
  /* @__PURE__ */ jsxRuntime.jsx(