@optifye/dashboard-core 6.12.28 → 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.
@@ -270,6 +287,9 @@ interface LineSignal {
270
287
  weight?: number | null;
271
288
  mode?: KpiSignalMode;
272
289
  reason?: string | null;
290
+ aggregate_eligible?: boolean | null;
291
+ aggregate_reason?: string | null;
292
+ aggregate_threshold_percent?: number | null;
273
293
  effective_end_at?: string | null;
274
294
  computed_at?: string | null;
275
295
  }
@@ -279,6 +299,9 @@ interface KpiSignal {
279
299
  weight?: number | null;
280
300
  mode?: KpiSignalMode;
281
301
  reason?: string | null;
302
+ aggregateEligible?: boolean | null;
303
+ aggregateReason?: string | null;
304
+ aggregateThresholdPercent?: number | null;
282
305
  effectiveEndAt?: string | null;
283
306
  computedAt?: string | null;
284
307
  }
@@ -471,4 +494,4 @@ interface RecentFlowSnapshotGridProps {
471
494
  }
472
495
  declare const RecentFlowSnapshotGrid: React__default.FC<RecentFlowSnapshotGridProps>;
473
496
 
474
- 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.
@@ -270,6 +287,9 @@ interface LineSignal {
270
287
  weight?: number | null;
271
288
  mode?: KpiSignalMode;
272
289
  reason?: string | null;
290
+ aggregate_eligible?: boolean | null;
291
+ aggregate_reason?: string | null;
292
+ aggregate_threshold_percent?: number | null;
273
293
  effective_end_at?: string | null;
274
294
  computed_at?: string | null;
275
295
  }
@@ -279,6 +299,9 @@ interface KpiSignal {
279
299
  weight?: number | null;
280
300
  mode?: KpiSignalMode;
281
301
  reason?: string | null;
302
+ aggregateEligible?: boolean | null;
303
+ aggregateReason?: string | null;
304
+ aggregateThresholdPercent?: number | null;
282
305
  effectiveEndAt?: string | null;
283
306
  computedAt?: string | null;
284
307
  }
@@ -471,4 +494,4 @@ interface RecentFlowSnapshotGridProps {
471
494
  }
472
495
  declare const RecentFlowSnapshotGrid: React__default.FC<RecentFlowSnapshotGridProps>;
473
496
 
474
- 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-CQqrAD4z.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-CQqrAD4z.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-CQqrAD4z.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-CQqrAD4z.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-CQqrAD4z.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-CQqrAD4z.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) => {
@@ -22096,7 +22102,7 @@ var normalizeLineSignalSource = (source) => source === "recent_flow" ? "recent_f
22096
22102
  var normalizeLineSignal = (lineSignal, fallbackEfficiency, fallbackWeight) => {
22097
22103
  if (lineSignal && typeof lineSignal === "object") {
22098
22104
  const raw = lineSignal;
22099
- return {
22105
+ const normalized = {
22100
22106
  source: normalizeLineSignalSource(raw.source),
22101
22107
  percent: toOptionalNumber(raw.percent),
22102
22108
  weight: toOptionalNumber(raw.weight),
@@ -22105,6 +22111,21 @@ var normalizeLineSignal = (lineSignal, fallbackEfficiency, fallbackWeight) => {
22105
22111
  effectiveEndAt: raw.effective_end_at ?? raw.effectiveEndAt ?? null,
22106
22112
  computedAt: raw.computed_at ?? raw.computedAt ?? null
22107
22113
  };
22114
+ const aggregateEligible = typeof raw.aggregate_eligible === "boolean" ? raw.aggregate_eligible : typeof raw.aggregateEligible === "boolean" ? raw.aggregateEligible : null;
22115
+ if (aggregateEligible !== null) {
22116
+ normalized.aggregateEligible = aggregateEligible;
22117
+ }
22118
+ const aggregateReason = typeof raw.aggregate_reason === "string" ? raw.aggregate_reason : typeof raw.aggregateReason === "string" ? raw.aggregateReason : null;
22119
+ if (aggregateReason !== null) {
22120
+ normalized.aggregateReason = aggregateReason;
22121
+ }
22122
+ const aggregateThresholdPercent = toOptionalNumber(
22123
+ raw.aggregate_threshold_percent ?? raw.aggregateThresholdPercent
22124
+ );
22125
+ if (aggregateThresholdPercent !== null) {
22126
+ normalized.aggregateThresholdPercent = aggregateThresholdPercent;
22127
+ }
22128
+ return normalized;
22108
22129
  }
22109
22130
  const percent2 = toOptionalNumber(fallbackEfficiency);
22110
22131
  if (percent2 === null) return null;
@@ -22132,7 +22153,7 @@ var getKpiSignalLabel = (signal, legend = DEFAULT_EFFICIENCY_LEGEND) => {
22132
22153
  };
22133
22154
  var aggregateLineSignals = (signals) => {
22134
22155
  const numericSignals = signals.filter(
22135
- (signal) => signal !== null && signal !== void 0 && toOptionalNumber(signal.percent) !== null
22156
+ (signal) => signal !== null && signal !== void 0 && signal.aggregateEligible !== false && toOptionalNumber(signal.percent) !== null
22136
22157
  );
22137
22158
  if (numericSignals.length === 0) return null;
22138
22159
  const percent2 = numericSignals.reduce(
@@ -38040,6 +38061,20 @@ var getVideoGridLegendLabel = (workspaces) => {
38040
38061
  }
38041
38062
  return visibleWorkspaces.some(isVideoGridRecentFlowEnabled) ? VIDEO_GRID_LEGEND_LABEL : MAP_GRID_LEGEND_LABEL;
38042
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
+ };
38043
38078
  function getTrendArrowAndColor(trend) {
38044
38079
  if (trend > 0) {
38045
38080
  return { arrow: "\u2191", color: "text-green-400" };
@@ -38071,6 +38106,7 @@ var VideoCard = React144__namespace.default.memo(({
38071
38106
  }) => {
38072
38107
  const videoRef = React144.useRef(null);
38073
38108
  const canvasRef = React144.useRef(null);
38109
+ const statusBadgeIdPrefix = React144.useId();
38074
38110
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
38075
38111
  const { isStale: isStreamStale } = useHlsStreamWithCropping(videoRef, canvasRef, {
38076
38112
  src: hlsUrl,
@@ -38097,6 +38133,7 @@ var VideoCard = React144__namespace.default.memo(({
38097
38133
  const shouldRenderMetricBadge = hasDisplayMetric;
38098
38134
  const badgeTitle = isHighEfficiencyOverride ? `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%` : hasVideoGridRecentFlow(workspace) ? `Flow ${Math.round(videoGridDisplayValue ?? 0)}%` : isRecentFlowCard ? "Flow unavailable" : `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%`;
38099
38135
  const badgeLabel = `${Math.round(videoGridDisplayValue ?? 0)}%`;
38136
+ const statusBadges = workspace.video_grid_badges || [];
38100
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";
38101
38138
  const efficiencyBarClass = videoGridColorState === "green" ? "bg-[#00AB45]" : videoGridColorState === "blue" ? "bg-[#0EA5E9]" : videoGridColorState === "yellow" ? "bg-[#FFB020]" : videoGridColorState === "red" ? "bg-[#E34329]" : "bg-gray-500/70";
38102
38139
  const efficiencyStatus = videoGridColorState === "green" ? "High" : videoGridColorState === "blue" ? "Best" : videoGridColorState === "yellow" ? "Medium" : videoGridColorState === "red" ? "Low" : "Neutral";
@@ -38175,6 +38212,61 @@ var VideoCard = React144__namespace.default.memo(({
38175
38212
  children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${compact ? "text-[10px]" : "text-xs"} font-semibold`, children: badgeLabel })
38176
38213
  }
38177
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
+ ),
38178
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(
38179
38271
  "div",
38180
38272
  {
@@ -38211,7 +38303,7 @@ var VideoCard = React144__namespace.default.memo(({
38211
38303
  }
38212
38304
  );
38213
38305
  }, (prevProps, nextProps) => {
38214
- 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) {
38215
38307
  return false;
38216
38308
  }
38217
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) {
@@ -64837,7 +64929,6 @@ var EMPTY_LINE_IDS = [];
64837
64929
  var EMPTY_WORKSPACES = [];
64838
64930
  var ALL_GREEN_CELEBRATION_DURATION_MS = 6e3;
64839
64931
  var ALL_GREEN_MILESTONE_DURATION_MS = 6e3;
64840
- var ALL_GREEN_STREAK_LOCAL_SECOND_CAP = 59;
64841
64932
  var formatAllGreenCelebrationTimer = (elapsedSeconds) => {
64842
64933
  const safeElapsedSeconds = Math.max(1, Math.floor(elapsedSeconds));
64843
64934
  const minutes = Math.floor(safeElapsedSeconds / 60);
@@ -64845,10 +64936,7 @@ var formatAllGreenCelebrationTimer = (elapsedSeconds) => {
64845
64936
  return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}s`;
64846
64937
  };
64847
64938
  var getAllGreenBackendVisibleSeconds = (confirmedSeconds, tickOriginAtMs, nowMs2) => {
64848
- const localSecondOffset = Math.min(
64849
- ALL_GREEN_STREAK_LOCAL_SECOND_CAP,
64850
- Math.max(0, Math.floor((nowMs2 - tickOriginAtMs) / 1e3))
64851
- );
64939
+ const localSecondOffset = Math.max(0, Math.floor((nowMs2 - tickOriginAtMs) / 1e3));
64852
64940
  return confirmedSeconds + localSecondOffset;
64853
64941
  };
64854
64942
  var LoadingPageCmp = LoadingPage_default;
@@ -65308,38 +65396,25 @@ function HomeView({
65308
65396
  allGreenStreakDisplay.tickOriginAtMs,
65309
65397
  currentAllGreenStreakNowMs
65310
65398
  );
65311
- const backendMaxVisibleSeconds = confirmedSeconds + ALL_GREEN_STREAK_LOCAL_SECOND_CAP;
65312
65399
  const currentBaseline = allGreenStreakTimerBaselineRef.current;
65313
65400
  if (!currentBaseline || currentBaseline.identity !== allGreenMilestoneIdentity) {
65314
65401
  allGreenStreakTimerBaselineRef.current = {
65315
65402
  identity: allGreenMilestoneIdentity,
65316
65403
  baseSeconds: backendVisibleSeconds,
65317
- baseReceivedAtMs: currentAllGreenStreakNowMs,
65318
- maxVisibleSeconds: backendMaxVisibleSeconds
65404
+ baseReceivedAtMs: currentAllGreenStreakNowMs
65319
65405
  };
65320
65406
  } else {
65321
- currentBaseline.maxVisibleSeconds = Math.max(
65322
- currentBaseline.maxVisibleSeconds,
65323
- backendMaxVisibleSeconds
65324
- );
65325
- const visibleSeconds = Math.min(
65326
- currentBaseline.maxVisibleSeconds,
65327
- currentBaseline.baseSeconds + Math.max(0, Math.floor((currentAllGreenStreakNowMs - currentBaseline.baseReceivedAtMs) / 1e3))
65328
- );
65407
+ const visibleSeconds = currentBaseline.baseSeconds + Math.max(0, Math.floor((currentAllGreenStreakNowMs - currentBaseline.baseReceivedAtMs) / 1e3));
65329
65408
  if (backendVisibleSeconds > visibleSeconds) {
65330
65409
  allGreenStreakTimerBaselineRef.current = {
65331
65410
  identity: allGreenMilestoneIdentity,
65332
65411
  baseSeconds: backendVisibleSeconds,
65333
- baseReceivedAtMs: currentAllGreenStreakNowMs,
65334
- maxVisibleSeconds: currentBaseline.maxVisibleSeconds
65412
+ baseReceivedAtMs: currentAllGreenStreakNowMs
65335
65413
  };
65336
65414
  }
65337
65415
  }
65338
65416
  const baseline = allGreenStreakTimerBaselineRef.current;
65339
- const elapsedSeconds = baseline ? Math.min(
65340
- baseline.maxVisibleSeconds,
65341
- baseline.baseSeconds + Math.max(0, Math.floor((currentAllGreenStreakNowMs - baseline.baseReceivedAtMs) / 1e3))
65342
- ) : backendVisibleSeconds;
65417
+ const elapsedSeconds = baseline ? baseline.baseSeconds + Math.max(0, Math.floor((currentAllGreenStreakNowMs - baseline.baseReceivedAtMs) / 1e3)) : backendVisibleSeconds;
65343
65418
  return {
65344
65419
  ...allGreenStreakDisplay,
65345
65420
  elapsedSeconds,
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) => {
@@ -22067,7 +22073,7 @@ var normalizeLineSignalSource = (source) => source === "recent_flow" ? "recent_f
22067
22073
  var normalizeLineSignal = (lineSignal, fallbackEfficiency, fallbackWeight) => {
22068
22074
  if (lineSignal && typeof lineSignal === "object") {
22069
22075
  const raw = lineSignal;
22070
- return {
22076
+ const normalized = {
22071
22077
  source: normalizeLineSignalSource(raw.source),
22072
22078
  percent: toOptionalNumber(raw.percent),
22073
22079
  weight: toOptionalNumber(raw.weight),
@@ -22076,6 +22082,21 @@ var normalizeLineSignal = (lineSignal, fallbackEfficiency, fallbackWeight) => {
22076
22082
  effectiveEndAt: raw.effective_end_at ?? raw.effectiveEndAt ?? null,
22077
22083
  computedAt: raw.computed_at ?? raw.computedAt ?? null
22078
22084
  };
22085
+ const aggregateEligible = typeof raw.aggregate_eligible === "boolean" ? raw.aggregate_eligible : typeof raw.aggregateEligible === "boolean" ? raw.aggregateEligible : null;
22086
+ if (aggregateEligible !== null) {
22087
+ normalized.aggregateEligible = aggregateEligible;
22088
+ }
22089
+ const aggregateReason = typeof raw.aggregate_reason === "string" ? raw.aggregate_reason : typeof raw.aggregateReason === "string" ? raw.aggregateReason : null;
22090
+ if (aggregateReason !== null) {
22091
+ normalized.aggregateReason = aggregateReason;
22092
+ }
22093
+ const aggregateThresholdPercent = toOptionalNumber(
22094
+ raw.aggregate_threshold_percent ?? raw.aggregateThresholdPercent
22095
+ );
22096
+ if (aggregateThresholdPercent !== null) {
22097
+ normalized.aggregateThresholdPercent = aggregateThresholdPercent;
22098
+ }
22099
+ return normalized;
22079
22100
  }
22080
22101
  const percent2 = toOptionalNumber(fallbackEfficiency);
22081
22102
  if (percent2 === null) return null;
@@ -22103,7 +22124,7 @@ var getKpiSignalLabel = (signal, legend = DEFAULT_EFFICIENCY_LEGEND) => {
22103
22124
  };
22104
22125
  var aggregateLineSignals = (signals) => {
22105
22126
  const numericSignals = signals.filter(
22106
- (signal) => signal !== null && signal !== void 0 && toOptionalNumber(signal.percent) !== null
22127
+ (signal) => signal !== null && signal !== void 0 && signal.aggregateEligible !== false && toOptionalNumber(signal.percent) !== null
22107
22128
  );
22108
22129
  if (numericSignals.length === 0) return null;
22109
22130
  const percent2 = numericSignals.reduce(
@@ -38011,6 +38032,20 @@ var getVideoGridLegendLabel = (workspaces) => {
38011
38032
  }
38012
38033
  return visibleWorkspaces.some(isVideoGridRecentFlowEnabled) ? VIDEO_GRID_LEGEND_LABEL : MAP_GRID_LEGEND_LABEL;
38013
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
+ };
38014
38049
  function getTrendArrowAndColor(trend) {
38015
38050
  if (trend > 0) {
38016
38051
  return { arrow: "\u2191", color: "text-green-400" };
@@ -38042,6 +38077,7 @@ var VideoCard = React144__default.memo(({
38042
38077
  }) => {
38043
38078
  const videoRef = useRef(null);
38044
38079
  const canvasRef = useRef(null);
38080
+ const statusBadgeIdPrefix = useId();
38045
38081
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
38046
38082
  const { isStale: isStreamStale } = useHlsStreamWithCropping(videoRef, canvasRef, {
38047
38083
  src: hlsUrl,
@@ -38068,6 +38104,7 @@ var VideoCard = React144__default.memo(({
38068
38104
  const shouldRenderMetricBadge = hasDisplayMetric;
38069
38105
  const badgeTitle = isHighEfficiencyOverride ? `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%` : hasVideoGridRecentFlow(workspace) ? `Flow ${Math.round(videoGridDisplayValue ?? 0)}%` : isRecentFlowCard ? "Flow unavailable" : `Efficiency ${Math.round(videoGridDisplayValue ?? 0)}%`;
38070
38106
  const badgeLabel = `${Math.round(videoGridDisplayValue ?? 0)}%`;
38107
+ const statusBadges = workspace.video_grid_badges || [];
38071
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";
38072
38109
  const efficiencyBarClass = videoGridColorState === "green" ? "bg-[#00AB45]" : videoGridColorState === "blue" ? "bg-[#0EA5E9]" : videoGridColorState === "yellow" ? "bg-[#FFB020]" : videoGridColorState === "red" ? "bg-[#E34329]" : "bg-gray-500/70";
38073
38110
  const efficiencyStatus = videoGridColorState === "green" ? "High" : videoGridColorState === "blue" ? "Best" : videoGridColorState === "yellow" ? "Medium" : videoGridColorState === "red" ? "Low" : "Neutral";
@@ -38146,6 +38183,61 @@ var VideoCard = React144__default.memo(({
38146
38183
  children: /* @__PURE__ */ jsx("span", { className: `${compact ? "text-[10px]" : "text-xs"} font-semibold`, children: badgeLabel })
38147
38184
  }
38148
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
+ ),
38149
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(
38150
38242
  "div",
38151
38243
  {
@@ -38182,7 +38274,7 @@ var VideoCard = React144__default.memo(({
38182
38274
  }
38183
38275
  );
38184
38276
  }, (prevProps, nextProps) => {
38185
- 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) {
38186
38278
  return false;
38187
38279
  }
38188
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) {
@@ -64808,7 +64900,6 @@ var EMPTY_LINE_IDS = [];
64808
64900
  var EMPTY_WORKSPACES = [];
64809
64901
  var ALL_GREEN_CELEBRATION_DURATION_MS = 6e3;
64810
64902
  var ALL_GREEN_MILESTONE_DURATION_MS = 6e3;
64811
- var ALL_GREEN_STREAK_LOCAL_SECOND_CAP = 59;
64812
64903
  var formatAllGreenCelebrationTimer = (elapsedSeconds) => {
64813
64904
  const safeElapsedSeconds = Math.max(1, Math.floor(elapsedSeconds));
64814
64905
  const minutes = Math.floor(safeElapsedSeconds / 60);
@@ -64816,10 +64907,7 @@ var formatAllGreenCelebrationTimer = (elapsedSeconds) => {
64816
64907
  return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}s`;
64817
64908
  };
64818
64909
  var getAllGreenBackendVisibleSeconds = (confirmedSeconds, tickOriginAtMs, nowMs2) => {
64819
- const localSecondOffset = Math.min(
64820
- ALL_GREEN_STREAK_LOCAL_SECOND_CAP,
64821
- Math.max(0, Math.floor((nowMs2 - tickOriginAtMs) / 1e3))
64822
- );
64910
+ const localSecondOffset = Math.max(0, Math.floor((nowMs2 - tickOriginAtMs) / 1e3));
64823
64911
  return confirmedSeconds + localSecondOffset;
64824
64912
  };
64825
64913
  var LoadingPageCmp = LoadingPage_default;
@@ -65279,38 +65367,25 @@ function HomeView({
65279
65367
  allGreenStreakDisplay.tickOriginAtMs,
65280
65368
  currentAllGreenStreakNowMs
65281
65369
  );
65282
- const backendMaxVisibleSeconds = confirmedSeconds + ALL_GREEN_STREAK_LOCAL_SECOND_CAP;
65283
65370
  const currentBaseline = allGreenStreakTimerBaselineRef.current;
65284
65371
  if (!currentBaseline || currentBaseline.identity !== allGreenMilestoneIdentity) {
65285
65372
  allGreenStreakTimerBaselineRef.current = {
65286
65373
  identity: allGreenMilestoneIdentity,
65287
65374
  baseSeconds: backendVisibleSeconds,
65288
- baseReceivedAtMs: currentAllGreenStreakNowMs,
65289
- maxVisibleSeconds: backendMaxVisibleSeconds
65375
+ baseReceivedAtMs: currentAllGreenStreakNowMs
65290
65376
  };
65291
65377
  } else {
65292
- currentBaseline.maxVisibleSeconds = Math.max(
65293
- currentBaseline.maxVisibleSeconds,
65294
- backendMaxVisibleSeconds
65295
- );
65296
- const visibleSeconds = Math.min(
65297
- currentBaseline.maxVisibleSeconds,
65298
- currentBaseline.baseSeconds + Math.max(0, Math.floor((currentAllGreenStreakNowMs - currentBaseline.baseReceivedAtMs) / 1e3))
65299
- );
65378
+ const visibleSeconds = currentBaseline.baseSeconds + Math.max(0, Math.floor((currentAllGreenStreakNowMs - currentBaseline.baseReceivedAtMs) / 1e3));
65300
65379
  if (backendVisibleSeconds > visibleSeconds) {
65301
65380
  allGreenStreakTimerBaselineRef.current = {
65302
65381
  identity: allGreenMilestoneIdentity,
65303
65382
  baseSeconds: backendVisibleSeconds,
65304
- baseReceivedAtMs: currentAllGreenStreakNowMs,
65305
- maxVisibleSeconds: currentBaseline.maxVisibleSeconds
65383
+ baseReceivedAtMs: currentAllGreenStreakNowMs
65306
65384
  };
65307
65385
  }
65308
65386
  }
65309
65387
  const baseline = allGreenStreakTimerBaselineRef.current;
65310
- const elapsedSeconds = baseline ? Math.min(
65311
- baseline.maxVisibleSeconds,
65312
- baseline.baseSeconds + Math.max(0, Math.floor((currentAllGreenStreakNowMs - baseline.baseReceivedAtMs) / 1e3))
65313
- ) : backendVisibleSeconds;
65388
+ const elapsedSeconds = baseline ? baseline.baseSeconds + Math.max(0, Math.floor((currentAllGreenStreakNowMs - baseline.baseReceivedAtMs) / 1e3)) : backendVisibleSeconds;
65314
65389
  return {
65315
65390
  ...allGreenStreakDisplay,
65316
65391
  elapsedSeconds,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.12.28",
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",