@optifye/dashboard-core 6.12.45 → 6.12.47

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
@@ -14900,6 +14900,7 @@ var transformMonitorWorkspaceMetrics = ({
14900
14900
  leaderboard_value: item.leaderboard_value ?? null,
14901
14901
  avg_recent_flow: item.avg_recent_flow ?? null,
14902
14902
  recent_flow_percent: item.recent_flow_percent ?? null,
14903
+ recent_flow_hourly: item.recent_flow_hourly ?? null,
14903
14904
  recent_flow_window_minutes: item.recent_flow_window_minutes ?? null,
14904
14905
  recent_flow_effective_end_at: item.recent_flow_effective_end_at ?? null,
14905
14906
  recent_flow_computed_at: item.recent_flow_computed_at ?? null,
@@ -38502,6 +38503,114 @@ var getRawVideoGridMetricValue = (workspace) => {
38502
38503
  };
38503
38504
  var hasIncomingWipMapping = (workspace) => Boolean(workspace.incoming_wip_buffer_name);
38504
38505
  var getVideoGridWorkspaceKey = (workspace) => workspace.workspace_uuid || `${workspace.line_id || "unknown"}:${workspace.workspace_name || "unknown"}`;
38506
+ var getWorstPerformanceWorkstationLimit = (totalWorkstations) => {
38507
+ if (!Number.isFinite(totalWorkstations) || totalWorkstations <= 0) return 0;
38508
+ if (totalWorkstations === 2) return 1;
38509
+ if (totalWorkstations === 4) return 2;
38510
+ return Math.min(3, Math.max(1, Math.floor(totalWorkstations)));
38511
+ };
38512
+ var coerceRecentFlowHourlyValue = (value) => {
38513
+ if (typeof value === "number" && Number.isFinite(value)) {
38514
+ return value;
38515
+ }
38516
+ if (typeof value === "string") {
38517
+ const trimmed = value.trim();
38518
+ if (!trimmed || trimmed.toLowerCase() === "x") return null;
38519
+ const parsed = Number(trimmed);
38520
+ return Number.isFinite(parsed) ? parsed : null;
38521
+ }
38522
+ return null;
38523
+ };
38524
+ var collectRecentFlowHourlyValues = (hourly) => {
38525
+ if (!hourly || typeof hourly !== "object" || Array.isArray(hourly)) {
38526
+ return [];
38527
+ }
38528
+ const values = [];
38529
+ Object.values(hourly).forEach((rawSlotValues) => {
38530
+ if (Array.isArray(rawSlotValues)) {
38531
+ rawSlotValues.forEach((rawValue) => {
38532
+ const value = coerceRecentFlowHourlyValue(rawValue);
38533
+ if (value !== null) values.push(value);
38534
+ });
38535
+ return;
38536
+ }
38537
+ if (rawSlotValues && typeof rawSlotValues === "object") {
38538
+ Object.values(rawSlotValues).forEach((rawValue) => {
38539
+ const value = coerceRecentFlowHourlyValue(rawValue);
38540
+ if (value !== null) values.push(value);
38541
+ });
38542
+ }
38543
+ });
38544
+ return values;
38545
+ };
38546
+ var getAverageRecentFlowHourlyPercent = (workspace) => {
38547
+ const values = collectRecentFlowHourlyValues(workspace.recent_flow_hourly);
38548
+ if (values.length > 0) {
38549
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
38550
+ }
38551
+ if (isFiniteNumber4(workspace.avg_recent_flow)) {
38552
+ return workspace.avg_recent_flow;
38553
+ }
38554
+ return isFiniteNumber4(workspace.recent_flow_percent) ? workspace.recent_flow_percent : null;
38555
+ };
38556
+ var getAssemblyCycleOverStandardRatio = (workspace) => {
38557
+ const medianCycleTime = toFiniteNumberOrNull(workspace.avg_cycle_time);
38558
+ const standardCycleTime = toFiniteNumberOrNull(workspace.ideal_cycle_time);
38559
+ if (medianCycleTime === null || standardCycleTime === null || medianCycleTime <= 0 || standardCycleTime <= 0) {
38560
+ return null;
38561
+ }
38562
+ return medianCycleTime / standardCycleTime;
38563
+ };
38564
+ var getUptimeWorstPerformanceValue = (workspace) => toFiniteNumberOrNull(workspace.efficiency);
38565
+ var isWorstPerformanceEligible = (workspace) => isValidAggregateEfficiency(workspace.monitoring_mode, workspace.efficiency);
38566
+ var isAssemblyLineGroup = (lineWorkspaces) => (
38567
+ // `assembly_enabled` is denormalized from `lines.assembly` onto every
38568
+ // live-monitor workspace row; it is not a workstation-level flag.
38569
+ lineWorkspaces.some((workspace) => workspace.assembly_enabled === true)
38570
+ );
38571
+ var getWorstPerformanceLineMode = (lineWorkspaces) => {
38572
+ if (lineWorkspaces.some((workspace) => normalizeMonitoringMode(workspace.monitoring_mode) === "uptime")) {
38573
+ return "uptime";
38574
+ }
38575
+ return isAssemblyLineGroup(lineWorkspaces) ? "assembly" : "flow";
38576
+ };
38577
+ var getEligibleWorstPerformanceCandidates = (lineWorkspaces) => lineWorkspaces.map((workspace, index) => ({ workspace, index })).filter(({ workspace }) => isWorstPerformanceEligible(workspace));
38578
+ var selectWorstPerformanceWorkspaceIds = (workspaces) => {
38579
+ const workspacesByLine = /* @__PURE__ */ new Map();
38580
+ workspaces.forEach((workspace) => {
38581
+ const lineKey = workspace.line_id || "unknown";
38582
+ const lineWorkspaces = workspacesByLine.get(lineKey) || [];
38583
+ lineWorkspaces.push(workspace);
38584
+ workspacesByLine.set(lineKey, lineWorkspaces);
38585
+ });
38586
+ const selected = /* @__PURE__ */ new Set();
38587
+ workspacesByLine.forEach((lineWorkspaces) => {
38588
+ const limit = getWorstPerformanceWorkstationLimit(lineWorkspaces.length);
38589
+ if (limit <= 0) return;
38590
+ const eligibleCandidates = getEligibleWorstPerformanceCandidates(lineWorkspaces);
38591
+ if (eligibleCandidates.length === 0) return;
38592
+ const lineMode = getWorstPerformanceLineMode(lineWorkspaces);
38593
+ if (lineMode === "uptime") {
38594
+ eligibleCandidates.map((candidate) => ({
38595
+ ...candidate,
38596
+ efficiency: getUptimeWorstPerformanceValue(candidate.workspace)
38597
+ })).filter((entry) => entry.efficiency !== null).sort((left, right) => left.efficiency - right.efficiency || left.workspace.workspace_name.localeCompare(right.workspace.workspace_name, void 0, { numeric: true }) || left.index - right.index).slice(0, limit).forEach((entry) => selected.add(getVideoGridWorkspaceKey(entry.workspace)));
38598
+ return;
38599
+ }
38600
+ if (lineMode === "assembly") {
38601
+ eligibleCandidates.map((candidate) => ({
38602
+ ...candidate,
38603
+ ratio: getAssemblyCycleOverStandardRatio(candidate.workspace)
38604
+ })).filter((entry) => entry.ratio !== null).sort((left, right) => right.ratio - left.ratio || left.workspace.workspace_name.localeCompare(right.workspace.workspace_name, void 0, { numeric: true }) || left.index - right.index).slice(0, limit).forEach((entry) => selected.add(getVideoGridWorkspaceKey(entry.workspace)));
38605
+ return;
38606
+ }
38607
+ eligibleCandidates.map((candidate) => ({
38608
+ ...candidate,
38609
+ averageFlow: getAverageRecentFlowHourlyPercent(candidate.workspace)
38610
+ })).filter((entry) => entry.averageFlow !== null).sort((left, right) => left.averageFlow - right.averageFlow || left.workspace.workspace_name.localeCompare(right.workspace.workspace_name, void 0, { numeric: true }) || left.index - right.index).slice(0, limit).forEach((entry) => selected.add(getVideoGridWorkspaceKey(entry.workspace)));
38611
+ });
38612
+ return selected;
38613
+ };
38505
38614
  var getVideoGridBlueComparisonGroupKey = (workspace) => {
38506
38615
  const factoryAreaId = typeof workspace.factory_area_id === "string" ? workspace.factory_area_id.trim() : "";
38507
38616
  if (factoryAreaId && workspace.factory_area_enabled === true) {
@@ -38794,6 +38903,7 @@ var VideoCard = React148__namespace.default.memo(({
38794
38903
  displayMinuteBucket,
38795
38904
  displayName,
38796
38905
  isBlueBest = false,
38906
+ isWorstPerformance = false,
38797
38907
  lastSeenLabel,
38798
38908
  hasRecentHealthSignal: hasRecentHealthSignal2 = false,
38799
38909
  onMouseEnter,
@@ -38832,6 +38942,8 @@ var VideoCard = React148__namespace.default.memo(({
38832
38942
  const efficiencyOverlayClass = videoGridColorState === "green" ? "bg-[#00D654]/25" : videoGridColorState === "blue" ? "bg-[#0EA5E9]/30" : videoGridColorState === "yellow" ? "bg-[#FFD700]/30" : videoGridColorState === "red" ? "bg-[#FF2D0A]/30" : "bg-transparent";
38833
38943
  const efficiencyBarClass = videoGridColorState === "green" ? "bg-[#00AB45]" : videoGridColorState === "blue" ? "bg-[#0EA5E9]" : videoGridColorState === "yellow" ? "bg-[#FFB020]" : videoGridColorState === "red" ? "bg-[#E34329]" : "bg-gray-500/70";
38834
38944
  const efficiencyStatus = videoGridColorState === "green" ? "High" : videoGridColorState === "blue" ? "Best" : videoGridColorState === "yellow" ? "Medium" : videoGridColorState === "red" ? "Low" : "Neutral";
38945
+ const worstMarkerClassName = compact ? "left-1.5 top-1.5 gap-1.5 px-2 py-1 text-[11px]" : "left-2 top-2 gap-2 px-3 py-1.5 text-xs";
38946
+ const statusBadgesPositionClass = isWorstPerformance ? compact ? "top-8 left-1.5 gap-1" : "top-10 left-2 gap-1.5" : compact ? "top-1.5 left-1.5 gap-1" : "top-2 left-2 gap-1.5";
38835
38947
  const trendInfo = workspace.trend !== void 0 ? getTrendArrowAndColor(workspace.trend) : null;
38836
38948
  const handleClick = React148.useCallback(() => {
38837
38949
  trackCoreEvent("Workspace Card Clicked", {
@@ -38850,6 +38962,7 @@ var VideoCard = React148__namespace.default.memo(({
38850
38962
  {
38851
38963
  className: `workspace-card relative bg-gray-950 rounded-md overflow-hidden cursor-pointer shadow-[0_1px_3px_rgba(15,23,42,0.16),0_0_0_1px_rgba(255,255,255,0.06)] transition-[box-shadow] duration-200 hover:shadow-[0_10px_24px_rgba(15,23,42,0.28),0_0_0_1px_rgba(255,255,255,0.10)] active:shadow-[0_1px_3px_rgba(15,23,42,0.16),0_0_0_1px_rgba(255,255,255,0.06)] touch-manipulation ${className}`,
38852
38964
  style: { width: "100%", height: "100%" },
38965
+ "data-worst-performance-highlight": isWorstPerformance ? "true" : void 0,
38853
38966
  onClick: handleClick,
38854
38967
  onMouseEnter,
38855
38968
  onMouseLeave,
@@ -38911,7 +39024,7 @@ var VideoCard = React148__namespace.default.memo(({
38911
39024
  "div",
38912
39025
  {
38913
39026
  "data-testid": "video-card-status-badges",
38914
- className: `absolute ${compact ? "top-1.5 left-1.5 gap-1" : "top-2 left-2 gap-1.5"} z-30 flex items-center`,
39027
+ className: `absolute ${statusBadgesPositionClass} z-30 flex items-center`,
38915
39028
  children: statusBadges.map((badge, index) => {
38916
39029
  const presentation = getIdleReasonPresentation({
38917
39030
  label: badge.label,
@@ -38962,6 +39075,17 @@ var VideoCard = React148__namespace.default.memo(({
38962
39075
  })
38963
39076
  }
38964
39077
  ),
39078
+ isWorstPerformance && /* @__PURE__ */ jsxRuntime.jsxs(
39079
+ "div",
39080
+ {
39081
+ "data-testid": "video-card-worst-performance-marker",
39082
+ className: `pointer-events-none absolute z-[65] inline-flex items-center rounded-md bg-[#1A0B09]/95 border border-[#E34329]/60 font-semibold tracking-wide text-white shadow-xl ${worstMarkerClassName}`,
39083
+ children: [
39084
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: `${compact ? "h-3.5 w-3.5" : "h-4 w-4"} text-[#E34329]`, "aria-hidden": "true" }),
39085
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Overall Underperformer" })
39086
+ ]
39087
+ }
39088
+ ),
38965
39089
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute bottom-0 left-0 right-0 ${compact ? "h-0.5" : "h-1"} bg-black/50 z-30`, children: /* @__PURE__ */ jsxRuntime.jsx(
38966
39090
  "div",
38967
39091
  {
@@ -39010,6 +39134,9 @@ var VideoCard = React148__namespace.default.memo(({
39010
39134
  if (prevProps.isBlueBest !== nextProps.isBlueBest) {
39011
39135
  return false;
39012
39136
  }
39137
+ if (prevProps.isWorstPerformance !== nextProps.isWorstPerformance) {
39138
+ return false;
39139
+ }
39013
39140
  if (prevProps.lastSeenLabel !== nextProps.lastSeenLabel) {
39014
39141
  return false;
39015
39142
  }
@@ -39052,6 +39179,7 @@ var hasRecentHealthSignal = (lastHeartbeat) => {
39052
39179
  var VideoGridView = React148__namespace.default.memo(({
39053
39180
  workspaces,
39054
39181
  blueComparisonWorkspaces,
39182
+ worstPerformanceWorkspaceIds = [],
39055
39183
  selectedLine,
39056
39184
  className = "",
39057
39185
  legend,
@@ -39059,6 +39187,9 @@ var VideoGridView = React148__namespace.default.memo(({
39059
39187
  videoStreamsByWorkspaceId,
39060
39188
  videoStreamsLoading = false,
39061
39189
  displayNames = {},
39190
+ lineOrder = [],
39191
+ activeSlideshowLineId = null,
39192
+ displayMode = "all",
39062
39193
  onWorkspaceHover,
39063
39194
  onWorkspaceHoverEnd
39064
39195
  }) => {
@@ -39181,6 +39312,48 @@ var VideoGridView = React148__namespace.default.memo(({
39181
39312
  });
39182
39313
  }, [filteredWorkspaces]);
39183
39314
  const blueWinnerIds = React148.useMemo(() => selectVideoGridBlueWinnerIds(blueComparisonWorkspaces || sortedWorkspaces, effectiveLegend), [blueComparisonWorkspaces, sortedWorkspaces, effectiveLegend]);
39315
+ const worstPerformanceIdSet = React148.useMemo(
39316
+ () => new Set(worstPerformanceWorkspaceIds),
39317
+ [worstPerformanceWorkspaceIds]
39318
+ );
39319
+ const modeBaseWorkspaces = React148.useMemo(() => {
39320
+ if (displayMode !== "red_only") {
39321
+ return sortedWorkspaces;
39322
+ }
39323
+ return sortedWorkspaces.filter((workspace) => getVideoGridColorState(workspace, effectiveLegend, blueWinnerIds) === "red");
39324
+ }, [blueWinnerIds, displayMode, effectiveLegend, sortedWorkspaces]);
39325
+ const slideshowLines = React148.useMemo(() => {
39326
+ const linesWithWorkspaces = new Set(
39327
+ sortedWorkspaces.map((workspace) => workspace.line_id).filter((lineId) => Boolean(lineId))
39328
+ );
39329
+ if (lineOrder.length > 0) {
39330
+ return lineOrder.filter((lineId) => linesWithWorkspaces.has(lineId));
39331
+ }
39332
+ const orderedLineIds = [];
39333
+ const seenLineIds = /* @__PURE__ */ new Set();
39334
+ for (const workspace of sortedWorkspaces) {
39335
+ if (!workspace.line_id || seenLineIds.has(workspace.line_id)) {
39336
+ continue;
39337
+ }
39338
+ seenLineIds.add(workspace.line_id);
39339
+ orderedLineIds.push(workspace.line_id);
39340
+ }
39341
+ return orderedLineIds;
39342
+ }, [lineOrder, sortedWorkspaces]);
39343
+ const currentSlideshowLineId = displayMode === "slideshow" && slideshowLines.length > 0 ? activeSlideshowLineId && slideshowLines.includes(activeSlideshowLineId) ? activeSlideshowLineId : slideshowLines[0] : null;
39344
+ const displayWorkspaces = React148.useMemo(() => {
39345
+ if (displayMode === "red_only") {
39346
+ return modeBaseWorkspaces;
39347
+ }
39348
+ if (displayMode !== "slideshow") {
39349
+ return sortedWorkspaces;
39350
+ }
39351
+ if (slideshowLines.length === 0) {
39352
+ return [];
39353
+ }
39354
+ const currentLineId = currentSlideshowLineId || slideshowLines[0];
39355
+ return sortedWorkspaces.filter((workspace) => workspace.line_id === currentLineId);
39356
+ }, [currentSlideshowLineId, displayMode, modeBaseWorkspaces, slideshowLines, sortedWorkspaces]);
39184
39357
  const streamsResolvedForWorkspaceSet = resolvedStreamWorkspaceKey === workspaceIdsKey;
39185
39358
  const resolveWorkspaceDisplayName = React148.useCallback((workspace) => {
39186
39359
  return workspace.displayName || displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || workspace.workspace_name;
@@ -39191,7 +39364,7 @@ var VideoGridView = React148__namespace.default.memo(({
39191
39364
  const rawContainerWidth = containerRef.current.clientWidth;
39192
39365
  const containerWidth = rawContainerWidth - containerPadding;
39193
39366
  const containerHeight = containerRef.current.clientHeight - containerPadding;
39194
- const count = filteredWorkspaces.length;
39367
+ const count = displayWorkspaces.length;
39195
39368
  if (count === 0) {
39196
39369
  setGridCols(1);
39197
39370
  setGridRows(1);
@@ -39242,7 +39415,7 @@ var VideoGridView = React148__namespace.default.memo(({
39242
39415
  setGridCols(bestCols);
39243
39416
  setGridRows(rows);
39244
39417
  setIsMobileScrollableGrid(shouldUseMobileScroll);
39245
- }, [filteredWorkspaces.length, selectedLine]);
39418
+ }, [displayWorkspaces.length, selectedLine]);
39246
39419
  React148.useEffect(() => {
39247
39420
  calculateOptimalGrid();
39248
39421
  const handleResize = () => calculateOptimalGrid();
@@ -39276,7 +39449,7 @@ var VideoGridView = React148__namespace.default.memo(({
39276
39449
  return () => {
39277
39450
  observerRef.current?.disconnect();
39278
39451
  };
39279
- }, [filteredWorkspaces]);
39452
+ }, [displayWorkspaces]);
39280
39453
  const prewarmClipsInit = React148.useCallback((workspace) => {
39281
39454
  if (!dashboardConfig?.s3Config) return;
39282
39455
  const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
@@ -39362,7 +39535,7 @@ var VideoGridView = React148__namespace.default.memo(({
39362
39535
  });
39363
39536
  }, []);
39364
39537
  const workspaceCards = React148.useMemo(() => {
39365
- return sortedWorkspaces.map((workspace) => {
39538
+ return displayWorkspaces.map((workspace) => {
39366
39539
  const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
39367
39540
  const workspaceKey = `${workspace.line_id || "unknown"}-${workspaceId}`;
39368
39541
  const isVisible = visibleWorkspaces.has(workspaceId);
@@ -39390,12 +39563,14 @@ var VideoGridView = React148__namespace.default.memo(({
39390
39563
  shouldPlay,
39391
39564
  lastSeenLabel,
39392
39565
  hasRecentHealthSignal: hasRecentHealthSignal(workspaceHealth?.lastHeartbeat),
39393
- isBlueBest: blueWinnerIds.has(getVideoGridWorkspaceKey(workspace))
39566
+ isBlueBest: blueWinnerIds.has(getVideoGridWorkspaceKey(workspace)),
39567
+ isWorstPerformance: worstPerformanceIdSet.has(getVideoGridWorkspaceKey(workspace))
39394
39568
  };
39395
39569
  });
39396
39570
  }, [
39397
- sortedWorkspaces,
39571
+ displayWorkspaces,
39398
39572
  blueWinnerIds,
39573
+ worstPerformanceIdSet,
39399
39574
  visibleWorkspaces,
39400
39575
  getWorkspaceCropping,
39401
39576
  videoStreamsByWorkspaceId,
@@ -39443,6 +39618,7 @@ var VideoGridView = React148__namespace.default.memo(({
39443
39618
  displayMinuteBucket,
39444
39619
  compact: !selectedLine,
39445
39620
  isBlueBest: card.isBlueBest,
39621
+ isWorstPerformance: card.isWorstPerformance,
39446
39622
  onMouseEnter: onWorkspaceHover ? () => onWorkspaceHover(card.workspaceId) : void 0,
39447
39623
  onMouseLeave: onWorkspaceHoverEnd ? () => onWorkspaceHoverEnd(card.workspaceId) : void 0
39448
39624
  }
@@ -39468,9 +39644,28 @@ var VideoGridView = React148__namespace.default.memo(({
39468
39644
  "data-testid": "video-grid-scroll-container",
39469
39645
  "data-mobile-scrollable": isMobileScrollableGrid ? "true" : "false",
39470
39646
  className: `absolute inset-0 w-full overflow-x-hidden px-1 py-1 sm:px-2 sm:py-2 ${isMobileScrollableGrid ? "overflow-y-auto" : "overflow-hidden"}`,
39471
- children: /* @__PURE__ */ jsxRuntime.jsx(
39472
- "div",
39647
+ children: /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { mode: "popLayout", children: displayMode === "red_only" && workspaceCards.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
39648
+ motion.div,
39649
+ {
39650
+ initial: { opacity: 0, scale: 0.95 },
39651
+ animate: { opacity: 1, scale: 1 },
39652
+ exit: { opacity: 0, scale: 0.95 },
39653
+ transition: { duration: 0.3 },
39654
+ "data-testid": "video-grid-empty-red-cameras",
39655
+ className: "flex h-full min-h-[240px] flex-col items-center justify-center rounded-xl border border-emerald-200 bg-emerald-50/70 px-6 text-center",
39656
+ children: [
39657
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-base font-semibold text-emerald-900", children: "No red cameras right now" }),
39658
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 max-w-md text-sm text-emerald-700", children: "The selected line filter has no cameras in the red state. Switch back to all cameras to keep monitoring the full floor." })
39659
+ ]
39660
+ },
39661
+ "empty-red"
39662
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
39663
+ motion.div,
39473
39664
  {
39665
+ initial: displayMode === "slideshow" ? { opacity: 0, x: 100 } : { opacity: 0 },
39666
+ animate: displayMode === "slideshow" ? { opacity: 1, x: 0 } : { opacity: 1 },
39667
+ exit: displayMode === "slideshow" ? { opacity: 0, x: -100 } : { opacity: 0 },
39668
+ transition: { duration: 0.5, ease: [0.32, 0.72, 0, 1] },
39474
39669
  "data-testid": "video-grid-layout",
39475
39670
  className: `grid min-w-0 w-full gap-1.5 sm:gap-2 ${isMobileScrollableGrid ? "content-start" : "h-full"}`,
39476
39671
  style: {
@@ -39482,8 +39677,9 @@ var VideoGridView = React148__namespace.default.memo(({
39482
39677
  card,
39483
39678
  isMobileScrollableGrid ? "workspace-card relative min-w-0 w-full aspect-video min-h-[92px]" : "workspace-card relative min-w-0 w-full h-full"
39484
39679
  ))
39485
- }
39486
- )
39680
+ },
39681
+ `grid-${displayMode === "slideshow" ? currentSlideshowLineId : "standard"}`
39682
+ ) })
39487
39683
  }
39488
39684
  ) });
39489
39685
  });
@@ -46343,6 +46539,13 @@ var FileManagerFilters = ({
46343
46539
  shift,
46344
46540
  count: node.count || 0
46345
46541
  });
46542
+ } else if (node.id === RECENT_FLOW_RED_STREAK_CLIP_TYPE2) {
46543
+ trackCoreEvent("Low Moments Clicked", {
46544
+ workspaceId,
46545
+ date,
46546
+ shift,
46547
+ count: node.count || 0
46548
+ });
46346
46549
  }
46347
46550
  if (node.id !== "idle_time" && idleLabelFilter) {
46348
46551
  setIdleLabelFilter(null);
@@ -47190,6 +47393,7 @@ function useClipsRealtimeUpdates({
47190
47393
  hasNewClips: newClipsNotification !== null && newClipsNotification.count > 0
47191
47394
  };
47192
47395
  }
47396
+ var LOW_EFFICIENCY_CATEGORY_ID = "recent_flow_red_streak";
47193
47397
  var parseFiniteNumber2 = (value) => {
47194
47398
  if (typeof value === "number" && Number.isFinite(value)) {
47195
47399
  return value;
@@ -47448,6 +47652,7 @@ var BottlenecksContent = ({
47448
47652
  totalOutput,
47449
47653
  enabled: isEffectiveShiftReady
47450
47654
  });
47655
+ const isLowMomentsCategoryAvailable = React148.useMemo(() => Array.isArray(clipTypes) && clipTypes.some((type) => type?.type === LOW_EFFICIENCY_CATEGORY_ID || type?.id === LOW_EFFICIENCY_CATEGORY_ID), [clipTypes]);
47451
47656
  console.log("[BottlenecksContent] Clip types data:", {
47452
47657
  clipTypes,
47453
47658
  clipTypesLength: clipTypes?.length,
@@ -47498,20 +47703,20 @@ var BottlenecksContent = ({
47498
47703
  if (initialFilter) {
47499
47704
  return;
47500
47705
  }
47501
- const redStreakType = "recent_flow_red_streak";
47502
- if (dynamicCounts[redStreakType] > 0) {
47706
+ const redStreakType = LOW_EFFICIENCY_CATEGORY_ID;
47707
+ if (isLowMomentsCategoryAvailable && dynamicCounts[redStreakType] > 0) {
47503
47708
  setInitialFilter(redStreakType);
47504
47709
  setActiveFilter(redStreakType);
47505
47710
  activeFilterRef.current = redStreakType;
47506
47711
  return;
47507
47712
  }
47508
- if (defaultCategory) {
47713
+ if (defaultCategory && (defaultCategory !== LOW_EFFICIENCY_CATEGORY_ID || isLowMomentsCategoryAvailable)) {
47509
47714
  setInitialFilter(defaultCategory);
47510
47715
  setActiveFilter(defaultCategory);
47511
47716
  activeFilterRef.current = defaultCategory;
47512
47717
  return;
47513
47718
  }
47514
- }, [clipTypes, dynamicCounts, defaultCategory, initialFilter]);
47719
+ }, [clipTypes, dynamicCounts, defaultCategory, initialFilter, isLowMomentsCategoryAvailable]);
47515
47720
  const mergedCounts = React148.useMemo(() => {
47516
47721
  return { ...dynamicCounts };
47517
47722
  }, [dynamicCounts]);
@@ -47730,7 +47935,7 @@ var BottlenecksContent = ({
47730
47935
  };
47731
47936
  const [cycleClips, redFlowClips, idleClips] = await Promise.all([
47732
47937
  fetchCategoryMetadata("cycle_completion"),
47733
- fetchCategoryMetadata("recent_flow_red_streak"),
47938
+ isLowMomentsCategoryAvailable ? fetchCategoryMetadata(LOW_EFFICIENCY_CATEGORY_ID) : Promise.resolve([]),
47734
47939
  fetchCategoryMetadata("idle_time")
47735
47940
  ]);
47736
47941
  const allClips = [...cycleClips, ...redFlowClips, ...idleClips].sort(
@@ -47744,7 +47949,7 @@ var BottlenecksContent = ({
47744
47949
  }
47745
47950
  };
47746
47951
  fetchTriageClips();
47747
- }, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, supabase, isEffectiveShiftReady]);
47952
+ }, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, supabase, isEffectiveShiftReady, isLowMomentsCategoryAvailable]);
47748
47953
  React148.useEffect(() => {
47749
47954
  if (!idleTimeVlmEnabled || !triageMode || triageClips.length === 0) {
47750
47955
  return;
@@ -47782,7 +47987,7 @@ var BottlenecksContent = ({
47782
47987
  }, [idleTimeVlmEnabled, triageClips, triageMode, getAuthToken4]);
47783
47988
  React148.useEffect(() => {
47784
47989
  if (s3ClipsService && (mergedCounts[activeFilter] || 0) > 0) {
47785
- if (activeFilter === "recent_flow_red_streak") {
47990
+ if (activeFilter === LOW_EFFICIENCY_CATEGORY_ID) {
47786
47991
  return;
47787
47992
  }
47788
47993
  if (firstClip && firstClip.type === activeFilter) {
@@ -47805,12 +48010,12 @@ var BottlenecksContent = ({
47805
48010
  return ["fast-cycles", "slow-cycles", "longest-idles"].includes(categoryId);
47806
48011
  }, [isFastSlowClipFiltersEnabled]);
47807
48012
  const shouldUseMetadataNavigation = React148.useCallback((categoryId) => {
47808
- return isPercentileCategory(categoryId) || categoryId === "recent_flow_red_streak" || categoryId === "idle_time" && idleClipSort === "idle_duration_desc";
48013
+ return isPercentileCategory(categoryId) || categoryId === LOW_EFFICIENCY_CATEGORY_ID || categoryId === "idle_time" && idleClipSort === "idle_duration_desc";
47809
48014
  }, [idleClipSort, isPercentileCategory]);
47810
48015
  const getMetadataCacheKey = React148.useCallback((categoryId) => {
47811
- const sortKey = categoryId === "recent_flow_red_streak" ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest";
47812
- return `${categoryId}-${effectiveDateString}-${effectiveShiftId}-${snapshotDateTime ?? "nosnap"}-${snapshotClipId ?? "nosnap"}-${sortKey}`;
47813
- }, [effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId, idleClipSort]);
48016
+ const sortKey = categoryId === LOW_EFFICIENCY_CATEGORY_ID ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest";
48017
+ return `${categoryId}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-${snapshotDateTime ?? "nosnap"}-${snapshotClipId ?? "nosnap"}-${sortKey}`;
48018
+ }, [workspaceId, effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId, idleClipSort]);
47814
48019
  const setVisibleCategoryMetadata = React148.useCallback((categoryId, clips) => {
47815
48020
  if (activeFilterRef.current !== categoryId) {
47816
48021
  return false;
@@ -47818,7 +48023,7 @@ var BottlenecksContent = ({
47818
48023
  categoryMetadataRef.current = clips;
47819
48024
  setCategoryMetadata(clips);
47820
48025
  setCategoryMetadataCategoryId(clips.length > 0 ? categoryId : null);
47821
- setCategoryMetadataSort(clips.length > 0 ? categoryId === "recent_flow_red_streak" ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest" : null);
48026
+ setCategoryMetadataSort(clips.length > 0 ? categoryId === LOW_EFFICIENCY_CATEGORY_ID ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest" : null);
47822
48027
  return true;
47823
48028
  }, [idleClipSort]);
47824
48029
  const applyMetadataSnapshot = React148.useCallback((categoryId, clips, total) => {
@@ -47871,7 +48076,8 @@ var BottlenecksContent = ({
47871
48076
  if (activeFilter !== "fast-cycles" && activeFilter !== "slow-cycles") {
47872
48077
  return;
47873
48078
  }
47874
- const fallbackFilter = ["cycle_completion", "recent_flow_red_streak", "idle_time"].find((type) => (dynamicCounts[type] || 0) > 0) || clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0)?.type || clipTypes[0]?.type;
48079
+ const lowMomentsFallback = isLowMomentsCategoryAvailable ? LOW_EFFICIENCY_CATEGORY_ID : null;
48080
+ const fallbackFilter = ["cycle_completion", lowMomentsFallback, "idle_time"].filter((type) => Boolean(type)).find((type) => (dynamicCounts[type] || 0) > 0) || clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0)?.type || clipTypes[0]?.type;
47875
48081
  if (!fallbackFilter || fallbackFilter === activeFilter) {
47876
48082
  return;
47877
48083
  }
@@ -47880,7 +48086,25 @@ var BottlenecksContent = ({
47880
48086
  setCategoryMetadataSort(null);
47881
48087
  categoryMetadataRef.current = [];
47882
48088
  updateActiveFilter(fallbackFilter);
47883
- }, [isFastSlowClipFiltersEnabled, activeFilter, dynamicCounts, clipTypes, updateActiveFilter]);
48089
+ }, [isFastSlowClipFiltersEnabled, activeFilter, dynamicCounts, clipTypes, updateActiveFilter, isLowMomentsCategoryAvailable]);
48090
+ React148.useEffect(() => {
48091
+ if (activeFilter !== LOW_EFFICIENCY_CATEGORY_ID || isLowMomentsCategoryAvailable) {
48092
+ return;
48093
+ }
48094
+ const fallbackFilter = clipTypes.find((type) => type.type !== LOW_EFFICIENCY_CATEGORY_ID && (dynamicCounts[type.type] || 0) > 0)?.type || clipTypes.find((type) => type.type !== LOW_EFFICIENCY_CATEGORY_ID)?.type || "";
48095
+ setCategoryMetadata([]);
48096
+ setCategoryMetadataCategoryId(null);
48097
+ setCategoryMetadataSort(null);
48098
+ categoryMetadataRef.current = [];
48099
+ if (fallbackFilter) {
48100
+ setInitialFilter(fallbackFilter);
48101
+ updateActiveFilter(fallbackFilter);
48102
+ } else {
48103
+ setInitialFilter("");
48104
+ updateActiveFilter("");
48105
+ setIsCategoryLoading(false);
48106
+ }
48107
+ }, [activeFilter, isLowMomentsCategoryAvailable, clipTypes, dynamicCounts, updateActiveFilter]);
47884
48108
  React148.useCallback((categoryId) => {
47885
48109
  if (isPercentileCategory(categoryId)) {
47886
48110
  return categoryMetadata.length;
@@ -47932,6 +48156,16 @@ var BottlenecksContent = ({
47932
48156
  }
47933
48157
  return;
47934
48158
  }
48159
+ if (categoryId === LOW_EFFICIENCY_CATEGORY_ID && !isLowMomentsCategoryAvailable) {
48160
+ setCategoryMetadata([]);
48161
+ setCategoryMetadataCategoryId(null);
48162
+ setCategoryMetadataSort(null);
48163
+ categoryMetadataRef.current = [];
48164
+ if (activeFilterRef.current === categoryId) {
48165
+ setIsCategoryLoading(false);
48166
+ }
48167
+ return;
48168
+ }
47935
48169
  if (!isEffectiveShiftReady) {
47936
48170
  console.log("[BottlenecksContent] Skipping metadata load - shift/date not ready");
47937
48171
  if (activeFilterRef.current === categoryId) {
@@ -47943,10 +48177,10 @@ var BottlenecksContent = ({
47943
48177
  const cacheKey = getMetadataCacheKey(categoryId);
47944
48178
  const candidateLowMomentsPrefetch = lowMomentsPrefetch ?? null;
47945
48179
  const lowMomentsPrefetchMatchesScope = Boolean(
47946
- candidateLowMomentsPrefetch?.key?.startsWith(`recent_flow_red_streak-${effectiveDateString}-${effectiveShiftId}-`)
48180
+ candidateLowMomentsPrefetch?.key?.startsWith(`${LOW_EFFICIENCY_CATEGORY_ID}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-`)
47947
48181
  );
47948
- const matchingLowMomentsPrefetch = !forceRefresh && categoryId === "recent_flow_red_streak" && candidateLowMomentsPrefetch && (candidateLowMomentsPrefetch.key === cacheKey || lowMomentsPrefetchMatchesScope) && !candidateLowMomentsPrefetch.loading && Array.isArray(candidateLowMomentsPrefetch.metadata) && candidateLowMomentsPrefetch.metadata.length > 0 ? candidateLowMomentsPrefetch : null;
47949
- const lowMomentsPrefetchInFlight = !forceRefresh && categoryId === "recent_flow_red_streak" && candidateLowMomentsPrefetch && (candidateLowMomentsPrefetch.key === cacheKey || lowMomentsPrefetchMatchesScope) && candidateLowMomentsPrefetch.loading;
48182
+ const matchingLowMomentsPrefetch = !forceRefresh && categoryId === LOW_EFFICIENCY_CATEGORY_ID && isLowMomentsCategoryAvailable && candidateLowMomentsPrefetch && (candidateLowMomentsPrefetch.key === cacheKey || lowMomentsPrefetchMatchesScope) && !candidateLowMomentsPrefetch.loading && Array.isArray(candidateLowMomentsPrefetch.metadata) && candidateLowMomentsPrefetch.metadata.length > 0 ? candidateLowMomentsPrefetch : null;
48183
+ const lowMomentsPrefetchInFlight = !forceRefresh && categoryId === LOW_EFFICIENCY_CATEGORY_ID && isLowMomentsCategoryAvailable && candidateLowMomentsPrefetch && (candidateLowMomentsPrefetch.key === cacheKey || lowMomentsPrefetchMatchesScope) && candidateLowMomentsPrefetch.loading;
47950
48184
  if (lowMomentsPrefetchInFlight) {
47951
48185
  if (autoLoadFirstVideo && candidateLowMomentsPrefetch?.firstVideo) {
47952
48186
  applyPrefetchedFirstVideo(candidateLowMomentsPrefetch.firstVideo);
@@ -48037,7 +48271,7 @@ var BottlenecksContent = ({
48037
48271
  knownTotal: mergedCounts[categoryId] ?? null,
48038
48272
  snapshotDateTime,
48039
48273
  snapshotClipId,
48040
- sort: categoryId === "recent_flow_red_streak" ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest"
48274
+ sort: categoryId === LOW_EFFICIENCY_CATEGORY_ID ? "red_flow_output_shortfall_desc" : categoryId === "idle_time" ? idleClipSort : "latest"
48041
48275
  }),
48042
48276
  redirectReason: "session_expired"
48043
48277
  });
@@ -48131,19 +48365,19 @@ var BottlenecksContent = ({
48131
48365
  setIsCategoryLoading(false);
48132
48366
  }
48133
48367
  }
48134
- }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, isFastSlowClipFiltersEnabled, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, idleClipSort, supabase, setVisibleCategoryMetadata, lowMomentsPrefetch, applyPrefetchedFirstVideo, applyMetadataSnapshot]);
48368
+ }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, isFastSlowClipFiltersEnabled, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, idleClipSort, supabase, setVisibleCategoryMetadata, lowMomentsPrefetch, applyPrefetchedFirstVideo, applyMetadataSnapshot, isLowMomentsCategoryAvailable]);
48135
48369
  React148.useEffect(() => {
48136
- if (activeFilter !== "recent_flow_red_streak") {
48370
+ if (activeFilter !== LOW_EFFICIENCY_CATEGORY_ID || !isLowMomentsCategoryAvailable) {
48137
48371
  return;
48138
48372
  }
48139
- if (!lowMomentsPrefetch?.key?.startsWith(`recent_flow_red_streak-${effectiveDateString}-${effectiveShiftId}-`)) {
48373
+ if (!lowMomentsPrefetch?.key?.startsWith(`${LOW_EFFICIENCY_CATEGORY_ID}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-`)) {
48140
48374
  return;
48141
48375
  }
48142
- if (lowMomentsPrefetch.firstVideo && !allVideos.some((video) => video.type === "recent_flow_red_streak")) {
48376
+ if (lowMomentsPrefetch.firstVideo && !allVideos.some((video) => video.type === LOW_EFFICIENCY_CATEGORY_ID)) {
48143
48377
  applyPrefetchedFirstVideo(lowMomentsPrefetch.firstVideo);
48144
48378
  }
48145
48379
  if (lowMomentsPrefetch.metadata.length > 0) {
48146
- applyMetadataSnapshot("recent_flow_red_streak", lowMomentsPrefetch.metadata, lowMomentsPrefetch.total);
48380
+ applyMetadataSnapshot(LOW_EFFICIENCY_CATEGORY_ID, lowMomentsPrefetch.metadata, lowMomentsPrefetch.total);
48147
48381
  if (isMountedRef.current) {
48148
48382
  setIsCategoryLoading(false);
48149
48383
  }
@@ -48155,11 +48389,13 @@ var BottlenecksContent = ({
48155
48389
  }, [
48156
48390
  activeFilter,
48157
48391
  lowMomentsPrefetch,
48392
+ workspaceId,
48158
48393
  effectiveDateString,
48159
48394
  effectiveShiftId,
48160
48395
  allVideos,
48161
48396
  applyPrefetchedFirstVideo,
48162
- applyMetadataSnapshot
48397
+ applyMetadataSnapshot,
48398
+ isLowMomentsCategoryAvailable
48163
48399
  ]);
48164
48400
  React148.useEffect(() => {
48165
48401
  if (previousIdleClipSortRef.current === idleClipSort) {
@@ -48855,11 +49091,11 @@ var BottlenecksContent = ({
48855
49091
  }
48856
49092
  return currentPosition;
48857
49093
  }, [activeFilter, categoryMetadata.length, currentMetadataIndex, currentPosition, shouldUseMetadataNavigation]);
48858
- const prefetchedExplorerMetadata = React148.useMemo(() => activeFilter === "recent_flow_red_streak" && lowMomentsPrefetch?.key?.startsWith(`recent_flow_red_streak-${effectiveDateString}-${effectiveShiftId}-`) && !lowMomentsPrefetch.loading && lowMomentsPrefetch.metadata.length > 0 ? { recent_flow_red_streak: lowMomentsPrefetch.metadata } : activeFilter === "idle_time" && categoryMetadataSort !== idleClipSort ? void 0 : buildPrefetchedExplorerMetadata(
49094
+ const prefetchedExplorerMetadata = React148.useMemo(() => activeFilter === LOW_EFFICIENCY_CATEGORY_ID && isLowMomentsCategoryAvailable && lowMomentsPrefetch?.key?.startsWith(`${LOW_EFFICIENCY_CATEGORY_ID}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-`) && !lowMomentsPrefetch.loading && lowMomentsPrefetch.metadata.length > 0 ? { [LOW_EFFICIENCY_CATEGORY_ID]: lowMomentsPrefetch.metadata } : activeFilter === "idle_time" && categoryMetadataSort !== idleClipSort ? void 0 : buildPrefetchedExplorerMetadata(
48859
49095
  activeFilter,
48860
49096
  categoryMetadataCategoryId,
48861
49097
  categoryMetadata
48862
- ), [activeFilter, categoryMetadata, categoryMetadataCategoryId, categoryMetadataSort, idleClipSort, lowMomentsPrefetch, effectiveDateString, effectiveShiftId]);
49098
+ ), [activeFilter, categoryMetadata, categoryMetadataCategoryId, categoryMetadataSort, idleClipSort, lowMomentsPrefetch, workspaceId, effectiveDateString, effectiveShiftId, isLowMomentsCategoryAvailable]);
48863
49099
  const classificationClipIds = React148.useMemo(() => {
48864
49100
  if (!idleTimeVlmEnabled) {
48865
49101
  return [];
@@ -49632,7 +49868,7 @@ var BottlenecksContent = ({
49632
49868
  prefetchedClipMetadata: prefetchedExplorerMetadata,
49633
49869
  externallyManagedLoadingCategories: {
49634
49870
  recent_flow_red_streak: Boolean(
49635
- activeFilter === "recent_flow_red_streak" && lowMomentsPrefetch?.key?.startsWith(`recent_flow_red_streak-${effectiveDateString}-${effectiveShiftId}-`) && lowMomentsPrefetch.loading
49871
+ activeFilter === LOW_EFFICIENCY_CATEGORY_ID && isLowMomentsCategoryAvailable && lowMomentsPrefetch?.key?.startsWith(`${LOW_EFFICIENCY_CATEGORY_ID}-${workspaceId}-${effectiveDateString}-${effectiveShiftId}-`) && lowMomentsPrefetch.loading
49636
49872
  )
49637
49873
  },
49638
49874
  activeCategoryLoading: isCategoryLoading,
@@ -56905,10 +57141,12 @@ WorkspaceGridItem.displayName = "WorkspaceGridItem";
56905
57141
  var WorkspaceGrid = React148__namespace.default.memo(({
56906
57142
  workspaces,
56907
57143
  blueComparisonWorkspaces,
57144
+ worstPerformanceWorkspaceIds = [],
56908
57145
  isPdfMode = false,
56909
57146
  customWorkspacePositions,
56910
57147
  lineNames = {},
56911
57148
  lineOrder = [],
57149
+ activeSlideshowLineId = null,
56912
57150
  factoryView = "factory",
56913
57151
  line2Uuid = "line-2",
56914
57152
  className = "",
@@ -56919,7 +57157,8 @@ var WorkspaceGrid = React148__namespace.default.memo(({
56919
57157
  displayNames = {},
56920
57158
  onWorkspaceHover,
56921
57159
  onWorkspaceHoverEnd,
56922
- toolbarRightContent
57160
+ toolbarRightContent,
57161
+ displayMode = "all"
56923
57162
  }) => {
56924
57163
  const mapViewEnabled = false;
56925
57164
  const [viewMode, setViewMode] = React148.useState(() => {
@@ -56977,13 +57216,16 @@ var WorkspaceGrid = React148__namespace.default.memo(({
56977
57216
  {
56978
57217
  workspaces,
56979
57218
  blueComparisonWorkspaces,
57219
+ worstPerformanceWorkspaceIds,
56980
57220
  lineNames,
56981
57221
  lineOrder,
57222
+ activeSlideshowLineId,
56982
57223
  videoSources,
56983
57224
  videoStreamsByWorkspaceId,
56984
57225
  videoStreamsLoading,
56985
57226
  displayNames,
56986
57227
  legend,
57228
+ displayMode,
56987
57229
  onWorkspaceHover,
56988
57230
  onWorkspaceHoverEnd
56989
57231
  }
@@ -67122,6 +67364,31 @@ var NotificationService = class {
67122
67364
  function createNotificationService(supabaseClient) {
67123
67365
  return new NotificationService(supabaseClient);
67124
67366
  }
67367
+
67368
+ // src/components/dashboard/grid/displayModes.ts
67369
+ var HOME_DISPLAY_MODE_OPTIONS = [
67370
+ {
67371
+ id: "all",
67372
+ label: "Default",
67373
+ description: "Show every camera in the selected line filter."
67374
+ },
67375
+ {
67376
+ id: "red_only",
67377
+ label: "Only red",
67378
+ description: "Only show workstations in red."
67379
+ },
67380
+ {
67381
+ id: "slideshow",
67382
+ label: "Slideshow",
67383
+ description: "Automatically switches between all lines every 15 seconds."
67384
+ },
67385
+ {
67386
+ id: "worst_workstations",
67387
+ label: "Overall Underperformers",
67388
+ description: "Lowest performers based on full day efficiency"
67389
+ }
67390
+ ];
67391
+ var getHomeDisplayModeLabel = (displayMode) => HOME_DISPLAY_MODE_OPTIONS.find((option) => option.id === displayMode)?.label || "Default";
67125
67392
  var KPISection2 = KPISection;
67126
67393
  var DEBUG_DASHBOARD_LOGS3 = process.env.NEXT_PUBLIC_DEBUG_DASHBOARD === "true";
67127
67394
  var logDebug3 = (...args) => {
@@ -67132,6 +67399,9 @@ var EMPTY_LINE_IDS = [];
67132
67399
  var EMPTY_WORKSPACES = [];
67133
67400
  var ALL_GREEN_CELEBRATION_DURATION_MS = 6e3;
67134
67401
  var ALL_GREEN_MILESTONE_DURATION_MS = 6e3;
67402
+ var SLIDESHOW_ROTATION_INTERVAL_MS = 15e3;
67403
+ var SLIDESHOW_IDLE_REQUIRED_MS = 15e3;
67404
+ var SLIDESHOW_ACTIVE_LINE_STORAGE_KEY = "optifye_home_slideshow_active_line_id";
67135
67405
  var ALL_GREEN_CELEBRATION_SEEN_PREFIX = "optifye:all-green-celebration:v1:";
67136
67406
  var ALL_GREEN_MILESTONE_SEEN_PREFIX = "optifye:all-green-milestone:v1:";
67137
67407
  var formatAllGreenCelebrationTimer = (elapsedSeconds) => {
@@ -67261,6 +67531,31 @@ function HomeView({
67261
67531
  const [isLineSelectorOpen, setIsLineSelectorOpen] = React148.useState(false);
67262
67532
  const [pendingSelectedLineIds, setPendingSelectedLineIds] = React148.useState([]);
67263
67533
  const lineSelectorRef = React148.useRef(null);
67534
+ const [displayMode, setDisplayMode] = React148.useState(() => {
67535
+ if (typeof window === "undefined") {
67536
+ return "all";
67537
+ }
67538
+ try {
67539
+ const savedDisplayMode = sessionStorage.getItem("optifye_home_display_mode");
67540
+ return HOME_DISPLAY_MODE_OPTIONS.some((option) => option.id === savedDisplayMode) ? savedDisplayMode : "all";
67541
+ } catch {
67542
+ return "all";
67543
+ }
67544
+ });
67545
+ const [isDisplayModeMenuOpen, setIsDisplayModeMenuOpen] = React148.useState(false);
67546
+ const displayModeSelectorRef = React148.useRef(null);
67547
+ const readPersistedSlideshowActiveLineId = () => {
67548
+ if (typeof window === "undefined") {
67549
+ return null;
67550
+ }
67551
+ try {
67552
+ return sessionStorage.getItem(SLIDESHOW_ACTIVE_LINE_STORAGE_KEY);
67553
+ } catch {
67554
+ return null;
67555
+ }
67556
+ };
67557
+ const [slideshowActiveLineId, setSlideshowActiveLineId] = React148.useState(() => readPersistedSlideshowActiveLineId());
67558
+ const slideshowLastUserActivityAtRef = React148.useRef(Date.now());
67264
67559
  React148.useEffect(() => {
67265
67560
  if (isLineSelectorOpen) {
67266
67561
  setPendingSelectedLineIds(selectedLineIds);
@@ -67287,6 +67582,13 @@ function HomeView({
67287
67582
  console.warn("Failed to save line filter to sessionStorage:", error);
67288
67583
  }
67289
67584
  }, [selectedLineIds]);
67585
+ React148.useEffect(() => {
67586
+ try {
67587
+ sessionStorage.setItem("optifye_home_display_mode", displayMode);
67588
+ } catch (error) {
67589
+ console.warn("Failed to save display mode to sessionStorage:", error);
67590
+ }
67591
+ }, [displayMode]);
67290
67592
  React148.useEffect(() => {
67291
67593
  if (!isLineSelectorOpen) {
67292
67594
  return;
@@ -67301,6 +67603,20 @@ function HomeView({
67301
67603
  document.removeEventListener("mousedown", handleClickOutside);
67302
67604
  };
67303
67605
  }, [isLineSelectorOpen]);
67606
+ React148.useEffect(() => {
67607
+ if (!isDisplayModeMenuOpen) {
67608
+ return;
67609
+ }
67610
+ const handleClickOutside = (event) => {
67611
+ if (displayModeSelectorRef.current && !displayModeSelectorRef.current.contains(event.target)) {
67612
+ setIsDisplayModeMenuOpen(false);
67613
+ }
67614
+ };
67615
+ document.addEventListener("mousedown", handleClickOutside);
67616
+ return () => {
67617
+ document.removeEventListener("mousedown", handleClickOutside);
67618
+ };
67619
+ }, [isDisplayModeMenuOpen]);
67304
67620
  const primarySelectedLineId = selectedLineIds[0] || defaultHomeLineId;
67305
67621
  const isMultiLineSelection = selectedLineIds.length > 1;
67306
67622
  const selectedLineIdsKey = selectedLineIds.join(",");
@@ -67603,6 +67919,88 @@ function HomeView({
67603
67919
  })),
67604
67920
  [currentWorkspaceMetrics, activeBreakLineIds]
67605
67921
  );
67922
+ const slideshowLineIds = React148.useMemo(() => {
67923
+ const selectedLineIdSetForSlideshow = new Set(selectedLineIds);
67924
+ const linesWithVisibleWorkspaces = new Set(
67925
+ workspaceMetricsWithBreakState.map((workspace) => workspace.line_id).filter((lineId) => Boolean(lineId))
67926
+ );
67927
+ const selectedVisibleLines = visibleLineIds.filter((lineId) => selectedLineIdSetForSlideshow.has(lineId));
67928
+ const selectedLinesWithWorkspaces = selectedVisibleLines.filter((lineId) => linesWithVisibleWorkspaces.has(lineId));
67929
+ if (selectedLinesWithWorkspaces.length > 0) {
67930
+ return selectedLinesWithWorkspaces;
67931
+ }
67932
+ return selectedVisibleLines;
67933
+ }, [selectedLineIds, visibleLineIds, workspaceMetricsWithBreakState]);
67934
+ const slideshowLineIdsKey = slideshowLineIds.join(",");
67935
+ React148.useEffect(() => {
67936
+ if (displayMode !== "slideshow") {
67937
+ return;
67938
+ }
67939
+ slideshowLastUserActivityAtRef.current = Date.now();
67940
+ setSlideshowActiveLineId((previousLineId) => {
67941
+ if (previousLineId && slideshowLineIds.includes(previousLineId)) {
67942
+ return previousLineId;
67943
+ }
67944
+ const persistedLineId = readPersistedSlideshowActiveLineId();
67945
+ if (persistedLineId && slideshowLineIds.includes(persistedLineId)) {
67946
+ return persistedLineId;
67947
+ }
67948
+ if (primarySelectedLineId && slideshowLineIds.includes(primarySelectedLineId)) {
67949
+ return primarySelectedLineId;
67950
+ }
67951
+ return slideshowLineIds[0] || null;
67952
+ });
67953
+ }, [displayMode, primarySelectedLineId, slideshowLineIdsKey]);
67954
+ React148.useEffect(() => {
67955
+ if (typeof window === "undefined") {
67956
+ return;
67957
+ }
67958
+ try {
67959
+ if (slideshowActiveLineId) {
67960
+ sessionStorage.setItem(SLIDESHOW_ACTIVE_LINE_STORAGE_KEY, slideshowActiveLineId);
67961
+ } else {
67962
+ sessionStorage.removeItem(SLIDESHOW_ACTIVE_LINE_STORAGE_KEY);
67963
+ }
67964
+ } catch {
67965
+ }
67966
+ }, [slideshowActiveLineId]);
67967
+ React148.useEffect(() => {
67968
+ if (displayMode !== "slideshow") {
67969
+ return void 0;
67970
+ }
67971
+ const markActivity = () => {
67972
+ slideshowLastUserActivityAtRef.current = Date.now();
67973
+ };
67974
+ const eventOptions = { passive: true };
67975
+ const activityEvents = ["mousemove", "mousedown", "keydown", "touchstart", "wheel"];
67976
+ activityEvents.forEach((eventName) => {
67977
+ window.addEventListener(eventName, markActivity, eventOptions);
67978
+ });
67979
+ return () => {
67980
+ activityEvents.forEach((eventName) => {
67981
+ window.removeEventListener(eventName, markActivity, eventOptions);
67982
+ });
67983
+ };
67984
+ }, [displayMode]);
67985
+ React148.useEffect(() => {
67986
+ if (displayMode !== "slideshow" || slideshowLineIds.length <= 1) {
67987
+ return void 0;
67988
+ }
67989
+ const intervalId = window.setInterval(() => {
67990
+ const nowMs2 = Date.now();
67991
+ if (nowMs2 - slideshowLastUserActivityAtRef.current < SLIDESHOW_IDLE_REQUIRED_MS) {
67992
+ return;
67993
+ }
67994
+ setSlideshowActiveLineId((previousLineId) => {
67995
+ const previousIndex = previousLineId ? slideshowLineIds.indexOf(previousLineId) : -1;
67996
+ const nextIndex = previousIndex >= 0 ? (previousIndex + 1) % slideshowLineIds.length : 0;
67997
+ return slideshowLineIds[nextIndex] || null;
67998
+ });
67999
+ }, SLIDESHOW_ROTATION_INTERVAL_MS);
68000
+ return () => {
68001
+ window.clearInterval(intervalId);
68002
+ };
68003
+ }, [displayMode, slideshowLineIdsKey, slideshowLineIds]);
67606
68004
  const [breakNotificationsDismissed, setBreakNotificationsDismissed] = React148.useState(false);
67607
68005
  React148.useEffect(() => {
67608
68006
  if (currentActiveBreaks.length > 0) {
@@ -67614,6 +68012,11 @@ function HomeView({
67614
68012
  () => workspaceMetricsWithBreakState.filter((workspace) => Boolean(workspace.workspace_uuid || workspace.workspace_name)).length,
67615
68013
  [workspaceMetricsWithBreakState]
67616
68014
  );
68015
+ const worstPerformanceWorkspaceIds = React148.useMemo(
68016
+ () => Array.from(selectWorstPerformanceWorkspaceIds(workspaceMetricsWithBreakState)),
68017
+ [workspaceMetricsWithBreakState]
68018
+ );
68019
+ const activeWorstPerformanceWorkspaceIds = displayMode === "worst_workstations" ? worstPerformanceWorkspaceIds : EMPTY_LINE_IDS;
67617
68020
  const allGreenCelebrationSignature = React148.useMemo(() => {
67618
68021
  const workspaceSignature = workspaceMetricsWithBreakState.map((workspace) => workspace.workspace_uuid || `${workspace.line_id}:${workspace.workspace_name}`).filter(Boolean).sort().join(",");
67619
68022
  return `${selectedLineIds.join(",")}::${workspaceSignature}`;
@@ -68250,6 +68653,10 @@ function HomeView({
68250
68653
  selection_mode: isAllLinesSelection(normalizedLineIds) ? "all" : normalizedLineIds.length === 1 ? "single" : "custom",
68251
68654
  line_name: getLineSelectionLabel(normalizedLineIds)
68252
68655
  });
68656
+ trackCoreEvent("Dashboard Filter Selected", {
68657
+ filter_type: "Line Filter",
68658
+ filter_value: getLineSelectionLabel(normalizedLineIds)
68659
+ });
68253
68660
  }, [factoryViewId, getLineSelectionLabel, getTrackedLineScope, selectedLineIds, selectedLineIdsKey, visibleLineIds]);
68254
68661
  React148.useCallback(() => {
68255
68662
  updateSelectedLineIds(visibleLineIds);
@@ -68304,7 +68711,7 @@ function HomeView({
68304
68711
  "aria-expanded": isLineSelectorOpen,
68305
68712
  "aria-label": "Select lines",
68306
68713
  children: [
68307
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: getLineSelectionLabel(selectedLineIds) }),
68714
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: displayMode === "slideshow" && slideshowActiveLineId ? getLineSelectionLabel([slideshowActiveLineId]) : getLineSelectionLabel(selectedLineIds) }),
68308
68715
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `h-4 w-4 text-slate-400 transition-transform ${isLineSelectorOpen ? "rotate-180" : ""}` })
68309
68716
  ]
68310
68717
  }
@@ -68400,10 +68807,78 @@ function HomeView({
68400
68807
  selectedLineIds,
68401
68808
  pendingSelectedLineIds,
68402
68809
  lineSelectorSignalStatusByLine,
68810
+ displayMode,
68811
+ slideshowActiveLineId,
68403
68812
  visibleLineIds,
68404
68813
  updateSelectedLineIds,
68405
68814
  isAllLinesSelection
68406
68815
  ]);
68816
+ const displayModeSelectorComponent = React148.useMemo(() => /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: displayModeSelectorRef, className: "relative", children: [
68817
+ /* @__PURE__ */ jsxRuntime.jsx(
68818
+ "button",
68819
+ {
68820
+ type: "button",
68821
+ onClick: () => {
68822
+ setIsLineSelectorOpen(false);
68823
+ setIsDisplayModeMenuOpen((previous) => !previous);
68824
+ },
68825
+ className: `inline-flex h-9 w-9 items-center justify-center rounded-md border shadow-sm transition-colors ${displayMode === "all" ? "border-slate-200 bg-white text-slate-500 hover:bg-slate-50 hover:text-slate-700" : "border-blue-200 bg-blue-50 text-blue-600 hover:bg-blue-100"}`,
68826
+ "aria-haspopup": "menu",
68827
+ "aria-expanded": isDisplayModeMenuOpen,
68828
+ "aria-label": `Display mode: ${getHomeDisplayModeLabel(displayMode)}`,
68829
+ title: `Display mode: ${getHomeDisplayModeLabel(displayMode)}`,
68830
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Monitor, { className: "h-4 w-4" })
68831
+ }
68832
+ ),
68833
+ isDisplayModeMenuOpen ? /* @__PURE__ */ jsxRuntime.jsx(
68834
+ "div",
68835
+ {
68836
+ role: "menu",
68837
+ "aria-label": "Display modes",
68838
+ className: "absolute right-0 top-full z-50 mt-1.5 w-64 rounded-md border border-slate-200 bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none",
68839
+ children: HOME_DISPLAY_MODE_OPTIONS.map((option) => {
68840
+ const isSelected = option.id === displayMode;
68841
+ return /* @__PURE__ */ jsxRuntime.jsx(
68842
+ "button",
68843
+ {
68844
+ type: "button",
68845
+ role: "menuitemradio",
68846
+ "aria-checked": isSelected,
68847
+ onClick: () => {
68848
+ const nextSelectedLineIds = option.id === "slideshow" ? visibleLineIds : selectedLineIds;
68849
+ setDisplayMode(option.id);
68850
+ if (option.id === "slideshow") {
68851
+ updateSelectedLineIds(visibleLineIds);
68852
+ }
68853
+ setIsDisplayModeMenuOpen(false);
68854
+ trackCoreEvent("Monitor Display Filter Selected", {
68855
+ filter_name: option.label,
68856
+ filter_id: option.id,
68857
+ previous_display_mode: displayMode,
68858
+ selected_line_ids: nextSelectedLineIds,
68859
+ selected_line_count: nextSelectedLineIds.length,
68860
+ highlighted_workspace_count: option.id === "worst_workstations" ? worstPerformanceWorkspaceIds.length : 0
68861
+ });
68862
+ trackCoreEvent("Dashboard Filter Selected", {
68863
+ filter_type: "Display Mode",
68864
+ filter_value: option.label
68865
+ });
68866
+ },
68867
+ className: "flex w-full items-start px-4 py-2.5 text-left transition-colors hover:bg-slate-50",
68868
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col", children: [
68869
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
68870
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-sm ${isSelected ? "font-semibold text-blue-600" : "font-medium text-slate-700"}`, children: option.label }),
68871
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-3 inline-flex items-center gap-2", children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 text-blue-600" }) })
68872
+ ] }),
68873
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 text-xs text-slate-500", children: option.description })
68874
+ ] })
68875
+ },
68876
+ option.id
68877
+ );
68878
+ })
68879
+ }
68880
+ ) : null
68881
+ ] }), [displayMode, isDisplayModeMenuOpen, selectedLineIds, updateSelectedLineIds, visibleLineIds, worstPerformanceWorkspaceIds]);
68407
68882
  const gridToolbarControls = React148.useMemo(() => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-2", children: [
68408
68883
  /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { children: visibleAllGreenStreakDisplay ? /* @__PURE__ */ jsxRuntime.jsxs(
68409
68884
  motion.div,
@@ -68421,8 +68896,10 @@ function HomeView({
68421
68896
  },
68422
68897
  visibleAllGreenStreakDisplay.startedAt
68423
68898
  ) : null }),
68424
- lineSelectorComponent
68899
+ lineSelectorComponent,
68900
+ displayModeSelectorComponent
68425
68901
  ] }), [
68902
+ displayModeSelectorComponent,
68426
68903
  lineSelectorComponent,
68427
68904
  visibleAllGreenStreakDisplay
68428
68905
  ]);
@@ -68599,14 +69076,17 @@ function HomeView({
68599
69076
  children: React148__namespace.default.createElement(WorkspaceGrid, {
68600
69077
  workspaces: workspaceMetricsWithBreakState,
68601
69078
  blueComparisonWorkspaces: currentBlueComparisonWorkspaceMetrics || workspaceMetricsWithBreakState,
69079
+ worstPerformanceWorkspaceIds: activeWorstPerformanceWorkspaceIds,
68602
69080
  lineNames: mergedLineNames,
68603
- lineOrder: selectedLineIds,
69081
+ lineOrder: displayMode === "slideshow" ? slideshowLineIds : selectedLineIds,
69082
+ activeSlideshowLineId: displayMode === "slideshow" ? slideshowActiveLineId : null,
68604
69083
  factoryView: factoryViewId,
68605
69084
  legend: effectiveEfficiencyLegend,
68606
69085
  videoSources,
68607
69086
  videoStreamsByWorkspaceId: currentVideoStreamsByWorkspaceId,
68608
69087
  videoStreamsLoading: currentVideoStreamsLoading,
68609
69088
  displayNames: metricsDisplayNames,
69089
+ displayMode,
68610
69090
  hasFlowBuffers,
68611
69091
  className: "h-full",
68612
69092
  toolbarRightContent: gridToolbarControls,
@@ -68633,14 +69113,17 @@ function HomeView({
68633
69113
  workspaces: [],
68634
69114
  // Show empty grid while loading
68635
69115
  blueComparisonWorkspaces: [],
69116
+ worstPerformanceWorkspaceIds: [],
68636
69117
  lineNames: mergedLineNames,
68637
- lineOrder: selectedLineIds,
69118
+ lineOrder: displayMode === "slideshow" ? slideshowLineIds : selectedLineIds,
69119
+ activeSlideshowLineId: displayMode === "slideshow" ? slideshowActiveLineId : null,
68638
69120
  factoryView: factoryViewId,
68639
69121
  legend: effectiveEfficiencyLegend,
68640
69122
  videoSources,
68641
69123
  videoStreamsByWorkspaceId: currentVideoStreamsByWorkspaceId,
68642
69124
  videoStreamsLoading: currentVideoStreamsLoading,
68643
69125
  displayNames: metricsDisplayNames,
69126
+ displayMode,
68644
69127
  hasFlowBuffers,
68645
69128
  className: "h-full",
68646
69129
  toolbarRightContent: gridToolbarControls,
@@ -81081,9 +81564,11 @@ var WorkspaceDetailView = ({
81081
81564
  const prefetchLowMoments = async () => {
81082
81565
  try {
81083
81566
  const initData = await s3Service.getClipsInit(workspaceId, resolvedDate, resolvedShiftId, totalOutput);
81084
- const lowMomentsCount = Number(initData?.counts?.recent_flow_red_streak || 0);
81567
+ const hasLowMomentsCategory = Array.isArray(initData?.clipTypes) ? initData.clipTypes.some((type) => type?.type === "recent_flow_red_streak" || type?.id === "recent_flow_red_streak") : false;
81568
+ const lowMomentsCount = hasLowMomentsCategory ? Number(initData?.counts?.recent_flow_red_streak || 0) : 0;
81085
81569
  const lowMomentsKey = [
81086
81570
  "recent_flow_red_streak",
81571
+ workspaceId,
81087
81572
  resolvedDate,
81088
81573
  resolvedShiftId,
81089
81574
  initData?.snapshotDateTime ?? "nosnap",
@@ -81094,15 +81579,15 @@ var WorkspaceDetailView = ({
81094
81579
  return;
81095
81580
  }
81096
81581
  const initFirstVideo = initData?.firstClips?.recent_flow_red_streak ?? null;
81097
- if (lowMomentsCount <= 0) {
81098
- setLowMomentsPrefetch((prev) => prev?.key === lowMomentsKey ? {
81582
+ if (!hasLowMomentsCategory || lowMomentsCount <= 0) {
81583
+ setLowMomentsPrefetch({
81099
81584
  key: lowMomentsKey,
81100
81585
  metadata: [],
81101
81586
  firstVideo: null,
81102
81587
  total: 0,
81103
81588
  loading: false,
81104
81589
  error: null
81105
- } : prev);
81590
+ });
81106
81591
  prewarmedClipsRef.current.add(cacheKey);
81107
81592
  return;
81108
81593
  }