@optifye/dashboard-core 6.12.29 → 6.12.30

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.
@@ -5,6 +5,9 @@ declare const normalizeVideoGridMetricMode: (value: unknown, assemblyEnabled?: b
5
5
  declare const isRecentFlowVideoGridMetricMode: (value: unknown, assemblyEnabled?: boolean) => boolean;
6
6
  declare const isWipGatedVideoGridMetricMode: (value: unknown, assemblyEnabled?: boolean) => boolean;
7
7
 
8
+ type IdleReasonPaletteToken = 'red' | 'amber' | 'blue' | 'violet' | 'emerald' | 'cyan' | 'slate';
9
+ type IdleReasonIconToken = 'alert-triangle' | 'refresh-cw' | 'package' | 'clock' | 'user-x' | 'wrench' | 'activity' | 'clipboard-x' | 'help-circle';
10
+
8
11
  /** One row of per-SKU workspace metrics. Backend computes; frontend renders. */
9
12
  interface SkuBreakdownItem {
10
13
  /** UUID — `skus.id`. Stable identifier for selectors and threshold writes. */
@@ -149,6 +152,18 @@ interface PoorPerformingWorkspace {
149
152
  action_count: number;
150
153
  action_threshold: number;
151
154
  }
155
+ interface VideoGridStatusBadge {
156
+ kind: 'idle_reason' | 'no_plan';
157
+ label: string | null;
158
+ display_name: string;
159
+ palette_token: IdleReasonPaletteToken;
160
+ icon_token: IdleReasonIconToken;
161
+ is_known?: boolean;
162
+ title?: string;
163
+ anchor_minute?: number;
164
+ reason_minute?: number;
165
+ shift_elapsed_fraction?: number;
166
+ }
152
167
  interface WorkspaceMetrics {
153
168
  company_id: string;
154
169
  line_id: string;
@@ -170,6 +185,7 @@ interface WorkspaceMetrics {
170
185
  monitoring_mode?: 'output' | 'uptime';
171
186
  idle_time?: number;
172
187
  idle_time_hourly?: Record<string, any> | null;
188
+ idle_reason_hourly?: Record<string, any> | null;
173
189
  shift_start?: string;
174
190
  shift_end?: string;
175
191
  assembly_enabled?: boolean;
@@ -200,6 +216,7 @@ interface WorkspaceMetrics {
200
216
  incoming_wip_current?: number | null;
201
217
  incoming_wip_effective_at?: string | null;
202
218
  incoming_wip_buffer_name?: string | null;
219
+ video_grid_badges?: VideoGridStatusBadge[];
203
220
  /**
204
221
  * When present, controls whether the UI should show the exclamation indicator for this workstation.
205
222
  * - Flow-configured lines: true only when a WIP alert is active for a buffer that outputs to this workstation.
@@ -477,4 +494,4 @@ interface RecentFlowSnapshotGridProps {
477
494
  }
478
495
  declare const RecentFlowSnapshotGrid: React__default.FC<RecentFlowSnapshotGridProps>;
479
496
 
480
- export { type ActionThreshold as A, type CurrentWorkspaceSKU as C, type DashboardKPIs as D, type EfficiencyLegendUpdate as E, type KpiTrend as K, type LineSignal as L, type MonthlyTrendSummary as M, type PoorPerformingWorkspace as P, type RecentFlowSnapshotGridProps as R, type SkuBreakdownItem as S, type VideoGridMetricMode as V, type WorkspaceMetrics as W, type LineInfo as a, type WorkspaceDetailedMetrics as b, type SkuSegmentItem as c, type LineSkuBreakdownItem as d, type WorkspaceVideoStream as e, type Workspace as f, type WorkspaceCameraIpInfo as g, type WorkspaceActionUpdate as h, type ShiftConfiguration as i, type KpiSignal as j, type LineIssueResolutionSummary as k, type LineDetailedMetrics as l, type LineSignalSource as m, type KpiSignalSource as n, type KpiSignalMode as o, type ValueDelta as p, type PercentageDelta as q, type CountDelta as r, type DurationDelta as s, type WorkspaceCropRect as t, type LineThreshold as u, type ShiftConfigurationRecord as v, normalizeVideoGridMetricMode as w, isRecentFlowVideoGridMetricMode as x, isWipGatedVideoGridMetricMode as y, RecentFlowSnapshotGrid as z };
497
+ export { type ActionThreshold as A, RecentFlowSnapshotGrid as B, type CurrentWorkspaceSKU as C, type DashboardKPIs as D, type EfficiencyLegendUpdate as E, type KpiTrend as K, type LineSignal as L, type MonthlyTrendSummary as M, type PoorPerformingWorkspace as P, type RecentFlowSnapshotGridProps as R, type SkuBreakdownItem as S, type VideoGridMetricMode as V, type WorkspaceMetrics as W, type LineInfo as a, type WorkspaceDetailedMetrics as b, type SkuSegmentItem as c, type LineSkuBreakdownItem as d, type WorkspaceVideoStream as e, type Workspace as f, type WorkspaceCameraIpInfo as g, type WorkspaceActionUpdate as h, type ShiftConfiguration as i, type KpiSignal as j, type LineIssueResolutionSummary as k, type LineDetailedMetrics as l, type VideoGridStatusBadge as m, type LineSignalSource as n, type KpiSignalSource as o, type KpiSignalMode as p, type ValueDelta as q, type PercentageDelta as r, type CountDelta as s, type DurationDelta as t, type WorkspaceCropRect as u, type LineThreshold as v, type ShiftConfigurationRecord as w, normalizeVideoGridMetricMode as x, isRecentFlowVideoGridMetricMode as y, isWipGatedVideoGridMetricMode as z };
@@ -5,6 +5,9 @@ declare const normalizeVideoGridMetricMode: (value: unknown, assemblyEnabled?: b
5
5
  declare const isRecentFlowVideoGridMetricMode: (value: unknown, assemblyEnabled?: boolean) => boolean;
6
6
  declare const isWipGatedVideoGridMetricMode: (value: unknown, assemblyEnabled?: boolean) => boolean;
7
7
 
8
+ type IdleReasonPaletteToken = 'red' | 'amber' | 'blue' | 'violet' | 'emerald' | 'cyan' | 'slate';
9
+ type IdleReasonIconToken = 'alert-triangle' | 'refresh-cw' | 'package' | 'clock' | 'user-x' | 'wrench' | 'activity' | 'clipboard-x' | 'help-circle';
10
+
8
11
  /** One row of per-SKU workspace metrics. Backend computes; frontend renders. */
9
12
  interface SkuBreakdownItem {
10
13
  /** UUID — `skus.id`. Stable identifier for selectors and threshold writes. */
@@ -149,6 +152,18 @@ interface PoorPerformingWorkspace {
149
152
  action_count: number;
150
153
  action_threshold: number;
151
154
  }
155
+ interface VideoGridStatusBadge {
156
+ kind: 'idle_reason' | 'no_plan';
157
+ label: string | null;
158
+ display_name: string;
159
+ palette_token: IdleReasonPaletteToken;
160
+ icon_token: IdleReasonIconToken;
161
+ is_known?: boolean;
162
+ title?: string;
163
+ anchor_minute?: number;
164
+ reason_minute?: number;
165
+ shift_elapsed_fraction?: number;
166
+ }
152
167
  interface WorkspaceMetrics {
153
168
  company_id: string;
154
169
  line_id: string;
@@ -170,6 +185,7 @@ interface WorkspaceMetrics {
170
185
  monitoring_mode?: 'output' | 'uptime';
171
186
  idle_time?: number;
172
187
  idle_time_hourly?: Record<string, any> | null;
188
+ idle_reason_hourly?: Record<string, any> | null;
173
189
  shift_start?: string;
174
190
  shift_end?: string;
175
191
  assembly_enabled?: boolean;
@@ -200,6 +216,7 @@ interface WorkspaceMetrics {
200
216
  incoming_wip_current?: number | null;
201
217
  incoming_wip_effective_at?: string | null;
202
218
  incoming_wip_buffer_name?: string | null;
219
+ video_grid_badges?: VideoGridStatusBadge[];
203
220
  /**
204
221
  * When present, controls whether the UI should show the exclamation indicator for this workstation.
205
222
  * - Flow-configured lines: true only when a WIP alert is active for a buffer that outputs to this workstation.
@@ -477,4 +494,4 @@ interface RecentFlowSnapshotGridProps {
477
494
  }
478
495
  declare const RecentFlowSnapshotGrid: React__default.FC<RecentFlowSnapshotGridProps>;
479
496
 
480
- export { type ActionThreshold as A, type CurrentWorkspaceSKU as C, type DashboardKPIs as D, type EfficiencyLegendUpdate as E, type KpiTrend as K, type LineSignal as L, type MonthlyTrendSummary as M, type PoorPerformingWorkspace as P, type RecentFlowSnapshotGridProps as R, type SkuBreakdownItem as S, type VideoGridMetricMode as V, type WorkspaceMetrics as W, type LineInfo as a, type WorkspaceDetailedMetrics as b, type SkuSegmentItem as c, type LineSkuBreakdownItem as d, type WorkspaceVideoStream as e, type Workspace as f, type WorkspaceCameraIpInfo as g, type WorkspaceActionUpdate as h, type ShiftConfiguration as i, type KpiSignal as j, type LineIssueResolutionSummary as k, type LineDetailedMetrics as l, type LineSignalSource as m, type KpiSignalSource as n, type KpiSignalMode as o, type ValueDelta as p, type PercentageDelta as q, type CountDelta as r, type DurationDelta as s, type WorkspaceCropRect as t, type LineThreshold as u, type ShiftConfigurationRecord as v, normalizeVideoGridMetricMode as w, isRecentFlowVideoGridMetricMode as x, isWipGatedVideoGridMetricMode as y, RecentFlowSnapshotGrid as z };
497
+ export { type ActionThreshold as A, RecentFlowSnapshotGrid as B, type CurrentWorkspaceSKU as C, type DashboardKPIs as D, type EfficiencyLegendUpdate as E, type KpiTrend as K, type LineSignal as L, type MonthlyTrendSummary as M, type PoorPerformingWorkspace as P, type RecentFlowSnapshotGridProps as R, type SkuBreakdownItem as S, type VideoGridMetricMode as V, type WorkspaceMetrics as W, type LineInfo as a, type WorkspaceDetailedMetrics as b, type SkuSegmentItem as c, type LineSkuBreakdownItem as d, type WorkspaceVideoStream as e, type Workspace as f, type WorkspaceCameraIpInfo as g, type WorkspaceActionUpdate as h, type ShiftConfiguration as i, type KpiSignal as j, type LineIssueResolutionSummary as k, type LineDetailedMetrics as l, type VideoGridStatusBadge as m, type LineSignalSource as n, type KpiSignalSource as o, type KpiSignalMode as p, type ValueDelta as q, type PercentageDelta as r, type CountDelta as s, type DurationDelta as t, type WorkspaceCropRect as u, type LineThreshold as v, type ShiftConfigurationRecord as w, normalizeVideoGridMetricMode as x, isRecentFlowVideoGridMetricMode as y, isWipGatedVideoGridMetricMode as z };
@@ -1,2 +1,2 @@
1
- export { z as RecentFlowSnapshotGrid, R as RecentFlowSnapshotGridProps, W as WorkspaceMetrics, e as WorkspaceVideoStream } from './automation-DYa3jNEf.mjs';
1
+ export { B as RecentFlowSnapshotGrid, R as RecentFlowSnapshotGridProps, W as WorkspaceMetrics, e as WorkspaceVideoStream } from './automation-Jf6Isg6G.mjs';
2
2
  import 'react';
@@ -1,2 +1,2 @@
1
- export { z as RecentFlowSnapshotGrid, R as RecentFlowSnapshotGridProps, W as WorkspaceMetrics, e as WorkspaceVideoStream } from './automation-DYa3jNEf.js';
1
+ export { B as RecentFlowSnapshotGrid, R as RecentFlowSnapshotGridProps, W as WorkspaceMetrics, e as WorkspaceVideoStream } from './automation-Jf6Isg6G.js';
2
2
  import 'react';
@@ -1734,6 +1734,99 @@ var trackCoreEvent = (eventName, properties) => {
1734
1734
  return;
1735
1735
  }
1736
1736
  };
1737
+ var DEFAULT_PALETTE_TOKEN = "slate";
1738
+ var DEFAULT_ICON_TOKEN = "help-circle";
1739
+ var PALETTE_CONFIG = {
1740
+ red: {
1741
+ hex: "#dc2626",
1742
+ textClass: "text-red-600",
1743
+ bgClass: "bg-red-50",
1744
+ borderClass: "border-red-200"
1745
+ },
1746
+ amber: {
1747
+ hex: "#f59e0b",
1748
+ textClass: "text-amber-600",
1749
+ bgClass: "bg-amber-50",
1750
+ borderClass: "border-amber-200"
1751
+ },
1752
+ blue: {
1753
+ hex: "#3b82f6",
1754
+ textClass: "text-blue-600",
1755
+ bgClass: "bg-blue-50",
1756
+ borderClass: "border-blue-200"
1757
+ },
1758
+ violet: {
1759
+ hex: "#8b5cf6",
1760
+ textClass: "text-violet-600",
1761
+ bgClass: "bg-violet-50",
1762
+ borderClass: "border-violet-200"
1763
+ },
1764
+ emerald: {
1765
+ hex: "#10b981",
1766
+ textClass: "text-emerald-600",
1767
+ bgClass: "bg-emerald-50",
1768
+ borderClass: "border-emerald-200"
1769
+ },
1770
+ cyan: {
1771
+ hex: "#0891b2",
1772
+ textClass: "text-cyan-600",
1773
+ bgClass: "bg-cyan-50",
1774
+ borderClass: "border-cyan-200"
1775
+ },
1776
+ slate: {
1777
+ hex: "#64748b",
1778
+ textClass: "text-slate-600",
1779
+ bgClass: "bg-slate-50",
1780
+ borderClass: "border-slate-200"
1781
+ }
1782
+ };
1783
+ var ICON_CONFIG = {
1784
+ "alert-triangle": lucideReact.AlertTriangle,
1785
+ "refresh-cw": lucideReact.RefreshCw,
1786
+ package: lucideReact.Package,
1787
+ clock: lucideReact.Clock,
1788
+ "user-x": lucideReact.UserX,
1789
+ wrench: lucideReact.Wrench,
1790
+ activity: lucideReact.Activity,
1791
+ "clipboard-x": lucideReact.ClipboardX,
1792
+ "help-circle": lucideReact.HelpCircle
1793
+ };
1794
+ var humanizeIdleReasonLabel = (value) => {
1795
+ const text = String(value || "").trim();
1796
+ if (!text) {
1797
+ return "Unknown";
1798
+ }
1799
+ return text.replace(/_/g, " ").split(/\s+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
1800
+ };
1801
+ var normalizePaletteToken = (value) => {
1802
+ const token = String(value || "").trim().toLowerCase();
1803
+ if (token in PALETTE_CONFIG) {
1804
+ return token;
1805
+ }
1806
+ return DEFAULT_PALETTE_TOKEN;
1807
+ };
1808
+ var normalizeIconToken = (value) => {
1809
+ const token = String(value || "").trim().toLowerCase();
1810
+ if (token in ICON_CONFIG) {
1811
+ return token;
1812
+ }
1813
+ return DEFAULT_ICON_TOKEN;
1814
+ };
1815
+ var getIdleReasonPresentation = (metadata) => {
1816
+ const label = typeof metadata?.label === "string" && metadata.label.trim() ? metadata.label.trim() : null;
1817
+ const displayName = typeof metadata?.displayName === "string" && metadata.displayName.trim() ? metadata.displayName.trim() : humanizeIdleReasonLabel(label);
1818
+ const paletteToken = normalizePaletteToken(metadata?.paletteToken);
1819
+ const iconToken = normalizeIconToken(metadata?.iconToken);
1820
+ return {
1821
+ label,
1822
+ displayName,
1823
+ paletteToken,
1824
+ iconToken,
1825
+ isKnown: Boolean(metadata?.isKnown),
1826
+ Icon: ICON_CONFIG[iconToken],
1827
+ ...PALETTE_CONFIG[paletteToken]
1828
+ };
1829
+ };
1737
1830
 
1738
1831
  // src/lib/constants/videoGridMetricMode.ts
1739
1832
  var VALID_VIDEO_GRID_METRIC_MODES = /* @__PURE__ */ new Set([
@@ -1875,6 +1968,20 @@ var getVideoGridColorState = (workspace, legend = DEFAULT_EFFICIENCY_LEGEND, blu
1875
1968
  }
1876
1969
  return baseColor;
1877
1970
  };
1971
+ var getStatusBadgeSignature = (workspace) => {
1972
+ const badges = workspace.video_grid_badges || [];
1973
+ return badges.map((badge) => [
1974
+ badge.kind,
1975
+ badge.label,
1976
+ badge.display_name,
1977
+ badge.palette_token,
1978
+ badge.icon_token,
1979
+ badge.anchor_minute,
1980
+ badge.reason_minute,
1981
+ badge.shift_elapsed_fraction,
1982
+ badge.title
1983
+ ].join(":")).join("|");
1984
+ };
1878
1985
  function getTrendArrowAndColor(trend) {
1879
1986
  if (trend > 0) {
1880
1987
  return { arrow: "\u2191", color: "text-green-400" };
@@ -1906,6 +2013,7 @@ var VideoCard = React__default.default.memo(({
1906
2013
  }) => {
1907
2014
  const videoRef = React.useRef(null);
1908
2015
  const canvasRef = React.useRef(null);
2016
+ const statusBadgeIdPrefix = React.useId();
1909
2017
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
1910
2018
  const { isStale: isStreamStale } = useHlsStreamWithCropping(videoRef, canvasRef, {
1911
2019
  src: hlsUrl,
@@ -1932,6 +2040,7 @@ var VideoCard = React__default.default.memo(({
1932
2040
  const shouldRenderMetricBadge = hasDisplayMetric;
1933
2041
  const badgeTitle = isHighEfficiencyOverride ? `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%` : hasVideoGridRecentFlow(workspace) ? `Flow ${Math.round(videoGridDisplayValue ?? 0)}%` : isRecentFlowCard ? "Flow unavailable" : `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%`;
1934
2042
  const badgeLabel = `${Math.round(videoGridDisplayValue ?? 0)}%`;
2043
+ const statusBadges = workspace.video_grid_badges || [];
1935
2044
  const efficiencyOverlayClass = videoGridColorState === "green" ? "bg-[#00D654]/25" : videoGridColorState === "blue" ? "bg-[#0EA5E9]/30" : videoGridColorState === "yellow" ? "bg-[#FFD700]/30" : videoGridColorState === "red" ? "bg-[#FF2D0A]/30" : "bg-transparent";
1936
2045
  const efficiencyBarClass = videoGridColorState === "green" ? "bg-[#00AB45]" : videoGridColorState === "blue" ? "bg-[#0EA5E9]" : videoGridColorState === "yellow" ? "bg-[#FFB020]" : videoGridColorState === "red" ? "bg-[#E34329]" : "bg-gray-500/70";
1937
2046
  const efficiencyStatus = videoGridColorState === "green" ? "High" : videoGridColorState === "blue" ? "Best" : videoGridColorState === "yellow" ? "Medium" : videoGridColorState === "red" ? "Low" : "Neutral";
@@ -2010,6 +2119,61 @@ var VideoCard = React__default.default.memo(({
2010
2119
  children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${compact ? "text-[10px]" : "text-xs"} font-semibold`, children: badgeLabel })
2011
2120
  }
2012
2121
  ) }),
2122
+ statusBadges.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
2123
+ "div",
2124
+ {
2125
+ "data-testid": "video-card-status-badges",
2126
+ className: `absolute ${compact ? "top-1.5 left-1.5 gap-1" : "top-2 left-2 gap-1.5"} z-30 flex items-center`,
2127
+ children: statusBadges.map((badge, index) => {
2128
+ const presentation = getIdleReasonPresentation({
2129
+ label: badge.label,
2130
+ displayName: badge.display_name,
2131
+ paletteToken: badge.palette_token,
2132
+ iconToken: badge.icon_token,
2133
+ isKnown: badge.is_known
2134
+ });
2135
+ const Icon = presentation.Icon;
2136
+ const tooltipText = presentation.displayName;
2137
+ const tooltipId = `video-card-status-tooltip-${statusBadgeIdPrefix}-${badge.kind}-${index}`;
2138
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2139
+ "div",
2140
+ {
2141
+ "data-testid": `video-card-status-badge-${badge.kind}`,
2142
+ "aria-label": tooltipText,
2143
+ "aria-describedby": tooltipId,
2144
+ className: `group relative inline-flex shrink-0 items-center justify-center rounded-full bg-slate-950/70 border-2 shadow-[0_3px_10px_rgba(0,0,0,0.34),inset_0_0_0_1px_rgba(255,255,255,0.18)] ${compact ? "h-10 w-10" : "h-11 w-11"}`,
2145
+ style: {
2146
+ borderColor: presentation.hex
2147
+ },
2148
+ children: [
2149
+ /* @__PURE__ */ jsxRuntime.jsx(
2150
+ Icon,
2151
+ {
2152
+ "aria-hidden": "true",
2153
+ className: `${compact ? "h-5 w-5" : "h-6 w-6"} text-white`,
2154
+ strokeWidth: 2.4
2155
+ }
2156
+ ),
2157
+ /* @__PURE__ */ jsxRuntime.jsxs(
2158
+ "span",
2159
+ {
2160
+ id: tooltipId,
2161
+ role: "tooltip",
2162
+ "data-testid": `video-card-status-tooltip-${badge.kind}`,
2163
+ className: "pointer-events-none absolute left-0 top-full mt-2 whitespace-nowrap rounded-md border border-white/10 bg-slate-950/95 px-2 py-1 text-[11px] font-semibold leading-none text-white opacity-0 shadow-[0_6px_18px_rgba(0,0,0,0.34)] transition-opacity duration-150 group-hover:opacity-100",
2164
+ children: [
2165
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 left-3 h-2 w-2 rotate-45 border-l border-t border-white/10 bg-slate-950/95" }),
2166
+ tooltipText
2167
+ ]
2168
+ }
2169
+ )
2170
+ ]
2171
+ },
2172
+ `${badge.kind}-${badge.label || index}-${badge.reason_minute ?? index}`
2173
+ );
2174
+ })
2175
+ }
2176
+ ),
2013
2177
  /* @__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(
2014
2178
  "div",
2015
2179
  {
@@ -2046,7 +2210,7 @@ var VideoCard = React__default.default.memo(({
2046
2210
  }
2047
2211
  );
2048
2212
  }, (prevProps, nextProps) => {
2049
- if (prevProps.workspace.efficiency !== nextProps.workspace.efficiency || prevProps.workspace.assembly_enabled !== nextProps.workspace.assembly_enabled || prevProps.workspace.video_grid_metric_mode !== nextProps.workspace.video_grid_metric_mode || prevProps.workspace.recent_flow_percent !== nextProps.workspace.recent_flow_percent || prevProps.workspace.recent_flow_effective_end_at !== nextProps.workspace.recent_flow_effective_end_at || prevProps.workspace.recent_flow_forced_zero_after_shift !== nextProps.workspace.recent_flow_forced_zero_after_shift || prevProps.workspace.scheduled_break_active !== nextProps.workspace.scheduled_break_active || prevProps.workspace.incoming_wip_current !== nextProps.workspace.incoming_wip_current || prevProps.workspace.incoming_wip_buffer_name !== nextProps.workspace.incoming_wip_buffer_name || prevProps.workspace.trend !== nextProps.workspace.trend || prevProps.workspace.performance_score !== nextProps.workspace.performance_score || prevProps.workspace.pph !== nextProps.workspace.pph) {
2213
+ if (prevProps.workspace.efficiency !== nextProps.workspace.efficiency || prevProps.workspace.assembly_enabled !== nextProps.workspace.assembly_enabled || prevProps.workspace.video_grid_metric_mode !== nextProps.workspace.video_grid_metric_mode || prevProps.workspace.recent_flow_percent !== nextProps.workspace.recent_flow_percent || prevProps.workspace.recent_flow_effective_end_at !== nextProps.workspace.recent_flow_effective_end_at || prevProps.workspace.recent_flow_forced_zero_after_shift !== nextProps.workspace.recent_flow_forced_zero_after_shift || prevProps.workspace.scheduled_break_active !== nextProps.workspace.scheduled_break_active || prevProps.workspace.incoming_wip_current !== nextProps.workspace.incoming_wip_current || prevProps.workspace.incoming_wip_buffer_name !== nextProps.workspace.incoming_wip_buffer_name || getStatusBadgeSignature(prevProps.workspace) !== getStatusBadgeSignature(nextProps.workspace) || prevProps.workspace.trend !== nextProps.workspace.trend || prevProps.workspace.performance_score !== nextProps.workspace.performance_score || prevProps.workspace.pph !== nextProps.workspace.pph) {
2050
2214
  return false;
2051
2215
  }
2052
2216
  if (prevProps.workspace.workspace_uuid !== nextProps.workspace.workspace_uuid || prevProps.workspace.workspace_name !== nextProps.workspace.workspace_name || prevProps.workspace.line_id !== nextProps.workspace.line_id) {
@@ -1,5 +1,5 @@
1
- import React, { useRef, useCallback, useEffect, useState, useMemo } from 'react';
2
- import { Camera, AlertTriangle } from 'lucide-react';
1
+ import React, { useRef, useId, useCallback, useEffect, useState, useMemo } from 'react';
2
+ import { Camera, AlertTriangle, HelpCircle, ClipboardX, Activity, Wrench, UserX, Clock, Package, RefreshCw } from 'lucide-react';
3
3
  import Hls from 'hls.js';
4
4
  import 'mixpanel-browser';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -1727,6 +1727,99 @@ var trackCoreEvent = (eventName, properties) => {
1727
1727
  return;
1728
1728
  }
1729
1729
  };
1730
+ var DEFAULT_PALETTE_TOKEN = "slate";
1731
+ var DEFAULT_ICON_TOKEN = "help-circle";
1732
+ var PALETTE_CONFIG = {
1733
+ red: {
1734
+ hex: "#dc2626",
1735
+ textClass: "text-red-600",
1736
+ bgClass: "bg-red-50",
1737
+ borderClass: "border-red-200"
1738
+ },
1739
+ amber: {
1740
+ hex: "#f59e0b",
1741
+ textClass: "text-amber-600",
1742
+ bgClass: "bg-amber-50",
1743
+ borderClass: "border-amber-200"
1744
+ },
1745
+ blue: {
1746
+ hex: "#3b82f6",
1747
+ textClass: "text-blue-600",
1748
+ bgClass: "bg-blue-50",
1749
+ borderClass: "border-blue-200"
1750
+ },
1751
+ violet: {
1752
+ hex: "#8b5cf6",
1753
+ textClass: "text-violet-600",
1754
+ bgClass: "bg-violet-50",
1755
+ borderClass: "border-violet-200"
1756
+ },
1757
+ emerald: {
1758
+ hex: "#10b981",
1759
+ textClass: "text-emerald-600",
1760
+ bgClass: "bg-emerald-50",
1761
+ borderClass: "border-emerald-200"
1762
+ },
1763
+ cyan: {
1764
+ hex: "#0891b2",
1765
+ textClass: "text-cyan-600",
1766
+ bgClass: "bg-cyan-50",
1767
+ borderClass: "border-cyan-200"
1768
+ },
1769
+ slate: {
1770
+ hex: "#64748b",
1771
+ textClass: "text-slate-600",
1772
+ bgClass: "bg-slate-50",
1773
+ borderClass: "border-slate-200"
1774
+ }
1775
+ };
1776
+ var ICON_CONFIG = {
1777
+ "alert-triangle": AlertTriangle,
1778
+ "refresh-cw": RefreshCw,
1779
+ package: Package,
1780
+ clock: Clock,
1781
+ "user-x": UserX,
1782
+ wrench: Wrench,
1783
+ activity: Activity,
1784
+ "clipboard-x": ClipboardX,
1785
+ "help-circle": HelpCircle
1786
+ };
1787
+ var humanizeIdleReasonLabel = (value) => {
1788
+ const text = String(value || "").trim();
1789
+ if (!text) {
1790
+ return "Unknown";
1791
+ }
1792
+ return text.replace(/_/g, " ").split(/\s+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
1793
+ };
1794
+ var normalizePaletteToken = (value) => {
1795
+ const token = String(value || "").trim().toLowerCase();
1796
+ if (token in PALETTE_CONFIG) {
1797
+ return token;
1798
+ }
1799
+ return DEFAULT_PALETTE_TOKEN;
1800
+ };
1801
+ var normalizeIconToken = (value) => {
1802
+ const token = String(value || "").trim().toLowerCase();
1803
+ if (token in ICON_CONFIG) {
1804
+ return token;
1805
+ }
1806
+ return DEFAULT_ICON_TOKEN;
1807
+ };
1808
+ var getIdleReasonPresentation = (metadata) => {
1809
+ const label = typeof metadata?.label === "string" && metadata.label.trim() ? metadata.label.trim() : null;
1810
+ const displayName = typeof metadata?.displayName === "string" && metadata.displayName.trim() ? metadata.displayName.trim() : humanizeIdleReasonLabel(label);
1811
+ const paletteToken = normalizePaletteToken(metadata?.paletteToken);
1812
+ const iconToken = normalizeIconToken(metadata?.iconToken);
1813
+ return {
1814
+ label,
1815
+ displayName,
1816
+ paletteToken,
1817
+ iconToken,
1818
+ isKnown: Boolean(metadata?.isKnown),
1819
+ Icon: ICON_CONFIG[iconToken],
1820
+ ...PALETTE_CONFIG[paletteToken]
1821
+ };
1822
+ };
1730
1823
 
1731
1824
  // src/lib/constants/videoGridMetricMode.ts
1732
1825
  var VALID_VIDEO_GRID_METRIC_MODES = /* @__PURE__ */ new Set([
@@ -1868,6 +1961,20 @@ var getVideoGridColorState = (workspace, legend = DEFAULT_EFFICIENCY_LEGEND, blu
1868
1961
  }
1869
1962
  return baseColor;
1870
1963
  };
1964
+ var getStatusBadgeSignature = (workspace) => {
1965
+ const badges = workspace.video_grid_badges || [];
1966
+ return badges.map((badge) => [
1967
+ badge.kind,
1968
+ badge.label,
1969
+ badge.display_name,
1970
+ badge.palette_token,
1971
+ badge.icon_token,
1972
+ badge.anchor_minute,
1973
+ badge.reason_minute,
1974
+ badge.shift_elapsed_fraction,
1975
+ badge.title
1976
+ ].join(":")).join("|");
1977
+ };
1871
1978
  function getTrendArrowAndColor(trend) {
1872
1979
  if (trend > 0) {
1873
1980
  return { arrow: "\u2191", color: "text-green-400" };
@@ -1899,6 +2006,7 @@ var VideoCard = React.memo(({
1899
2006
  }) => {
1900
2007
  const videoRef = useRef(null);
1901
2008
  const canvasRef = useRef(null);
2009
+ const statusBadgeIdPrefix = useId();
1902
2010
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
1903
2011
  const { isStale: isStreamStale } = useHlsStreamWithCropping(videoRef, canvasRef, {
1904
2012
  src: hlsUrl,
@@ -1925,6 +2033,7 @@ var VideoCard = React.memo(({
1925
2033
  const shouldRenderMetricBadge = hasDisplayMetric;
1926
2034
  const badgeTitle = isHighEfficiencyOverride ? `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%` : hasVideoGridRecentFlow(workspace) ? `Flow ${Math.round(videoGridDisplayValue ?? 0)}%` : isRecentFlowCard ? "Flow unavailable" : `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%`;
1927
2035
  const badgeLabel = `${Math.round(videoGridDisplayValue ?? 0)}%`;
2036
+ const statusBadges = workspace.video_grid_badges || [];
1928
2037
  const efficiencyOverlayClass = videoGridColorState === "green" ? "bg-[#00D654]/25" : videoGridColorState === "blue" ? "bg-[#0EA5E9]/30" : videoGridColorState === "yellow" ? "bg-[#FFD700]/30" : videoGridColorState === "red" ? "bg-[#FF2D0A]/30" : "bg-transparent";
1929
2038
  const efficiencyBarClass = videoGridColorState === "green" ? "bg-[#00AB45]" : videoGridColorState === "blue" ? "bg-[#0EA5E9]" : videoGridColorState === "yellow" ? "bg-[#FFB020]" : videoGridColorState === "red" ? "bg-[#E34329]" : "bg-gray-500/70";
1930
2039
  const efficiencyStatus = videoGridColorState === "green" ? "High" : videoGridColorState === "blue" ? "Best" : videoGridColorState === "yellow" ? "Medium" : videoGridColorState === "red" ? "Low" : "Neutral";
@@ -2003,6 +2112,61 @@ var VideoCard = React.memo(({
2003
2112
  children: /* @__PURE__ */ jsx("span", { className: `${compact ? "text-[10px]" : "text-xs"} font-semibold`, children: badgeLabel })
2004
2113
  }
2005
2114
  ) }),
2115
+ statusBadges.length > 0 && /* @__PURE__ */ jsx(
2116
+ "div",
2117
+ {
2118
+ "data-testid": "video-card-status-badges",
2119
+ className: `absolute ${compact ? "top-1.5 left-1.5 gap-1" : "top-2 left-2 gap-1.5"} z-30 flex items-center`,
2120
+ children: statusBadges.map((badge, index) => {
2121
+ const presentation = getIdleReasonPresentation({
2122
+ label: badge.label,
2123
+ displayName: badge.display_name,
2124
+ paletteToken: badge.palette_token,
2125
+ iconToken: badge.icon_token,
2126
+ isKnown: badge.is_known
2127
+ });
2128
+ const Icon = presentation.Icon;
2129
+ const tooltipText = presentation.displayName;
2130
+ const tooltipId = `video-card-status-tooltip-${statusBadgeIdPrefix}-${badge.kind}-${index}`;
2131
+ return /* @__PURE__ */ jsxs(
2132
+ "div",
2133
+ {
2134
+ "data-testid": `video-card-status-badge-${badge.kind}`,
2135
+ "aria-label": tooltipText,
2136
+ "aria-describedby": tooltipId,
2137
+ className: `group relative inline-flex shrink-0 items-center justify-center rounded-full bg-slate-950/70 border-2 shadow-[0_3px_10px_rgba(0,0,0,0.34),inset_0_0_0_1px_rgba(255,255,255,0.18)] ${compact ? "h-10 w-10" : "h-11 w-11"}`,
2138
+ style: {
2139
+ borderColor: presentation.hex
2140
+ },
2141
+ children: [
2142
+ /* @__PURE__ */ jsx(
2143
+ Icon,
2144
+ {
2145
+ "aria-hidden": "true",
2146
+ className: `${compact ? "h-5 w-5" : "h-6 w-6"} text-white`,
2147
+ strokeWidth: 2.4
2148
+ }
2149
+ ),
2150
+ /* @__PURE__ */ jsxs(
2151
+ "span",
2152
+ {
2153
+ id: tooltipId,
2154
+ role: "tooltip",
2155
+ "data-testid": `video-card-status-tooltip-${badge.kind}`,
2156
+ className: "pointer-events-none absolute left-0 top-full mt-2 whitespace-nowrap rounded-md border border-white/10 bg-slate-950/95 px-2 py-1 text-[11px] font-semibold leading-none text-white opacity-0 shadow-[0_6px_18px_rgba(0,0,0,0.34)] transition-opacity duration-150 group-hover:opacity-100",
2157
+ children: [
2158
+ /* @__PURE__ */ jsx("span", { className: "absolute -top-1 left-3 h-2 w-2 rotate-45 border-l border-t border-white/10 bg-slate-950/95" }),
2159
+ tooltipText
2160
+ ]
2161
+ }
2162
+ )
2163
+ ]
2164
+ },
2165
+ `${badge.kind}-${badge.label || index}-${badge.reason_minute ?? index}`
2166
+ );
2167
+ })
2168
+ }
2169
+ ),
2006
2170
  /* @__PURE__ */ jsx("div", { className: `absolute bottom-0 left-0 right-0 ${compact ? "h-0.5" : "h-1"} bg-black/50 z-30`, children: /* @__PURE__ */ jsx(
2007
2171
  "div",
2008
2172
  {
@@ -2039,7 +2203,7 @@ var VideoCard = React.memo(({
2039
2203
  }
2040
2204
  );
2041
2205
  }, (prevProps, nextProps) => {
2042
- if (prevProps.workspace.efficiency !== nextProps.workspace.efficiency || prevProps.workspace.assembly_enabled !== nextProps.workspace.assembly_enabled || prevProps.workspace.video_grid_metric_mode !== nextProps.workspace.video_grid_metric_mode || prevProps.workspace.recent_flow_percent !== nextProps.workspace.recent_flow_percent || prevProps.workspace.recent_flow_effective_end_at !== nextProps.workspace.recent_flow_effective_end_at || prevProps.workspace.recent_flow_forced_zero_after_shift !== nextProps.workspace.recent_flow_forced_zero_after_shift || prevProps.workspace.scheduled_break_active !== nextProps.workspace.scheduled_break_active || prevProps.workspace.incoming_wip_current !== nextProps.workspace.incoming_wip_current || prevProps.workspace.incoming_wip_buffer_name !== nextProps.workspace.incoming_wip_buffer_name || prevProps.workspace.trend !== nextProps.workspace.trend || prevProps.workspace.performance_score !== nextProps.workspace.performance_score || prevProps.workspace.pph !== nextProps.workspace.pph) {
2206
+ if (prevProps.workspace.efficiency !== nextProps.workspace.efficiency || prevProps.workspace.assembly_enabled !== nextProps.workspace.assembly_enabled || prevProps.workspace.video_grid_metric_mode !== nextProps.workspace.video_grid_metric_mode || prevProps.workspace.recent_flow_percent !== nextProps.workspace.recent_flow_percent || prevProps.workspace.recent_flow_effective_end_at !== nextProps.workspace.recent_flow_effective_end_at || prevProps.workspace.recent_flow_forced_zero_after_shift !== nextProps.workspace.recent_flow_forced_zero_after_shift || prevProps.workspace.scheduled_break_active !== nextProps.workspace.scheduled_break_active || prevProps.workspace.incoming_wip_current !== nextProps.workspace.incoming_wip_current || prevProps.workspace.incoming_wip_buffer_name !== nextProps.workspace.incoming_wip_buffer_name || getStatusBadgeSignature(prevProps.workspace) !== getStatusBadgeSignature(nextProps.workspace) || prevProps.workspace.trend !== nextProps.workspace.trend || prevProps.workspace.performance_score !== nextProps.workspace.performance_score || prevProps.workspace.pph !== nextProps.workspace.pph) {
2043
2207
  return false;
2044
2208
  }
2045
2209
  if (prevProps.workspace.workspace_uuid !== nextProps.workspace.workspace_uuid || prevProps.workspace.workspace_name !== nextProps.workspace.workspace_name || prevProps.workspace.line_id !== nextProps.workspace.line_id) {
package/dist/index.css CHANGED
@@ -538,6 +538,9 @@ body {
538
538
  .left-1 {
539
539
  left: 0.25rem;
540
540
  }
541
+ .left-1\.5 {
542
+ left: 0.375rem;
543
+ }
541
544
  .left-1\/2 {
542
545
  left: 50%;
543
546
  }
@@ -595,6 +598,9 @@ body {
595
598
  .top-1 {
596
599
  top: 0.25rem;
597
600
  }
601
+ .top-1\.5 {
602
+ top: 0.375rem;
603
+ }
598
604
  .top-1\/2 {
599
605
  top: 50%;
600
606
  }
@@ -1740,6 +1746,10 @@ body {
1740
1746
  --tw-rotate: 180deg;
1741
1747
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1742
1748
  }
1749
+ .rotate-45 {
1750
+ --tw-rotate: 45deg;
1751
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1752
+ }
1743
1753
  .scale-0 {
1744
1754
  --tw-scale-x: 0;
1745
1755
  --tw-scale-y: 0;
@@ -2261,6 +2271,9 @@ body {
2261
2271
  .border-b-\[6px\] {
2262
2272
  border-bottom-width: 6px;
2263
2273
  }
2274
+ .border-l {
2275
+ border-left-width: 1px;
2276
+ }
2264
2277
  .border-l-2 {
2265
2278
  border-left-width: 2px;
2266
2279
  }
@@ -3091,6 +3104,12 @@ body {
3091
3104
  .bg-slate-900\/60 {
3092
3105
  background-color: rgb(15 23 42 / 0.6);
3093
3106
  }
3107
+ .bg-slate-950\/70 {
3108
+ background-color: rgb(2 6 23 / 0.7);
3109
+ }
3110
+ .bg-slate-950\/95 {
3111
+ background-color: rgb(2 6 23 / 0.95);
3112
+ }
3094
3113
  .bg-teal-100 {
3095
3114
  --tw-bg-opacity: 1;
3096
3115
  background-color: rgb(204 251 241 / var(--tw-bg-opacity, 1));
@@ -4974,6 +4993,22 @@ body {
4974
4993
  var(--tw-ring-shadow, 0 0 #0000),
4975
4994
  var(--tw-shadow);
4976
4995
  }
4996
+ .shadow-\[0_3px_10px_rgba\(0\,0\,0\,0\.34\)\,inset_0_0_0_1px_rgba\(255\,255\,255\,0\.18\)\] {
4997
+ --tw-shadow: 0 3px 10px rgba(0,0,0,0.34),inset 0 0 0 1px rgba(255,255,255,0.18);
4998
+ --tw-shadow-colored: 0 3px 10px var(--tw-shadow-color), inset 0 0 0 1px var(--tw-shadow-color);
4999
+ box-shadow:
5000
+ var(--tw-ring-offset-shadow, 0 0 #0000),
5001
+ var(--tw-ring-shadow, 0 0 #0000),
5002
+ var(--tw-shadow);
5003
+ }
5004
+ .shadow-\[0_6px_18px_rgba\(0\,0\,0\,0\.34\)\] {
5005
+ --tw-shadow: 0 6px 18px rgba(0,0,0,0.34);
5006
+ --tw-shadow-colored: 0 6px 18px var(--tw-shadow-color);
5007
+ box-shadow:
5008
+ var(--tw-ring-offset-shadow, 0 0 #0000),
5009
+ var(--tw-ring-shadow, 0 0 #0000),
5010
+ var(--tw-shadow);
5011
+ }
4977
5012
  .shadow-inner {
4978
5013
  --tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
4979
5014
  --tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { L as LineSignal, V as VideoGridMetricMode, W as WorkspaceMetrics, a as LineInfo, b as WorkspaceDetailedMetrics, S as SkuBreakdownItem, c as SkuSegmentItem, d as LineSkuBreakdownItem, E as EfficiencyLegendUpdate, D as DashboardKPIs, P as PoorPerformingWorkspace, e as WorkspaceVideoStream, K as KpiTrend, M as MonthlyTrendSummary, f as Workspace, g as WorkspaceCameraIpInfo, h as WorkspaceActionUpdate, A as ActionThreshold, i as ShiftConfiguration, j as KpiSignal, k as LineIssueResolutionSummary } from './automation-DYa3jNEf.mjs';
2
- export { r as CountDelta, C as CurrentWorkspaceSKU, s as DurationDelta, o as KpiSignalMode, n as KpiSignalSource, l as LineDetailedMetrics, m as LineSignalSource, u as LineThreshold, q as PercentageDelta, z as RecentFlowSnapshotGrid, R as RecentFlowSnapshotGridProps, v as ShiftConfigurationRecord, p as ValueDelta, t as WorkspaceCropRect, x as isRecentFlowVideoGridMetricMode, y as isWipGatedVideoGridMetricMode, w as normalizeVideoGridMetricMode } from './automation-DYa3jNEf.mjs';
1
+ import { L as LineSignal, V as VideoGridMetricMode, W as WorkspaceMetrics, a as LineInfo, b as WorkspaceDetailedMetrics, S as SkuBreakdownItem, c as SkuSegmentItem, d as LineSkuBreakdownItem, E as EfficiencyLegendUpdate, D as DashboardKPIs, P as PoorPerformingWorkspace, e as WorkspaceVideoStream, K as KpiTrend, M as MonthlyTrendSummary, f as Workspace, g as WorkspaceCameraIpInfo, h as WorkspaceActionUpdate, A as ActionThreshold, i as ShiftConfiguration, j as KpiSignal, k as LineIssueResolutionSummary } from './automation-Jf6Isg6G.mjs';
2
+ export { s as CountDelta, C as CurrentWorkspaceSKU, t as DurationDelta, p as KpiSignalMode, o as KpiSignalSource, l as LineDetailedMetrics, n as LineSignalSource, v as LineThreshold, r as PercentageDelta, B as RecentFlowSnapshotGrid, R as RecentFlowSnapshotGridProps, w as ShiftConfigurationRecord, q as ValueDelta, m as VideoGridStatusBadge, u as WorkspaceCropRect, y as isRecentFlowVideoGridMetricMode, z as isWipGatedVideoGridMetricMode, x as normalizeVideoGridMetricMode } from './automation-Jf6Isg6G.mjs';
3
3
  import * as _supabase_supabase_js from '@supabase/supabase-js';
4
4
  import { SupabaseClient as SupabaseClient$1, Session, User, AuthError } from '@supabase/supabase-js';
5
5
  import { LucideProps, Share2, Download } from 'lucide-react';
@@ -5262,9 +5262,11 @@ declare const pickPreferredLineMetricsRow: (supabase: {
5262
5262
  * - Aggregates `current_output` and `ideal_output` sum across
5263
5263
  * **real-SKU rows only**. Dummy rows contribute 0 — they are skipped
5264
5264
  * from that summation set.
5265
- * - Weighted-average fields (avg_efficiency, avg_cycle_time, threshold_pph)
5266
- * use per-row active-minutes as weight; falls back to plain mean when
5267
- * no row has positive weight.
5265
+ * - Weighted-average fields (avg_efficiency, avg_cycle_time) use per-row
5266
+ * active-minutes as weight; falls back to plain mean when no row has
5267
+ * positive weight.
5268
+ * - `threshold_pph` sums across real rows so it matches the Targets
5269
+ * page/API additive output-family PPH rule.
5268
5270
  * - `underperforming_workspaces` is averaged across ACTIVE real rows only
5269
5271
  * (real row with `current_output > 0 || ideal_output > 0`), rounded
5270
5272
  * half-up to the nearest whole number. Names / UUIDs are deduped unions
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { L as LineSignal, V as VideoGridMetricMode, W as WorkspaceMetrics, a as LineInfo, b as WorkspaceDetailedMetrics, S as SkuBreakdownItem, c as SkuSegmentItem, d as LineSkuBreakdownItem, E as EfficiencyLegendUpdate, D as DashboardKPIs, P as PoorPerformingWorkspace, e as WorkspaceVideoStream, K as KpiTrend, M as MonthlyTrendSummary, f as Workspace, g as WorkspaceCameraIpInfo, h as WorkspaceActionUpdate, A as ActionThreshold, i as ShiftConfiguration, j as KpiSignal, k as LineIssueResolutionSummary } from './automation-DYa3jNEf.js';
2
- export { r as CountDelta, C as CurrentWorkspaceSKU, s as DurationDelta, o as KpiSignalMode, n as KpiSignalSource, l as LineDetailedMetrics, m as LineSignalSource, u as LineThreshold, q as PercentageDelta, z as RecentFlowSnapshotGrid, R as RecentFlowSnapshotGridProps, v as ShiftConfigurationRecord, p as ValueDelta, t as WorkspaceCropRect, x as isRecentFlowVideoGridMetricMode, y as isWipGatedVideoGridMetricMode, w as normalizeVideoGridMetricMode } from './automation-DYa3jNEf.js';
1
+ import { L as LineSignal, V as VideoGridMetricMode, W as WorkspaceMetrics, a as LineInfo, b as WorkspaceDetailedMetrics, S as SkuBreakdownItem, c as SkuSegmentItem, d as LineSkuBreakdownItem, E as EfficiencyLegendUpdate, D as DashboardKPIs, P as PoorPerformingWorkspace, e as WorkspaceVideoStream, K as KpiTrend, M as MonthlyTrendSummary, f as Workspace, g as WorkspaceCameraIpInfo, h as WorkspaceActionUpdate, A as ActionThreshold, i as ShiftConfiguration, j as KpiSignal, k as LineIssueResolutionSummary } from './automation-Jf6Isg6G.js';
2
+ export { s as CountDelta, C as CurrentWorkspaceSKU, t as DurationDelta, p as KpiSignalMode, o as KpiSignalSource, l as LineDetailedMetrics, n as LineSignalSource, v as LineThreshold, r as PercentageDelta, B as RecentFlowSnapshotGrid, R as RecentFlowSnapshotGridProps, w as ShiftConfigurationRecord, q as ValueDelta, m as VideoGridStatusBadge, u as WorkspaceCropRect, y as isRecentFlowVideoGridMetricMode, z as isWipGatedVideoGridMetricMode, x as normalizeVideoGridMetricMode } from './automation-Jf6Isg6G.js';
3
3
  import * as _supabase_supabase_js from '@supabase/supabase-js';
4
4
  import { SupabaseClient as SupabaseClient$1, Session, User, AuthError } from '@supabase/supabase-js';
5
5
  import { LucideProps, Share2, Download } from 'lucide-react';
@@ -5262,9 +5262,11 @@ declare const pickPreferredLineMetricsRow: (supabase: {
5262
5262
  * - Aggregates `current_output` and `ideal_output` sum across
5263
5263
  * **real-SKU rows only**. Dummy rows contribute 0 — they are skipped
5264
5264
  * from that summation set.
5265
- * - Weighted-average fields (avg_efficiency, avg_cycle_time, threshold_pph)
5266
- * use per-row active-minutes as weight; falls back to plain mean when
5267
- * no row has positive weight.
5265
+ * - Weighted-average fields (avg_efficiency, avg_cycle_time) use per-row
5266
+ * active-minutes as weight; falls back to plain mean when no row has
5267
+ * positive weight.
5268
+ * - `threshold_pph` sums across real rows so it matches the Targets
5269
+ * page/API additive output-family PPH rule.
5268
5270
  * - `underperforming_workspaces` is averaged across ACTIVE real rows only
5269
5271
  * (real row with `current_output > 0 || ideal_output > 0`), rounded
5270
5272
  * half-up to the nearest whole number. Names / UUIDs are deduped unions
package/dist/index.js CHANGED
@@ -4665,7 +4665,10 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4665
4665
  };
4666
4666
  const avgEfficiency = weighted("avg_efficiency");
4667
4667
  const avgCycleTime = weighted("avg_cycle_time");
4668
- const thresholdPph = weighted("threshold_pph");
4668
+ const thresholdPph = rowsForAggregation.reduce(
4669
+ (acc, row) => acc + (coerceOptionalNumber(row.threshold_pph) ?? 0),
4670
+ 0
4671
+ );
4669
4672
  const underperformingWorkspaces = activeRealRows.length > 0 ? roundHalfUpInt(
4670
4673
  activeRealRows.reduce(
4671
4674
  (acc, row) => acc + safeInt(row.underperforming_workspaces),
@@ -14716,6 +14719,7 @@ var transformMonitorWorkspaceMetrics = ({
14716
14719
  monitoring_mode: item.monitoring_mode ?? void 0,
14717
14720
  idle_time: idleTimeSeconds,
14718
14721
  idle_time_hourly: item.idle_time_hourly ?? null,
14722
+ idle_reason_hourly: item.idle_reason_hourly ?? null,
14719
14723
  shift_start: item.shift_start ?? void 0,
14720
14724
  shift_end: item.shift_end ?? void 0,
14721
14725
  assembly_enabled: item.assembly_enabled ?? false,
@@ -14751,6 +14755,7 @@ var transformMonitorWorkspaceMetrics = ({
14751
14755
  incoming_wip_current: item.incoming_wip_current ?? null,
14752
14756
  incoming_wip_effective_at: item.incoming_wip_effective_at ?? null,
14753
14757
  incoming_wip_buffer_name: item.incoming_wip_buffer_name ?? null,
14758
+ video_grid_badges: Array.isArray(item.video_grid_badges) ? item.video_grid_badges : [],
14754
14759
  show_exclamation: item.show_exclamation ?? void 0
14755
14760
  };
14756
14761
  workspaceMetricsStore.setOverview(metric);
@@ -21269,6 +21274,7 @@ var ICON_CONFIG = {
21269
21274
  "user-x": lucideReact.UserX,
21270
21275
  wrench: lucideReact.Wrench,
21271
21276
  activity: lucideReact.Activity,
21277
+ "clipboard-x": lucideReact.ClipboardX,
21272
21278
  "help-circle": lucideReact.HelpCircle
21273
21279
  };
21274
21280
  var humanizeIdleReasonLabel = (value) => {
@@ -38055,6 +38061,20 @@ var getVideoGridLegendLabel = (workspaces) => {
38055
38061
  }
38056
38062
  return visibleWorkspaces.some(isVideoGridRecentFlowEnabled) ? VIDEO_GRID_LEGEND_LABEL : MAP_GRID_LEGEND_LABEL;
38057
38063
  };
38064
+ var getStatusBadgeSignature = (workspace) => {
38065
+ const badges = workspace.video_grid_badges || [];
38066
+ return badges.map((badge) => [
38067
+ badge.kind,
38068
+ badge.label,
38069
+ badge.display_name,
38070
+ badge.palette_token,
38071
+ badge.icon_token,
38072
+ badge.anchor_minute,
38073
+ badge.reason_minute,
38074
+ badge.shift_elapsed_fraction,
38075
+ badge.title
38076
+ ].join(":")).join("|");
38077
+ };
38058
38078
  function getTrendArrowAndColor(trend) {
38059
38079
  if (trend > 0) {
38060
38080
  return { arrow: "\u2191", color: "text-green-400" };
@@ -38086,6 +38106,7 @@ var VideoCard = React144__namespace.default.memo(({
38086
38106
  }) => {
38087
38107
  const videoRef = React144.useRef(null);
38088
38108
  const canvasRef = React144.useRef(null);
38109
+ const statusBadgeIdPrefix = React144.useId();
38089
38110
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
38090
38111
  const { isStale: isStreamStale } = useHlsStreamWithCropping(videoRef, canvasRef, {
38091
38112
  src: hlsUrl,
@@ -38112,6 +38133,7 @@ var VideoCard = React144__namespace.default.memo(({
38112
38133
  const shouldRenderMetricBadge = hasDisplayMetric;
38113
38134
  const badgeTitle = isHighEfficiencyOverride ? `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%` : hasVideoGridRecentFlow(workspace) ? `Flow ${Math.round(videoGridDisplayValue ?? 0)}%` : isRecentFlowCard ? "Flow unavailable" : `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%`;
38114
38135
  const badgeLabel = `${Math.round(videoGridDisplayValue ?? 0)}%`;
38136
+ const statusBadges = workspace.video_grid_badges || [];
38115
38137
  const efficiencyOverlayClass = videoGridColorState === "green" ? "bg-[#00D654]/25" : videoGridColorState === "blue" ? "bg-[#0EA5E9]/30" : videoGridColorState === "yellow" ? "bg-[#FFD700]/30" : videoGridColorState === "red" ? "bg-[#FF2D0A]/30" : "bg-transparent";
38116
38138
  const efficiencyBarClass = videoGridColorState === "green" ? "bg-[#00AB45]" : videoGridColorState === "blue" ? "bg-[#0EA5E9]" : videoGridColorState === "yellow" ? "bg-[#FFB020]" : videoGridColorState === "red" ? "bg-[#E34329]" : "bg-gray-500/70";
38117
38139
  const efficiencyStatus = videoGridColorState === "green" ? "High" : videoGridColorState === "blue" ? "Best" : videoGridColorState === "yellow" ? "Medium" : videoGridColorState === "red" ? "Low" : "Neutral";
@@ -38190,6 +38212,61 @@ var VideoCard = React144__namespace.default.memo(({
38190
38212
  children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${compact ? "text-[10px]" : "text-xs"} font-semibold`, children: badgeLabel })
38191
38213
  }
38192
38214
  ) }),
38215
+ statusBadges.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
38216
+ "div",
38217
+ {
38218
+ "data-testid": "video-card-status-badges",
38219
+ className: `absolute ${compact ? "top-1.5 left-1.5 gap-1" : "top-2 left-2 gap-1.5"} z-30 flex items-center`,
38220
+ children: statusBadges.map((badge, index) => {
38221
+ const presentation = getIdleReasonPresentation({
38222
+ label: badge.label,
38223
+ displayName: badge.display_name,
38224
+ paletteToken: badge.palette_token,
38225
+ iconToken: badge.icon_token,
38226
+ isKnown: badge.is_known
38227
+ });
38228
+ const Icon2 = presentation.Icon;
38229
+ const tooltipText = presentation.displayName;
38230
+ const tooltipId = `video-card-status-tooltip-${statusBadgeIdPrefix}-${badge.kind}-${index}`;
38231
+ return /* @__PURE__ */ jsxRuntime.jsxs(
38232
+ "div",
38233
+ {
38234
+ "data-testid": `video-card-status-badge-${badge.kind}`,
38235
+ "aria-label": tooltipText,
38236
+ "aria-describedby": tooltipId,
38237
+ className: `group relative inline-flex shrink-0 items-center justify-center rounded-full bg-slate-950/70 border-2 shadow-[0_3px_10px_rgba(0,0,0,0.34),inset_0_0_0_1px_rgba(255,255,255,0.18)] ${compact ? "h-10 w-10" : "h-11 w-11"}`,
38238
+ style: {
38239
+ borderColor: presentation.hex
38240
+ },
38241
+ children: [
38242
+ /* @__PURE__ */ jsxRuntime.jsx(
38243
+ Icon2,
38244
+ {
38245
+ "aria-hidden": "true",
38246
+ className: `${compact ? "h-5 w-5" : "h-6 w-6"} text-white`,
38247
+ strokeWidth: 2.4
38248
+ }
38249
+ ),
38250
+ /* @__PURE__ */ jsxRuntime.jsxs(
38251
+ "span",
38252
+ {
38253
+ id: tooltipId,
38254
+ role: "tooltip",
38255
+ "data-testid": `video-card-status-tooltip-${badge.kind}`,
38256
+ className: "pointer-events-none absolute left-0 top-full mt-2 whitespace-nowrap rounded-md border border-white/10 bg-slate-950/95 px-2 py-1 text-[11px] font-semibold leading-none text-white opacity-0 shadow-[0_6px_18px_rgba(0,0,0,0.34)] transition-opacity duration-150 group-hover:opacity-100",
38257
+ children: [
38258
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 left-3 h-2 w-2 rotate-45 border-l border-t border-white/10 bg-slate-950/95" }),
38259
+ tooltipText
38260
+ ]
38261
+ }
38262
+ )
38263
+ ]
38264
+ },
38265
+ `${badge.kind}-${badge.label || index}-${badge.reason_minute ?? index}`
38266
+ );
38267
+ })
38268
+ }
38269
+ ),
38193
38270
  /* @__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(
38194
38271
  "div",
38195
38272
  {
@@ -38226,7 +38303,7 @@ var VideoCard = React144__namespace.default.memo(({
38226
38303
  }
38227
38304
  );
38228
38305
  }, (prevProps, nextProps) => {
38229
- if (prevProps.workspace.efficiency !== nextProps.workspace.efficiency || prevProps.workspace.assembly_enabled !== nextProps.workspace.assembly_enabled || prevProps.workspace.video_grid_metric_mode !== nextProps.workspace.video_grid_metric_mode || prevProps.workspace.recent_flow_percent !== nextProps.workspace.recent_flow_percent || prevProps.workspace.recent_flow_effective_end_at !== nextProps.workspace.recent_flow_effective_end_at || prevProps.workspace.recent_flow_forced_zero_after_shift !== nextProps.workspace.recent_flow_forced_zero_after_shift || prevProps.workspace.scheduled_break_active !== nextProps.workspace.scheduled_break_active || prevProps.workspace.incoming_wip_current !== nextProps.workspace.incoming_wip_current || prevProps.workspace.incoming_wip_buffer_name !== nextProps.workspace.incoming_wip_buffer_name || prevProps.workspace.trend !== nextProps.workspace.trend || prevProps.workspace.performance_score !== nextProps.workspace.performance_score || prevProps.workspace.pph !== nextProps.workspace.pph) {
38306
+ if (prevProps.workspace.efficiency !== nextProps.workspace.efficiency || prevProps.workspace.assembly_enabled !== nextProps.workspace.assembly_enabled || prevProps.workspace.video_grid_metric_mode !== nextProps.workspace.video_grid_metric_mode || prevProps.workspace.recent_flow_percent !== nextProps.workspace.recent_flow_percent || prevProps.workspace.recent_flow_effective_end_at !== nextProps.workspace.recent_flow_effective_end_at || prevProps.workspace.recent_flow_forced_zero_after_shift !== nextProps.workspace.recent_flow_forced_zero_after_shift || prevProps.workspace.scheduled_break_active !== nextProps.workspace.scheduled_break_active || prevProps.workspace.incoming_wip_current !== nextProps.workspace.incoming_wip_current || prevProps.workspace.incoming_wip_buffer_name !== nextProps.workspace.incoming_wip_buffer_name || getStatusBadgeSignature(prevProps.workspace) !== getStatusBadgeSignature(nextProps.workspace) || prevProps.workspace.trend !== nextProps.workspace.trend || prevProps.workspace.performance_score !== nextProps.workspace.performance_score || prevProps.workspace.pph !== nextProps.workspace.pph) {
38230
38307
  return false;
38231
38308
  }
38232
38309
  if (prevProps.workspace.workspace_uuid !== nextProps.workspace.workspace_uuid || prevProps.workspace.workspace_name !== nextProps.workspace.workspace_name || prevProps.workspace.line_id !== nextProps.workspace.line_id) {
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { format, addDays, subMonths, endOfMonth, startOfMonth, endOfDay, eachDayOfInterval, getDay, isSameDay, isWithinInterval, startOfDay, parseISO, subDays, differenceInMinutes, addMinutes, addMonths, isValid, formatDistanceToNow, isToday, isFuture, isBefore } from 'date-fns';
2
2
  import { formatInTimeZone, fromZonedTime, toZonedTime } from 'date-fns-tz';
3
3
  import * as React144 from 'react';
4
- import React144__default, { createContext, useRef, useCallback, useState, useMemo, useEffect, forwardRef, useImperativeHandle, useLayoutEffect, memo as memo$1, useContext, useSyncExternalStore, useId, Children, isValidElement, useInsertionEffect, startTransition, Fragment as Fragment$1, createElement, Component } from 'react';
4
+ import React144__default, { createContext, useRef, useId, useCallback, useState, useMemo, useEffect, forwardRef, useImperativeHandle, useLayoutEffect, memo as memo$1, useContext, useSyncExternalStore, Children, isValidElement, useInsertionEffect, startTransition, Fragment as Fragment$1, createElement, Component } from 'react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import { useRouter } from 'next/router';
7
7
  import { toast } from 'sonner';
@@ -10,7 +10,7 @@ import { EventEmitter } from 'events';
10
10
  import { createClient, REALTIME_SUBSCRIBE_STATES } from '@supabase/supabase-js';
11
11
  import Hls, { Events, ErrorTypes } from 'hls.js';
12
12
  import useSWR from 'swr';
13
- import { Camera, AlertTriangle, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, Filter, X, Coffee, Plus, ArrowUp, ArrowDown, ArrowRight, CheckCircle2, ArrowLeft, Clock, Calendar, Save, AlertCircle, Loader2, Minus, ChevronLeft, ChevronRight, TrendingUp, Sparkles, Pause, Play, XCircle, HelpCircle, Activity, Wrench, UserX, Package, RefreshCw, Palette, TrendingDown, FolderOpen, Folder, ArrowDownWideNarrow, Tag, Sliders, Layers, Search, Edit2, CheckCircle, User, Users, Shield, Building2, Mail, Lock, Info, Share2, Trophy, Target, Download, Video, Copy, Sun, Moon, MousePointer, UserPlus, UserCog, Trash2, Eye, MoreVertical, BarChart3, Pencil, UserCheck, LogOut, Film, MessageSquare, Menu, Send, Settings, LifeBuoy, EyeOff, Zap, Flame, Crown, Medal } from 'lucide-react';
13
+ import { Camera, AlertTriangle, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, Filter, X, Coffee, Plus, ArrowUp, ArrowDown, ArrowRight, HelpCircle, ClipboardX, Activity, Wrench, UserX, Clock, Package, RefreshCw, CheckCircle2, ArrowLeft, Calendar, Save, AlertCircle, Loader2, Minus, ChevronLeft, ChevronRight, TrendingUp, Sparkles, Pause, Play, XCircle, Palette, TrendingDown, FolderOpen, Folder, ArrowDownWideNarrow, Tag, Sliders, Layers, Search, Edit2, CheckCircle, User, Users, Shield, Building2, Mail, Lock, Info, Share2, Trophy, Target, Download, Video, Copy, Sun, Moon, MousePointer, UserPlus, UserCog, Trash2, Eye, MoreVertical, BarChart3, Pencil, UserCheck, LogOut, Film, MessageSquare, Menu, Send, Settings, LifeBuoy, EyeOff, Zap, Flame, Crown, Medal } from 'lucide-react';
14
14
  import { memo, noop, warning, invariant, progress, secondsToMilliseconds, millisecondsToSeconds } from 'motion-utils';
15
15
  import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, ReferenceLine, Tooltip, Legend, Bar, LabelList, ResponsiveContainer, LineChart as LineChart$1, Line, Customized, Cell, PieChart, Pie, ComposedChart, Area, ScatterChart, Scatter } from 'recharts';
16
16
  import { Slot } from '@radix-ui/react-slot';
@@ -4636,7 +4636,10 @@ var combineLineMetricsRows = (rows, dummySkuId) => {
4636
4636
  };
4637
4637
  const avgEfficiency = weighted("avg_efficiency");
4638
4638
  const avgCycleTime = weighted("avg_cycle_time");
4639
- const thresholdPph = weighted("threshold_pph");
4639
+ const thresholdPph = rowsForAggregation.reduce(
4640
+ (acc, row) => acc + (coerceOptionalNumber(row.threshold_pph) ?? 0),
4641
+ 0
4642
+ );
4640
4643
  const underperformingWorkspaces = activeRealRows.length > 0 ? roundHalfUpInt(
4641
4644
  activeRealRows.reduce(
4642
4645
  (acc, row) => acc + safeInt(row.underperforming_workspaces),
@@ -14687,6 +14690,7 @@ var transformMonitorWorkspaceMetrics = ({
14687
14690
  monitoring_mode: item.monitoring_mode ?? void 0,
14688
14691
  idle_time: idleTimeSeconds,
14689
14692
  idle_time_hourly: item.idle_time_hourly ?? null,
14693
+ idle_reason_hourly: item.idle_reason_hourly ?? null,
14690
14694
  shift_start: item.shift_start ?? void 0,
14691
14695
  shift_end: item.shift_end ?? void 0,
14692
14696
  assembly_enabled: item.assembly_enabled ?? false,
@@ -14722,6 +14726,7 @@ var transformMonitorWorkspaceMetrics = ({
14722
14726
  incoming_wip_current: item.incoming_wip_current ?? null,
14723
14727
  incoming_wip_effective_at: item.incoming_wip_effective_at ?? null,
14724
14728
  incoming_wip_buffer_name: item.incoming_wip_buffer_name ?? null,
14729
+ video_grid_badges: Array.isArray(item.video_grid_badges) ? item.video_grid_badges : [],
14725
14730
  show_exclamation: item.show_exclamation ?? void 0
14726
14731
  };
14727
14732
  workspaceMetricsStore.setOverview(metric);
@@ -21240,6 +21245,7 @@ var ICON_CONFIG = {
21240
21245
  "user-x": UserX,
21241
21246
  wrench: Wrench,
21242
21247
  activity: Activity,
21248
+ "clipboard-x": ClipboardX,
21243
21249
  "help-circle": HelpCircle
21244
21250
  };
21245
21251
  var humanizeIdleReasonLabel = (value) => {
@@ -38026,6 +38032,20 @@ var getVideoGridLegendLabel = (workspaces) => {
38026
38032
  }
38027
38033
  return visibleWorkspaces.some(isVideoGridRecentFlowEnabled) ? VIDEO_GRID_LEGEND_LABEL : MAP_GRID_LEGEND_LABEL;
38028
38034
  };
38035
+ var getStatusBadgeSignature = (workspace) => {
38036
+ const badges = workspace.video_grid_badges || [];
38037
+ return badges.map((badge) => [
38038
+ badge.kind,
38039
+ badge.label,
38040
+ badge.display_name,
38041
+ badge.palette_token,
38042
+ badge.icon_token,
38043
+ badge.anchor_minute,
38044
+ badge.reason_minute,
38045
+ badge.shift_elapsed_fraction,
38046
+ badge.title
38047
+ ].join(":")).join("|");
38048
+ };
38029
38049
  function getTrendArrowAndColor(trend) {
38030
38050
  if (trend > 0) {
38031
38051
  return { arrow: "\u2191", color: "text-green-400" };
@@ -38057,6 +38077,7 @@ var VideoCard = React144__default.memo(({
38057
38077
  }) => {
38058
38078
  const videoRef = useRef(null);
38059
38079
  const canvasRef = useRef(null);
38080
+ const statusBadgeIdPrefix = useId();
38060
38081
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
38061
38082
  const { isStale: isStreamStale } = useHlsStreamWithCropping(videoRef, canvasRef, {
38062
38083
  src: hlsUrl,
@@ -38083,6 +38104,7 @@ var VideoCard = React144__default.memo(({
38083
38104
  const shouldRenderMetricBadge = hasDisplayMetric;
38084
38105
  const badgeTitle = isHighEfficiencyOverride ? `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%` : hasVideoGridRecentFlow(workspace) ? `Flow ${Math.round(videoGridDisplayValue ?? 0)}%` : isRecentFlowCard ? "Flow unavailable" : `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%`;
38085
38106
  const badgeLabel = `${Math.round(videoGridDisplayValue ?? 0)}%`;
38107
+ const statusBadges = workspace.video_grid_badges || [];
38086
38108
  const efficiencyOverlayClass = videoGridColorState === "green" ? "bg-[#00D654]/25" : videoGridColorState === "blue" ? "bg-[#0EA5E9]/30" : videoGridColorState === "yellow" ? "bg-[#FFD700]/30" : videoGridColorState === "red" ? "bg-[#FF2D0A]/30" : "bg-transparent";
38087
38109
  const efficiencyBarClass = videoGridColorState === "green" ? "bg-[#00AB45]" : videoGridColorState === "blue" ? "bg-[#0EA5E9]" : videoGridColorState === "yellow" ? "bg-[#FFB020]" : videoGridColorState === "red" ? "bg-[#E34329]" : "bg-gray-500/70";
38088
38110
  const efficiencyStatus = videoGridColorState === "green" ? "High" : videoGridColorState === "blue" ? "Best" : videoGridColorState === "yellow" ? "Medium" : videoGridColorState === "red" ? "Low" : "Neutral";
@@ -38161,6 +38183,61 @@ var VideoCard = React144__default.memo(({
38161
38183
  children: /* @__PURE__ */ jsx("span", { className: `${compact ? "text-[10px]" : "text-xs"} font-semibold`, children: badgeLabel })
38162
38184
  }
38163
38185
  ) }),
38186
+ statusBadges.length > 0 && /* @__PURE__ */ jsx(
38187
+ "div",
38188
+ {
38189
+ "data-testid": "video-card-status-badges",
38190
+ className: `absolute ${compact ? "top-1.5 left-1.5 gap-1" : "top-2 left-2 gap-1.5"} z-30 flex items-center`,
38191
+ children: statusBadges.map((badge, index) => {
38192
+ const presentation = getIdleReasonPresentation({
38193
+ label: badge.label,
38194
+ displayName: badge.display_name,
38195
+ paletteToken: badge.palette_token,
38196
+ iconToken: badge.icon_token,
38197
+ isKnown: badge.is_known
38198
+ });
38199
+ const Icon2 = presentation.Icon;
38200
+ const tooltipText = presentation.displayName;
38201
+ const tooltipId = `video-card-status-tooltip-${statusBadgeIdPrefix}-${badge.kind}-${index}`;
38202
+ return /* @__PURE__ */ jsxs(
38203
+ "div",
38204
+ {
38205
+ "data-testid": `video-card-status-badge-${badge.kind}`,
38206
+ "aria-label": tooltipText,
38207
+ "aria-describedby": tooltipId,
38208
+ className: `group relative inline-flex shrink-0 items-center justify-center rounded-full bg-slate-950/70 border-2 shadow-[0_3px_10px_rgba(0,0,0,0.34),inset_0_0_0_1px_rgba(255,255,255,0.18)] ${compact ? "h-10 w-10" : "h-11 w-11"}`,
38209
+ style: {
38210
+ borderColor: presentation.hex
38211
+ },
38212
+ children: [
38213
+ /* @__PURE__ */ jsx(
38214
+ Icon2,
38215
+ {
38216
+ "aria-hidden": "true",
38217
+ className: `${compact ? "h-5 w-5" : "h-6 w-6"} text-white`,
38218
+ strokeWidth: 2.4
38219
+ }
38220
+ ),
38221
+ /* @__PURE__ */ jsxs(
38222
+ "span",
38223
+ {
38224
+ id: tooltipId,
38225
+ role: "tooltip",
38226
+ "data-testid": `video-card-status-tooltip-${badge.kind}`,
38227
+ className: "pointer-events-none absolute left-0 top-full mt-2 whitespace-nowrap rounded-md border border-white/10 bg-slate-950/95 px-2 py-1 text-[11px] font-semibold leading-none text-white opacity-0 shadow-[0_6px_18px_rgba(0,0,0,0.34)] transition-opacity duration-150 group-hover:opacity-100",
38228
+ children: [
38229
+ /* @__PURE__ */ jsx("span", { className: "absolute -top-1 left-3 h-2 w-2 rotate-45 border-l border-t border-white/10 bg-slate-950/95" }),
38230
+ tooltipText
38231
+ ]
38232
+ }
38233
+ )
38234
+ ]
38235
+ },
38236
+ `${badge.kind}-${badge.label || index}-${badge.reason_minute ?? index}`
38237
+ );
38238
+ })
38239
+ }
38240
+ ),
38164
38241
  /* @__PURE__ */ jsx("div", { className: `absolute bottom-0 left-0 right-0 ${compact ? "h-0.5" : "h-1"} bg-black/50 z-30`, children: /* @__PURE__ */ jsx(
38165
38242
  "div",
38166
38243
  {
@@ -38197,7 +38274,7 @@ var VideoCard = React144__default.memo(({
38197
38274
  }
38198
38275
  );
38199
38276
  }, (prevProps, nextProps) => {
38200
- if (prevProps.workspace.efficiency !== nextProps.workspace.efficiency || prevProps.workspace.assembly_enabled !== nextProps.workspace.assembly_enabled || prevProps.workspace.video_grid_metric_mode !== nextProps.workspace.video_grid_metric_mode || prevProps.workspace.recent_flow_percent !== nextProps.workspace.recent_flow_percent || prevProps.workspace.recent_flow_effective_end_at !== nextProps.workspace.recent_flow_effective_end_at || prevProps.workspace.recent_flow_forced_zero_after_shift !== nextProps.workspace.recent_flow_forced_zero_after_shift || prevProps.workspace.scheduled_break_active !== nextProps.workspace.scheduled_break_active || prevProps.workspace.incoming_wip_current !== nextProps.workspace.incoming_wip_current || prevProps.workspace.incoming_wip_buffer_name !== nextProps.workspace.incoming_wip_buffer_name || prevProps.workspace.trend !== nextProps.workspace.trend || prevProps.workspace.performance_score !== nextProps.workspace.performance_score || prevProps.workspace.pph !== nextProps.workspace.pph) {
38277
+ if (prevProps.workspace.efficiency !== nextProps.workspace.efficiency || prevProps.workspace.assembly_enabled !== nextProps.workspace.assembly_enabled || prevProps.workspace.video_grid_metric_mode !== nextProps.workspace.video_grid_metric_mode || prevProps.workspace.recent_flow_percent !== nextProps.workspace.recent_flow_percent || prevProps.workspace.recent_flow_effective_end_at !== nextProps.workspace.recent_flow_effective_end_at || prevProps.workspace.recent_flow_forced_zero_after_shift !== nextProps.workspace.recent_flow_forced_zero_after_shift || prevProps.workspace.scheduled_break_active !== nextProps.workspace.scheduled_break_active || prevProps.workspace.incoming_wip_current !== nextProps.workspace.incoming_wip_current || prevProps.workspace.incoming_wip_buffer_name !== nextProps.workspace.incoming_wip_buffer_name || getStatusBadgeSignature(prevProps.workspace) !== getStatusBadgeSignature(nextProps.workspace) || prevProps.workspace.trend !== nextProps.workspace.trend || prevProps.workspace.performance_score !== nextProps.workspace.performance_score || prevProps.workspace.pph !== nextProps.workspace.pph) {
38201
38278
  return false;
38202
38279
  }
38203
38280
  if (prevProps.workspace.workspace_uuid !== nextProps.workspace.workspace_uuid || prevProps.workspace.workspace_name !== nextProps.workspace.workspace_name || prevProps.workspace.line_id !== nextProps.workspace.line_id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.12.29",
3
+ "version": "6.12.30",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",