@optifye/dashboard-core 6.11.24 → 6.11.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3016,6 +3016,7 @@ var AuthService = class {
3016
3016
  scope_ttl_seconds: enrichedUser.scope_ttl_seconds,
3017
3017
  access_scope: enrichedUser.access_scope,
3018
3018
  company_id: enrichedUser.company_id,
3019
+ company: enrichedUser.company,
3019
3020
  first_login_completed: enrichedUser.profile.first_login_completed,
3020
3021
  properties: {
3021
3022
  company_id: enrichedUser.company_id,
@@ -13284,7 +13285,7 @@ var parseEfficiencyLegend = (legend) => {
13284
13285
  critical_threshold: coerce(legend.critical_threshold, DEFAULT_EFFICIENCY_LEGEND.critical_threshold)
13285
13286
  };
13286
13287
  };
13287
- var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds }) => {
13288
+ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, lineIds, userAccessibleLineIds }) => {
13288
13289
  const { supabaseUrl, supabaseKey } = useDashboardConfig();
13289
13290
  const entityConfig = useEntityConfig();
13290
13291
  const databaseConfig = useDatabaseConfig();
@@ -13301,9 +13302,9 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13301
13302
  return getConfiguredLineIds(entityConfig);
13302
13303
  }, [entityConfig]);
13303
13304
  const targetFactoryLineIds = React142.useMemo(() => {
13304
- const sourceLineIds = userAccessibleLineIds !== void 0 ? userAccessibleLineIds : configuredLineIds;
13305
+ const sourceLineIds = lineIds !== void 0 ? lineIds : userAccessibleLineIds !== void 0 ? userAccessibleLineIds : configuredLineIds;
13305
13306
  return Array.from(new Set((sourceLineIds || []).filter(Boolean)));
13306
- }, [userAccessibleLineIds, configuredLineIds]);
13307
+ }, [lineIds, userAccessibleLineIds, configuredLineIds]);
13307
13308
  const { shiftConfig: staticShiftConfig } = useDashboardConfig();
13308
13309
  const {
13309
13310
  shiftConfigMap: multiLineShiftConfigMap,
@@ -13356,6 +13357,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13356
13357
  const operationalShiftKeyRef = React142.useRef(operationalShiftKey);
13357
13358
  const configuredLineIdsRef = React142.useRef(configuredLineIds);
13358
13359
  const userAccessibleLineIdsRef = React142.useRef(userAccessibleLineIds);
13360
+ const explicitLineIdsRef = React142.useRef(lineIds);
13359
13361
  React142.useEffect(() => {
13360
13362
  onLineMetricsUpdateRef.current = onLineMetricsUpdate;
13361
13363
  }, [onLineMetricsUpdate]);
@@ -13371,6 +13373,9 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13371
13373
  React142.useEffect(() => {
13372
13374
  userAccessibleLineIdsRef.current = userAccessibleLineIds;
13373
13375
  }, [userAccessibleLineIds]);
13376
+ React142.useEffect(() => {
13377
+ explicitLineIdsRef.current = lineIds;
13378
+ }, [lineIds]);
13374
13379
  const companySpecificMetricsTable = React142.useMemo(
13375
13380
  () => getCompanyMetricsTableName(entityConfig.companyId, "performance_metrics"),
13376
13381
  [entityConfig.companyId]
@@ -13721,6 +13726,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13721
13726
  isFactoryView,
13722
13727
  multiLineShiftConfigMap,
13723
13728
  staticShiftConfig,
13729
+ lineIds,
13724
13730
  userAccessibleLineIds,
13725
13731
  effectiveWorkspaceConfig,
13726
13732
  shiftLoading,
@@ -13838,7 +13844,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
13838
13844
  logDebug("[useDashboardMetrics] Setting up group subscriptions:", {
13839
13845
  groupCount: currentShiftGroups.length
13840
13846
  });
13841
- const targetFactoryLineIdsForSubscriptions = currentUserAccessibleLineIds !== void 0 ? currentUserAccessibleLineIds : currentConfiguredLineIds;
13847
+ const targetFactoryLineIdsForSubscriptions = explicitLineIdsRef.current !== void 0 ? explicitLineIdsRef.current : currentUserAccessibleLineIds !== void 0 ? currentUserAccessibleLineIds : currentConfiguredLineIds;
13842
13848
  const targetFactoryLineIdSet = new Set(
13843
13849
  (targetFactoryLineIdsForSubscriptions || []).filter(Boolean)
13844
13850
  );
@@ -14009,7 +14015,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
14009
14015
  });
14010
14016
  const currentShiftDetails = shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : getCurrentShift(defaultTimezone, staticShiftConfig);
14011
14017
  const operationalDateForSubscription = currentShiftDetails.date;
14012
- const targetLineIds = isFactory ? currentUserAccessibleLineIds || currentConfiguredLineIds : [currentLineIdToUse];
14018
+ const targetLineIds = isFactory ? explicitLineIdsRef.current || currentUserAccessibleLineIds || currentConfiguredLineIds : [currentLineIdToUse];
14013
14019
  const filteredLineIds = targetLineIds.filter((id3) => id3 && id3 !== factoryViewIdentifier);
14014
14020
  if (filteredLineIds.length === 0) {
14015
14021
  logDebug("[useDashboardMetrics] Realtime setup skipped: no line IDs after filtering", {
@@ -20251,6 +20257,17 @@ function useCompanyHasVlmEnabledLine(options = {}) {
20251
20257
  };
20252
20258
  }
20253
20259
 
20260
+ // src/lib/hooks/useCompanyFastSlowClipFiltersEnabled.ts
20261
+ function useCompanyFastSlowClipFiltersEnabled() {
20262
+ const { user, loading } = useAuth();
20263
+ const company = user?.company || user?.properties?.company;
20264
+ const explicitValue = company?.enable_fast_slow_clip_filters;
20265
+ return {
20266
+ isFastSlowClipFiltersEnabled: typeof explicitValue === "boolean" ? explicitValue : true,
20267
+ isResolved: !loading || typeof explicitValue === "boolean"
20268
+ };
20269
+ }
20270
+
20254
20271
  // src/lib/utils/api.ts
20255
20272
  var apiUtils = {
20256
20273
  /**
@@ -20423,13 +20440,15 @@ var buildKPIsFromLineMetricsRow = (row) => {
20423
20440
  };
20424
20441
  var aggregateKPIsFromLineMetricsRows = (rows) => {
20425
20442
  if (!rows || rows.length === 0) return createDefaultKPIs();
20426
- const currentOutputSum = rows.reduce((sum, row) => sum + toNumber(row.current_output), 0);
20427
- const lineThresholdSum = rows.reduce((sum, row) => sum + toNumber(row.line_threshold), 0);
20428
- const idealOutputSum = rows.reduce(
20443
+ const eligibleRows = rows.filter((row) => toNumber(row?.avg_efficiency) >= 5);
20444
+ if (eligibleRows.length === 0) return createDefaultKPIs();
20445
+ const currentOutputSum = eligibleRows.reduce((sum, row) => sum + toNumber(row.current_output), 0);
20446
+ const lineThresholdSum = eligibleRows.reduce((sum, row) => sum + toNumber(row.line_threshold), 0);
20447
+ const idealOutputSum = eligibleRows.reduce(
20429
20448
  (sum, row) => sum + (toNumber(row.ideal_output) || toNumber(row.line_threshold)),
20430
20449
  0
20431
20450
  );
20432
- const efficiencyValues = rows.map((row) => {
20451
+ const efficiencyValues = eligibleRows.map((row) => {
20433
20452
  const value = row?.avg_efficiency;
20434
20453
  if (typeof value === "number" && Number.isFinite(value)) return value;
20435
20454
  if (typeof value === "string" && value.trim() !== "") {
@@ -20439,10 +20458,10 @@ var aggregateKPIsFromLineMetricsRows = (rows) => {
20439
20458
  return null;
20440
20459
  }).filter((value) => value !== null);
20441
20460
  const avgEfficiency = efficiencyValues.length > 0 ? efficiencyValues.reduce((sum, value) => sum + value, 0) / efficiencyValues.length : 0;
20442
- const numLines = rows.length;
20443
- const avgCycleTime = numLines > 0 ? rows.reduce((sum, row) => sum + toNumber(row.avg_cycle_time), 0) / numLines : 0;
20444
- const totalUnderperforming = rows.reduce((sum, row) => sum + toNumber(row.underperforming_workspaces), 0);
20445
- const totalWorkspaces = rows.reduce((sum, row) => sum + toNumber(row.total_workspaces), 0);
20461
+ const numLines = eligibleRows.length;
20462
+ const avgCycleTime = numLines > 0 ? eligibleRows.reduce((sum, row) => sum + toNumber(row.avg_cycle_time), 0) / numLines : 0;
20463
+ const totalUnderperforming = eligibleRows.reduce((sum, row) => sum + toNumber(row.underperforming_workspaces), 0);
20464
+ const totalWorkspaces = eligibleRows.reduce((sum, row) => sum + toNumber(row.total_workspaces), 0);
20446
20465
  return {
20447
20466
  underperformingWorkers: {
20448
20467
  current: totalUnderperforming,
@@ -34598,6 +34617,8 @@ var logDebug2 = (...args) => {
34598
34617
  var VideoGridView = React142__namespace.default.memo(({
34599
34618
  workspaces,
34600
34619
  selectedLine,
34620
+ lineNames = {},
34621
+ lineOrder = [],
34601
34622
  className = "",
34602
34623
  legend,
34603
34624
  videoSources = {},
@@ -34700,6 +34721,44 @@ var VideoGridView = React142__namespace.default.memo(({
34700
34721
  }
34701
34722
  }) : workspaces;
34702
34723
  }, [workspaces, selectedLine]);
34724
+ const sortedWorkspaces = React142.useMemo(() => {
34725
+ return [...filteredWorkspaces].sort((a, b) => {
34726
+ if (a.line_id !== b.line_id) {
34727
+ return (a.line_id || "").localeCompare(b.line_id || "");
34728
+ }
34729
+ const aMatch = a.workspace_name.match(/WS(\d+)/);
34730
+ const bMatch = b.workspace_name.match(/WS(\d+)/);
34731
+ if (aMatch && bMatch) {
34732
+ const aNum = parseInt(aMatch[1], 10);
34733
+ const bNum = parseInt(bMatch[1], 10);
34734
+ return aNum - bNum;
34735
+ }
34736
+ return a.workspace_name.localeCompare(b.workspace_name);
34737
+ });
34738
+ }, [filteredWorkspaces]);
34739
+ const lineGroups = React142.useMemo(() => {
34740
+ const grouped = /* @__PURE__ */ new Map();
34741
+ sortedWorkspaces.forEach((workspace) => {
34742
+ const lineId = workspace.line_id || "unknown";
34743
+ const existing = grouped.get(lineId);
34744
+ if (existing) {
34745
+ existing.push(workspace);
34746
+ return;
34747
+ }
34748
+ grouped.set(lineId, [workspace]);
34749
+ });
34750
+ const sortedRemainingLineIds = Array.from(grouped.keys()).sort((a, b) => a.localeCompare(b));
34751
+ const orderedLineIds = [
34752
+ ...lineOrder.filter((lineId) => grouped.has(lineId)),
34753
+ ...sortedRemainingLineIds.filter((lineId) => !lineOrder.includes(lineId))
34754
+ ];
34755
+ return orderedLineIds.map((lineId) => ({
34756
+ lineId,
34757
+ lineName: lineNames[lineId] || `Line ${lineId.substring(0, 4)}`,
34758
+ workspaces: grouped.get(lineId) || []
34759
+ }));
34760
+ }, [sortedWorkspaces, lineOrder, lineNames]);
34761
+ lineGroups.length > 1;
34703
34762
  const streamsReady = !videoStreamsLoading;
34704
34763
  const calculateOptimalGrid = React142.useCallback(() => {
34705
34764
  if (!containerRef.current) return;
@@ -34863,108 +34922,130 @@ var VideoGridView = React142__namespace.default.memo(({
34863
34922
  stream_source: isR2Stream ? "r2" : "media_config"
34864
34923
  });
34865
34924
  }, []);
34925
+ const workspaceCards = React142.useMemo(() => {
34926
+ return sortedWorkspaces.map((workspace) => {
34927
+ const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
34928
+ const workspaceKey = `${workspace.line_id || "unknown"}-${workspaceId}`;
34929
+ const isVisible = visibleWorkspaces.has(workspaceId);
34930
+ const isVeryLowEfficiency = workspace.show_exclamation ?? (workspace.efficiency < 50 && workspace.efficiency >= 10);
34931
+ const workspaceCropping = getWorkspaceCropping(workspaceId, workspace.workspace_name);
34932
+ const workspaceStream = videoStreamsByWorkspaceId?.[workspaceId];
34933
+ const lastSeenLabel = workspace.workspace_uuid ? lastSeenByWorkspaceId[workspace.workspace_uuid]?.timeSinceLastUpdate : void 0;
34934
+ const r2Url = workspaceStream?.hls_url;
34935
+ const fallbackUrl = getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id);
34936
+ const hasR2Stream = Boolean(r2Url);
34937
+ const useFallback = r2FallbackWorkspaces.has(workspaceId) || streamsReady && !hasR2Stream;
34938
+ const hlsUrl = useFallback ? fallbackUrl : r2Url ?? "";
34939
+ const isR2Stream = !useFallback && hasR2Stream;
34940
+ const canAttemptR2 = hasR2Stream && !r2FallbackWorkspaces.has(workspaceId);
34941
+ const shouldPlay = isVisible && Boolean(hlsUrl) && (!failedStreams.has(workspaceId) || canAttemptR2);
34942
+ return {
34943
+ workspace,
34944
+ workspaceId,
34945
+ workspaceKey,
34946
+ isVisible,
34947
+ isVeryLowEfficiency,
34948
+ workspaceCropping,
34949
+ fallbackUrl,
34950
+ hlsUrl,
34951
+ isR2Stream,
34952
+ shouldPlay,
34953
+ lastSeenLabel
34954
+ };
34955
+ });
34956
+ }, [
34957
+ sortedWorkspaces,
34958
+ visibleWorkspaces,
34959
+ getWorkspaceCropping,
34960
+ videoStreamsByWorkspaceId,
34961
+ lastSeenByWorkspaceId,
34962
+ getWorkspaceHlsUrl,
34963
+ r2FallbackWorkspaces,
34964
+ streamsReady,
34965
+ failedStreams
34966
+ ]);
34967
+ React142.useMemo(() => {
34968
+ const map = /* @__PURE__ */ new Map();
34969
+ workspaceCards.forEach((card) => {
34970
+ map.set(card.workspaceKey, card);
34971
+ });
34972
+ return map;
34973
+ }, [workspaceCards]);
34974
+ const croppedActiveCount = React142.useMemo(() => {
34975
+ return workspaceCards.reduce((count, card) => {
34976
+ if (card.shouldPlay && card.workspaceCropping) {
34977
+ return count + 1;
34978
+ }
34979
+ return count;
34980
+ }, 0);
34981
+ }, [workspaceCards]);
34982
+ const throttleCropping = croppedActiveCount > 10;
34983
+ const effectiveCanvasFps = throttleCropping ? 10 : canvasConfig?.fps;
34984
+ const effectiveUseRAF = throttleCropping ? false : canvasConfig?.useRAF;
34866
34985
  const displayMinuteBucket = Math.floor(Date.now() / 6e4);
34867
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `relative overflow-hidden h-full w-full ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "h-full w-full p-3 sm:p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
34986
+ const renderWorkspaceCard = React142.useCallback((card, className2) => /* @__PURE__ */ jsxRuntime.jsx(
34868
34987
  "div",
34869
34988
  {
34870
- className: "grid h-full w-full gap-3 sm:gap-2",
34871
- style: {
34872
- gridTemplateColumns: `repeat(${gridCols}, 1fr)`,
34873
- gridTemplateRows: `repeat(${gridRows}, 1fr)`,
34874
- gridAutoFlow: "row"
34875
- },
34876
- children: (() => {
34877
- const sortedWorkspaces = [...filteredWorkspaces].sort((a, b) => {
34878
- if (a.line_id !== b.line_id) {
34879
- return (a.line_id || "").localeCompare(b.line_id || "");
34880
- }
34881
- const aMatch = a.workspace_name.match(/WS(\d+)/);
34882
- const bMatch = b.workspace_name.match(/WS(\d+)/);
34883
- if (aMatch && bMatch) {
34884
- const aNum = parseInt(aMatch[1]);
34885
- const bNum = parseInt(bMatch[1]);
34886
- return aNum - bNum;
34887
- }
34888
- return a.workspace_name.localeCompare(b.workspace_name);
34889
- });
34890
- const workspaceCards = sortedWorkspaces.map((workspace) => {
34891
- const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
34892
- `${workspace.line_id || "unknown"}-${workspaceId}`;
34893
- const isVisible = visibleWorkspaces.has(workspaceId);
34894
- const isVeryLowEfficiency = workspace.show_exclamation ?? (workspace.efficiency < 50 && workspace.efficiency >= 10);
34895
- const workspaceCropping = getWorkspaceCropping(workspaceId, workspace.workspace_name);
34896
- const workspaceStream = videoStreamsByWorkspaceId?.[workspaceId];
34897
- const lastSeenLabel = workspace.workspace_uuid ? lastSeenByWorkspaceId[workspace.workspace_uuid]?.timeSinceLastUpdate : void 0;
34898
- const r2Url = workspaceStream?.hls_url;
34899
- const fallbackUrl = getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id);
34900
- const hasR2Stream = Boolean(r2Url);
34901
- const useFallback = r2FallbackWorkspaces.has(workspaceId) || streamsReady && !hasR2Stream;
34902
- const hlsUrl = useFallback ? fallbackUrl : r2Url ?? "";
34903
- const isR2Stream = !useFallback && hasR2Stream;
34904
- const canAttemptR2 = hasR2Stream && !r2FallbackWorkspaces.has(workspaceId);
34905
- const shouldPlay = isVisible && Boolean(hlsUrl) && (!failedStreams.has(workspaceId) || canAttemptR2);
34906
- return {
34907
- workspace,
34908
- workspaceId,
34909
- isVisible,
34910
- isVeryLowEfficiency,
34911
- workspaceCropping,
34912
- fallbackUrl,
34913
- hlsUrl,
34914
- isR2Stream,
34915
- shouldPlay,
34916
- lastSeenLabel
34917
- };
34918
- });
34919
- const croppedActiveCount = workspaceCards.reduce((count, card) => {
34920
- if (card.shouldPlay && card.workspaceCropping) {
34921
- return count + 1;
34922
- }
34923
- return count;
34924
- }, 0);
34925
- const throttleCropping = croppedActiveCount > 10;
34926
- const effectiveCanvasFps = throttleCropping ? 10 : canvasConfig?.fps;
34927
- const effectiveUseRAF = throttleCropping ? false : canvasConfig?.useRAF;
34928
- return workspaceCards.map((card) => /* @__PURE__ */ jsxRuntime.jsx(
34929
- "div",
34930
- {
34931
- "data-workspace-id": card.workspaceId,
34932
- className: "workspace-card relative w-full h-full",
34933
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsxRuntime.jsx(
34934
- VideoCard,
34935
- {
34936
- workspace: card.workspace,
34937
- hlsUrl: card.hlsUrl,
34938
- shouldPlay: card.shouldPlay,
34939
- onClick: () => handleWorkspaceClick(card.workspace),
34940
- onFatalError: () => handleStreamError(card.workspaceId, {
34941
- isR2Stream: card.isR2Stream,
34942
- fallbackUrl: card.fallbackUrl
34943
- }),
34944
- isVeryLowEfficiency: card.isVeryLowEfficiency,
34945
- legend: effectiveLegend,
34946
- cropping: card.workspaceCropping,
34947
- canvasFps: effectiveCanvasFps,
34948
- displayName: (
34949
- // Create line-aware lookup key: lineId_workspaceName
34950
- // This ensures correct mapping when multiple lines have same workspace names
34951
- displayNames[`${card.workspace.line_id}_${card.workspace.workspace_name}`] || // Always pass line_id to fallback to ensure correct mapping per line
34952
- getWorkspaceDisplayName(card.workspace.workspace_name, card.workspace.line_id)
34953
- ),
34954
- lastSeenLabel: card.lastSeenLabel,
34955
- useRAF: effectiveUseRAF,
34956
- displayMinuteBucket,
34957
- compact: !selectedLine,
34958
- onMouseEnter: onWorkspaceHover ? () => onWorkspaceHover(card.workspaceId) : void 0,
34959
- onMouseLeave: onWorkspaceHoverEnd ? () => onWorkspaceHoverEnd(card.workspaceId) : void 0
34960
- }
34961
- ) })
34989
+ "data-workspace-id": card.workspaceId,
34990
+ className: className2,
34991
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsxRuntime.jsx(
34992
+ VideoCard,
34993
+ {
34994
+ workspace: card.workspace,
34995
+ hlsUrl: card.hlsUrl,
34996
+ shouldPlay: card.shouldPlay,
34997
+ onClick: () => handleWorkspaceClick(card.workspace),
34998
+ onFatalError: () => handleStreamError(card.workspaceId, {
34999
+ isR2Stream: card.isR2Stream,
35000
+ fallbackUrl: card.fallbackUrl
35001
+ }),
35002
+ isVeryLowEfficiency: card.isVeryLowEfficiency,
35003
+ legend: effectiveLegend,
35004
+ cropping: card.workspaceCropping,
35005
+ canvasFps: effectiveCanvasFps,
35006
+ displayName: displayNames[`${card.workspace.line_id}_${card.workspace.workspace_name}`] || getWorkspaceDisplayName(card.workspace.workspace_name, card.workspace.line_id),
35007
+ lastSeenLabel: card.lastSeenLabel,
35008
+ useRAF: effectiveUseRAF,
35009
+ displayMinuteBucket,
35010
+ compact: !selectedLine,
35011
+ onMouseEnter: onWorkspaceHover ? () => onWorkspaceHover(card.workspaceId) : void 0,
35012
+ onMouseLeave: onWorkspaceHoverEnd ? () => onWorkspaceHoverEnd(card.workspaceId) : void 0
35013
+ }
35014
+ ) })
35015
+ },
35016
+ card.workspaceKey
35017
+ ), [
35018
+ displayNames,
35019
+ displayMinuteBucket,
35020
+ effectiveCanvasFps,
35021
+ effectiveLegend,
35022
+ effectiveUseRAF,
35023
+ getWorkspaceDisplayName,
35024
+ handleStreamError,
35025
+ handleWorkspaceClick,
35026
+ onWorkspaceHover,
35027
+ onWorkspaceHoverEnd,
35028
+ selectedLine
35029
+ ]);
35030
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `relative overflow-hidden h-full w-full bg-slate-50/30 ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx(
35031
+ "div",
35032
+ {
35033
+ ref: containerRef,
35034
+ className: "h-full w-full px-1 sm:px-2 py-1 sm:py-2",
35035
+ children: /* @__PURE__ */ jsxRuntime.jsx(
35036
+ "div",
35037
+ {
35038
+ className: "grid h-full w-full gap-1.5 sm:gap-2",
35039
+ style: {
35040
+ gridTemplateColumns: `repeat(${gridCols}, 1fr)`,
35041
+ gridTemplateRows: `repeat(${gridRows}, 1fr)`,
35042
+ gridAutoFlow: "row"
34962
35043
  },
34963
- card.workspaceId
34964
- ));
34965
- })()
35044
+ children: workspaceCards.map((card) => renderWorkspaceCard(card, "workspace-card relative w-full h-full"))
35045
+ }
35046
+ )
34966
35047
  }
34967
- ) }) });
35048
+ ) });
34968
35049
  });
34969
35050
  VideoGridView.displayName = "VideoGridView";
34970
35051
  var MapGridView = React142__namespace.default.memo(({
@@ -38157,14 +38238,15 @@ var HlsVideoPlayer = React142.forwardRef(({
38157
38238
  };
38158
38239
  }, [effectiveSrc, initializePlayer, cleanupBlobUrl]);
38159
38240
  React142.useEffect(() => {
38160
- if (videoRef.current) {
38161
- if (autoplay) {
38162
- videoRef.current.play().catch((err) => {
38163
- console.warn("[HlsVideoPlayer] Autoplay failed:", err);
38164
- });
38165
- }
38241
+ if (!videoRef.current || !autoplay || !effectiveSrc) {
38242
+ return;
38166
38243
  }
38167
- }, [autoplay]);
38244
+ videoRef.current.play().catch((err) => {
38245
+ if (err?.name !== "AbortError") {
38246
+ console.warn("[HlsVideoPlayer] Autoplay failed:", err);
38247
+ }
38248
+ });
38249
+ }, [autoplay, effectiveSrc]);
38168
38250
  const resetControlsTimeout = React142.useCallback(() => {
38169
38251
  if (controlsPinned) {
38170
38252
  setShowControls(true);
@@ -40085,7 +40167,8 @@ var FileManagerFilters = ({
40085
40167
  snapshotClipId,
40086
40168
  className = "",
40087
40169
  targetCycleTime = null,
40088
- idleTimeVlmEnabled = false
40170
+ idleTimeVlmEnabled = false,
40171
+ showPercentileCycleFilters = true
40089
40172
  }) => {
40090
40173
  const [expandedNodes, setExpandedNodes] = React142.useState(/* @__PURE__ */ new Set());
40091
40174
  const [startTime, setStartTime] = React142.useState("");
@@ -40364,6 +40447,9 @@ var FileManagerFilters = ({
40364
40447
  }
40365
40448
  }, [ensureAllIdleTimeClipMetadataLoaded]);
40366
40449
  const fetchPercentileClips = React142.useCallback(async (type) => {
40450
+ if (!showPercentileCycleFilters && type !== "idle-times") {
40451
+ return;
40452
+ }
40367
40453
  if (!workspaceId || !date || shift === void 0) {
40368
40454
  console.warn("[FileManager] Missing required params for percentile clips fetch");
40369
40455
  return;
@@ -40410,7 +40496,7 @@ var FileManagerFilters = ({
40410
40496
  } catch (error) {
40411
40497
  console.error(`[FileManager] Error fetching ${type} clips:`, error);
40412
40498
  }
40413
- }, [workspaceId, date, shift, filterState.percentile, supabase]);
40499
+ }, [workspaceId, date, shift, filterState.percentile, showPercentileCycleFilters, supabase]);
40414
40500
  const percentileCountsKey = React142.useMemo(() => {
40415
40501
  if (!workspaceId || !date || shift === void 0) {
40416
40502
  return null;
@@ -40418,7 +40504,7 @@ var FileManagerFilters = ({
40418
40504
  return `${workspaceId}:${date}:${shift}:${filterState.percentile}`;
40419
40505
  }, [workspaceId, date, shift, filterState.percentile]);
40420
40506
  React142.useEffect(() => {
40421
- if (!percentileCountsKey) {
40507
+ if (!showPercentileCycleFilters || !percentileCountsKey) {
40422
40508
  percentileCountsKeyRef.current = null;
40423
40509
  percentilePrefetchRef.current = { key: null, types: /* @__PURE__ */ new Set() };
40424
40510
  setPercentileCounts({
@@ -40438,8 +40524,11 @@ var FileManagerFilters = ({
40438
40524
  "slow-cycles": null
40439
40525
  });
40440
40526
  setPercentileClips({});
40441
- }, [percentileCountsKey]);
40527
+ }, [showPercentileCycleFilters, percentileCountsKey]);
40442
40528
  React142.useEffect(() => {
40529
+ if (!showPercentileCycleFilters) {
40530
+ return;
40531
+ }
40443
40532
  if (!prefetchedPercentileCounts || !percentileCountsKey) {
40444
40533
  return;
40445
40534
  }
@@ -40454,8 +40543,11 @@ var FileManagerFilters = ({
40454
40543
  "fast-cycles": typeof prefetchedPercentileCounts.counts["fast-cycles"] === "number" ? prefetchedPercentileCounts.counts["fast-cycles"] : prev["fast-cycles"],
40455
40544
  "slow-cycles": typeof prefetchedPercentileCounts.counts["slow-cycles"] === "number" ? prefetchedPercentileCounts.counts["slow-cycles"] : prev["slow-cycles"]
40456
40545
  }));
40457
- }, [prefetchedPercentileCounts, percentileCountsKey, filterState.percentile]);
40546
+ }, [showPercentileCycleFilters, prefetchedPercentileCounts, percentileCountsKey, filterState.percentile]);
40458
40547
  const fetchPercentileCounts = React142.useCallback(async (options) => {
40548
+ if (!showPercentileCycleFilters) {
40549
+ return;
40550
+ }
40459
40551
  if (!workspaceId || !date || shift === void 0) {
40460
40552
  console.warn("[FileManager] Missing required params for percentile counts fetch");
40461
40553
  return;
@@ -40509,9 +40601,9 @@ var FileManagerFilters = ({
40509
40601
  } catch (error) {
40510
40602
  console.error("[FileManager] Error fetching percentile counts:", error);
40511
40603
  }
40512
- }, [workspaceId, date, shift, filterState.percentile, supabase, percentileCounts, percentileClips, fetchPercentileClips]);
40604
+ }, [workspaceId, date, shift, filterState.percentile, showPercentileCycleFilters, supabase, percentileCounts, percentileClips, fetchPercentileClips]);
40513
40605
  React142.useEffect(() => {
40514
- if (!isReady || !percentileCountsKey) {
40606
+ if (!showPercentileCycleFilters || !isReady || !percentileCountsKey) {
40515
40607
  return;
40516
40608
  }
40517
40609
  const schedule = () => {
@@ -40522,13 +40614,13 @@ var FileManagerFilters = ({
40522
40614
  } else {
40523
40615
  setTimeout(schedule, 0);
40524
40616
  }
40525
- }, [isReady, percentileCountsKey, fetchPercentileCounts]);
40617
+ }, [showPercentileCycleFilters, isReady, percentileCountsKey, fetchPercentileCounts]);
40526
40618
  const shouldShowCategory = React142.useCallback((categoryId) => {
40527
40619
  switch (categoryId) {
40528
40620
  case "fast-cycles":
40529
- return filterState.showFastCycles;
40621
+ return showPercentileCycleFilters && filterState.showFastCycles;
40530
40622
  case "slow-cycles":
40531
- return filterState.showSlowCycles;
40623
+ return showPercentileCycleFilters && filterState.showSlowCycles;
40532
40624
  case "longest-idles":
40533
40625
  return false;
40534
40626
  // filterState.showLongestIdles; // Temporarily disabled
@@ -40541,7 +40633,7 @@ var FileManagerFilters = ({
40541
40633
  default:
40542
40634
  return true;
40543
40635
  }
40544
- }, [filterState]);
40636
+ }, [filterState, showPercentileCycleFilters]);
40545
40637
  const getPercentileIcon = React142.useCallback((type, isExpanded, colorClasses) => {
40546
40638
  const iconMap = {
40547
40639
  "fast-cycles": { icon: lucideReact.TrendingUp, color: "text-green-600" },
@@ -40712,7 +40804,7 @@ var FileManagerFilters = ({
40712
40804
  const filteredSlowCycles = (percentileClips["slow-cycles"] || []).filter((clip) => isClipInTimeRange(clip.creation_timestamp || ""));
40713
40805
  const fastCount = typeof percentileCounts["fast-cycles"] === "number" ? percentileCounts["fast-cycles"] : null;
40714
40806
  const slowCount = typeof percentileCounts["slow-cycles"] === "number" ? percentileCounts["slow-cycles"] : null;
40715
- const percentileCategories = [
40807
+ const percentileCategories = showPercentileCycleFilters ? [
40716
40808
  {
40717
40809
  id: "fast-cycles",
40718
40810
  label: "Fast Cycles",
@@ -40810,7 +40902,7 @@ var FileManagerFilters = ({
40810
40902
  };
40811
40903
  })
40812
40904
  } */
40813
- ];
40905
+ ] : [];
40814
40906
  const orderedIds = ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
40815
40907
  orderedIds.forEach((orderedId) => {
40816
40908
  const percentileCategory = percentileCategories.find((cat) => cat.id === orderedId);
@@ -40835,7 +40927,7 @@ var FileManagerFilters = ({
40835
40927
  }
40836
40928
  });
40837
40929
  return tree;
40838
- }, [categories, expandedNodes, counts, clipMetadata, percentileCounts, percentileClips, shouldShowCategory, getPercentileIcon, isClipInTimeRange, isTimeFilterActive]);
40930
+ }, [categories, expandedNodes, counts, clipMetadata, percentileCounts, percentileClips, shouldShowCategory, getPercentileIcon, isClipInTimeRange, isTimeFilterActive, showPercentileCycleFilters]);
40839
40931
  const toggleExpanded = (nodeId) => {
40840
40932
  const newExpanded = new Set(expandedNodes);
40841
40933
  if (newExpanded.has(nodeId)) {
@@ -40847,10 +40939,10 @@ var FileManagerFilters = ({
40847
40939
  console.log(`[FileManager] Fetching clips for expanded category: ${nodeId}`);
40848
40940
  fetchClipMetadata(nodeId, 1);
40849
40941
  }
40850
- if (nodeId === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40942
+ if (showPercentileCycleFilters && nodeId === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40851
40943
  fetchPercentileClips("fast-cycles");
40852
40944
  }
40853
- if (nodeId === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40945
+ if (showPercentileCycleFilters && nodeId === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40854
40946
  fetchPercentileClips("slow-cycles");
40855
40947
  }
40856
40948
  }
@@ -40868,10 +40960,10 @@ var FileManagerFilters = ({
40868
40960
  console.log(`[FileManager] Fetching clips for expanded category: ${node.id}`);
40869
40961
  fetchClipMetadata(node.id, 1);
40870
40962
  }
40871
- if (node.id === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40963
+ if (showPercentileCycleFilters && node.id === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40872
40964
  fetchPercentileClips("fast-cycles");
40873
40965
  }
40874
- if (node.id === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40966
+ if (showPercentileCycleFilters && node.id === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40875
40967
  fetchPercentileClips("slow-cycles");
40876
40968
  }
40877
40969
  }
@@ -41309,7 +41401,8 @@ var FileManagerFilters = ({
41309
41401
  };
41310
41402
  var PERCENTILE_PRESETS = [5, 10, 15, 20, 25, 30];
41311
41403
  var AdvancedFilterDialog = ({
41312
- onApply
41404
+ onApply,
41405
+ showPercentileCycleFilters = true
41313
41406
  }) => {
41314
41407
  const {
41315
41408
  state,
@@ -41376,7 +41469,7 @@ var AdvancedFilterDialog = ({
41376
41469
  )
41377
41470
  ] }) }),
41378
41471
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 space-y-8 overflow-y-auto max-h-[60vh]", children: [
41379
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
41472
+ showPercentileCycleFilters && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
41380
41473
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-3", children: [
41381
41474
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 bg-gradient-to-r from-emerald-100 to-blue-100 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-5 w-5 text-emerald-600" }) }),
41382
41475
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -41469,7 +41562,7 @@ var AdvancedFilterDialog = ({
41469
41562
  ] })
41470
41563
  ] }),
41471
41564
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [
41472
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-gradient-to-br from-green-50 to-red-50 rounded-xl p-4 space-y-3", children: [
41565
+ showPercentileCycleFilters && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-gradient-to-br from-green-50 to-red-50 rounded-xl p-4 space-y-3", children: [
41473
41566
  /* @__PURE__ */ jsxRuntime.jsxs("h4", { className: "text-sm font-bold text-gray-700 uppercase tracking-wide flex items-center space-x-2", children: [
41474
41567
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-4 w-4 text-emerald-600" }),
41475
41568
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Performance Extremes" })
@@ -41750,6 +41843,7 @@ var BottlenecksContent = ({
41750
41843
  const supabase = useSupabase();
41751
41844
  const timezone = useAppTimezone();
41752
41845
  const { isIdleTimeVlmEnabled } = useIdleTimeVlmConfig();
41846
+ const { isFastSlowClipFiltersEnabled } = useCompanyFastSlowClipFiltersEnabled();
41753
41847
  const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineId);
41754
41848
  const { effectiveShift, effectiveDate } = React142.useMemo(() => {
41755
41849
  if (shift !== void 0 && shift !== null && date) {
@@ -41806,12 +41900,16 @@ var BottlenecksContent = ({
41806
41900
  }), []);
41807
41901
  const [initialFilter, setInitialFilter] = React142.useState("");
41808
41902
  const currentIndexRef = React142.useRef(0);
41903
+ const currentClipIdRef = React142.useRef(null);
41809
41904
  const currentPositionRef = React142.useRef(0);
41810
41905
  const currentTotalRef = React142.useRef(0);
41811
41906
  const activeFilterRef = React142.useRef(initialFilter);
41812
41907
  const isMountedRef = React142.useRef(true);
41813
41908
  const fetchInProgressRef = React142.useRef(/* @__PURE__ */ new Set());
41814
41909
  const refreshCountsTimeoutRef = React142.useRef(null);
41910
+ const awaitingNextClipRef = React142.useRef(false);
41911
+ const retryTimeoutRef = React142.useRef(null);
41912
+ const navigationLockRef = React142.useRef(false);
41815
41913
  const [isPlaying, setIsPlaying] = React142.useState(false);
41816
41914
  const [currentTime, setCurrentTime] = React142.useState(0);
41817
41915
  const [duration, setDuration] = React142.useState(0);
@@ -41820,6 +41918,7 @@ var BottlenecksContent = ({
41820
41918
  const [playbackSpeed, setPlaybackSpeed] = React142.useState(1);
41821
41919
  const [currentPosition, setCurrentPosition] = React142.useState(0);
41822
41920
  const [currentTotal, setCurrentTotal] = React142.useState(0);
41921
+ const [playerInstanceNonce, setPlayerInstanceNonce] = React142.useState(0);
41823
41922
  const [isTransitioning, setIsTransitioning] = React142.useState(false);
41824
41923
  const [pendingVideo, setPendingVideo] = React142.useState(null);
41825
41924
  const [isVideoBuffering, setIsVideoBuffering] = React142.useState(false);
@@ -41876,14 +41975,29 @@ var BottlenecksContent = ({
41876
41975
  const [isFullscreen, setIsFullscreen] = React142.useState(false);
41877
41976
  const categoryMetadataRef = React142.useRef([]);
41878
41977
  const currentMetadataIndexRef = React142.useRef(0);
41978
+ const clearRetryTimeout = React142.useCallback(() => {
41979
+ if (retryTimeoutRef.current) {
41980
+ clearTimeout(retryTimeoutRef.current);
41981
+ retryTimeoutRef.current = null;
41982
+ }
41983
+ }, []);
41984
+ const bumpPlayerInstanceNonce = React142.useCallback(() => {
41985
+ setPlayerInstanceNonce((prev) => prev + 1);
41986
+ }, []);
41879
41987
  const updateActiveFilter = React142.useCallback((newFilter) => {
41880
41988
  console.log(`[BottlenecksContent] Updating active filter: ${activeFilterRef.current} -> ${newFilter}`);
41989
+ awaitingNextClipRef.current = false;
41990
+ navigationLockRef.current = false;
41991
+ clearRetryTimeout();
41881
41992
  setActiveFilter(newFilter);
41882
41993
  activeFilterRef.current = newFilter;
41883
- }, []);
41994
+ }, [clearRetryTimeout]);
41884
41995
  React142.useEffect(() => {
41885
41996
  currentIndexRef.current = currentIndex;
41886
41997
  }, [currentIndex]);
41998
+ React142.useEffect(() => {
41999
+ currentClipIdRef.current = currentClipId;
42000
+ }, [currentClipId]);
41887
42001
  React142.useEffect(() => {
41888
42002
  currentPositionRef.current = currentPosition;
41889
42003
  }, [currentPosition]);
@@ -42292,8 +42406,11 @@ var BottlenecksContent = ({
42292
42406
  }
42293
42407
  }, [activeFilter, s3ClipsService, mergedCounts, loadFirstVideoForCategory, allVideos, firstClip]);
42294
42408
  const isPercentileCategory = React142.useCallback((categoryId) => {
42409
+ if (!isFastSlowClipFiltersEnabled) {
42410
+ return false;
42411
+ }
42295
42412
  return ["fast-cycles", "slow-cycles", "longest-idles"].includes(categoryId);
42296
- }, []);
42413
+ }, [isFastSlowClipFiltersEnabled]);
42297
42414
  const getMetadataCacheKey = React142.useCallback((categoryId) => {
42298
42415
  return `${categoryId}-${effectiveDateString}-${effectiveShiftId}-${snapshotDateTime ?? "nosnap"}-${snapshotClipId ?? "nosnap"}`;
42299
42416
  }, [effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId]);
@@ -42325,6 +42442,21 @@ var BottlenecksContent = ({
42325
42442
  return [categoryId];
42326
42443
  }
42327
42444
  }, []);
42445
+ React142.useEffect(() => {
42446
+ if (isFastSlowClipFiltersEnabled) {
42447
+ return;
42448
+ }
42449
+ if (activeFilter !== "fast-cycles" && activeFilter !== "slow-cycles") {
42450
+ return;
42451
+ }
42452
+ const fallbackFilter = ["cycle_completion", "idle_time"].find((type) => (dynamicCounts[type] || 0) > 0) || clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0)?.type || clipTypes[0]?.type;
42453
+ if (!fallbackFilter || fallbackFilter === activeFilter) {
42454
+ return;
42455
+ }
42456
+ setCategoryMetadata([]);
42457
+ categoryMetadataRef.current = [];
42458
+ updateActiveFilter(fallbackFilter);
42459
+ }, [isFastSlowClipFiltersEnabled, activeFilter, dynamicCounts, clipTypes, updateActiveFilter]);
42328
42460
  React142.useCallback((categoryId) => {
42329
42461
  if (isPercentileCategory(categoryId)) {
42330
42462
  return categoryMetadata.length;
@@ -42354,6 +42486,7 @@ var BottlenecksContent = ({
42354
42486
  }
42355
42487
  }, [isNavigating, currentIndex, filteredVideos.length]);
42356
42488
  const clearLoadingState = React142.useCallback(() => {
42489
+ navigationLockRef.current = false;
42357
42490
  setIsTransitioning(false);
42358
42491
  setIsNavigating(false);
42359
42492
  if (loadingTimeoutRef.current) {
@@ -42365,6 +42498,11 @@ var BottlenecksContent = ({
42365
42498
  if (!workspaceId) {
42366
42499
  return;
42367
42500
  }
42501
+ if (!isFastSlowClipFiltersEnabled && (categoryId === "fast-cycles" || categoryId === "slow-cycles")) {
42502
+ setCategoryMetadata([]);
42503
+ categoryMetadataRef.current = [];
42504
+ return;
42505
+ }
42368
42506
  if (!isEffectiveShiftReady) {
42369
42507
  console.log("[BottlenecksContent] Skipping metadata load - shift/date not ready");
42370
42508
  return;
@@ -42517,7 +42655,7 @@ var BottlenecksContent = ({
42517
42655
  } finally {
42518
42656
  setIsCategoryLoading(false);
42519
42657
  }
42520
- }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, supabase]);
42658
+ }, [workspaceId, effectiveDateString, effectiveShiftId, getMetadataCacheKey, isPercentileCategory, isFastSlowClipFiltersEnabled, metadataCache, s3ClipsService, clearLoadingState, isEffectiveShiftReady, snapshotDateTime, snapshotClipId, supabase]);
42521
42659
  React142.useEffect(() => {
42522
42660
  if (previousFilterRef.current !== activeFilter) {
42523
42661
  console.log(`Filter changed from ${previousFilterRef.current} to ${activeFilter} - resetting to first video`);
@@ -42568,8 +42706,12 @@ var BottlenecksContent = ({
42568
42706
  const loadAndPlayClipById = React142.useCallback(async (clipId, categoryId, position, metadataContext) => {
42569
42707
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
42570
42708
  console.log(`[BottlenecksContent] Loading clip by ID: ${clipId}, category=${categoryId}, position=${position}`);
42709
+ awaitingNextClipRef.current = false;
42710
+ navigationLockRef.current = true;
42711
+ clearRetryTimeout();
42571
42712
  setIsTransitioning(true);
42572
42713
  setIsInitialLoading(true);
42714
+ setIsNavigating(true);
42573
42715
  setError(null);
42574
42716
  if (videoRef.current?.player) {
42575
42717
  try {
@@ -42667,7 +42809,67 @@ var BottlenecksContent = ({
42667
42809
  clearLoadingState();
42668
42810
  }
42669
42811
  }
42670
- }, [workspaceId, s3ClipsService, updateActiveFilter, clearLoadingState, loadCategoryMetadata, applyMetadataSnapshot, mergedCounts, isPercentileCategory]);
42812
+ }, [workspaceId, s3ClipsService, updateActiveFilter, clearLoadingState, clearRetryTimeout, loadCategoryMetadata, applyMetadataSnapshot, mergedCounts, isPercentileCategory]);
42813
+ const restartCurrentClipPlayback = React142.useCallback(() => {
42814
+ if (!currentClipId) {
42815
+ return;
42816
+ }
42817
+ console.log(`[BottlenecksContent] Restarting playback for clip ${currentClipId}`);
42818
+ awaitingNextClipRef.current = false;
42819
+ navigationLockRef.current = true;
42820
+ clearRetryTimeout();
42821
+ setError(null);
42822
+ setIsTransitioning(true);
42823
+ setIsInitialLoading(true);
42824
+ setIsNavigating(true);
42825
+ setIsVideoBuffering(false);
42826
+ bumpPlayerInstanceNonce();
42827
+ }, [currentClipId, clearRetryTimeout, bumpPlayerInstanceNonce]);
42828
+ React142.useEffect(() => {
42829
+ if (!newClipsNotification || !awaitingNextClipRef.current || !s3ClipsService) {
42830
+ return;
42831
+ }
42832
+ const activeCategory = activeFilterRef.current;
42833
+ if (!activeCategory || activeCategory === "all" || isPercentileCategory(activeCategory)) {
42834
+ return;
42835
+ }
42836
+ let cancelled = false;
42837
+ const timer = setTimeout(() => {
42838
+ void (async () => {
42839
+ const incomingClips = [...newClipsNotification.clips].reverse();
42840
+ for (const incomingClip of incomingClips) {
42841
+ if (cancelled || !incomingClip?.id || incomingClip.id === currentClipId) {
42842
+ continue;
42843
+ }
42844
+ const incomingVideo = await s3ClipsService.getClipById(incomingClip.id);
42845
+ if (cancelled || !incomingVideo || incomingVideo.type !== activeCategory) {
42846
+ continue;
42847
+ }
42848
+ console.log(`[BottlenecksContent] Autoplaying incoming ${activeCategory} clip ${incomingClip.id}`);
42849
+ invalidateMetadataCache(activeCategory);
42850
+ await loadAndPlayClipById(incomingClip.id, activeCategory, 1, {
42851
+ clips: [{ clipId: incomingClip.id }],
42852
+ total: Math.max(mergedCounts[activeCategory] || 0, 1)
42853
+ });
42854
+ void loadCategoryMetadata(activeCategory, false, true);
42855
+ return;
42856
+ }
42857
+ })();
42858
+ }, 300);
42859
+ return () => {
42860
+ cancelled = true;
42861
+ clearTimeout(timer);
42862
+ };
42863
+ }, [
42864
+ newClipsNotification,
42865
+ s3ClipsService,
42866
+ isPercentileCategory,
42867
+ currentClipId,
42868
+ invalidateMetadataCache,
42869
+ loadAndPlayClipById,
42870
+ loadCategoryMetadata,
42871
+ mergedCounts
42872
+ ]);
42671
42873
  React142.useCallback(async (categoryId, clipIndex) => {
42672
42874
  console.warn("[BottlenecksContent] loadAndPlayClip is deprecated, use loadAndPlayClipById instead");
42673
42875
  if (!workspaceId || !s3ClipsService || !isMountedRef.current || !isEffectiveShiftReady) return;
@@ -42696,8 +42898,11 @@ var BottlenecksContent = ({
42696
42898
  }
42697
42899
  }, [workspaceId, s3ClipsService, effectiveDateString, effectiveShiftId, loadAndPlayClipById, isEffectiveShiftReady]);
42698
42900
  const handleNext = React142.useCallback(async () => {
42699
- if (!isMountedRef.current) return;
42901
+ if (!isMountedRef.current || navigationLockRef.current) return;
42700
42902
  const currentFilter = activeFilterRef.current;
42903
+ navigationLockRef.current = true;
42904
+ clearRetryTimeout();
42905
+ setIsNavigating(true);
42701
42906
  setIsTransitioning(true);
42702
42907
  setIsInitialLoading(true);
42703
42908
  setError(null);
@@ -42727,10 +42932,32 @@ var BottlenecksContent = ({
42727
42932
  );
42728
42933
  const olderClip = neighbors.previous;
42729
42934
  if (!olderClip) {
42730
- console.log("[handleNext] Already at last clip in category");
42731
- clearLoadingState();
42935
+ let metadataArray3 = categoryMetadataRef.current;
42936
+ if (metadataArray3.length === 0) {
42937
+ console.log(`[handleNext] Metadata empty for ${currentFilter}, loading before loop restart`);
42938
+ await loadCategoryMetadata(currentFilter, false, true);
42939
+ metadataArray3 = categoryMetadataRef.current;
42940
+ }
42941
+ const firstClipMeta = metadataArray3[0];
42942
+ if (firstClipMeta?.clipId && firstClipMeta.clipId !== currentClipId) {
42943
+ console.log(`[handleNext] Reached end of ${currentFilter}, looping back to newest clip ${firstClipMeta.clipId}`);
42944
+ await loadAndPlayClipById(firstClipMeta.clipId, currentFilter, 1, {
42945
+ clips: metadataArray3,
42946
+ total: mergedCounts[currentFilter] || metadataArray3.length
42947
+ });
42948
+ return;
42949
+ }
42950
+ console.log("[handleNext] Reached live edge, waiting for the next clip");
42951
+ awaitingNextClipRef.current = true;
42952
+ navigationLockRef.current = false;
42953
+ setIsInitialLoading(false);
42954
+ setIsVideoBuffering(false);
42955
+ setIsPlaying(false);
42956
+ setIsNavigating(false);
42957
+ setIsTransitioning(false);
42732
42958
  return;
42733
42959
  }
42960
+ awaitingNextClipRef.current = false;
42734
42961
  setPendingVideo(olderClip);
42735
42962
  setCurrentClipId(olderClip.id || null);
42736
42963
  setAllVideos([olderClip]);
@@ -42786,8 +43013,23 @@ var BottlenecksContent = ({
42786
43013
  clearLoadingState();
42787
43014
  }
42788
43015
  } else {
42789
- console.log(`[handleNext] Already at last clip in category`);
42790
- clearLoadingState();
43016
+ const firstClipMeta = metadataArray[0];
43017
+ if (firstClipMeta?.clipId && firstClipMeta.clipId !== currentClipId) {
43018
+ console.log(`[handleNext] Reached end of ${currentFilter}, looping back to newest percentile clip ${firstClipMeta.clipId}`);
43019
+ await loadAndPlayClipById(firstClipMeta.clipId, currentFilter, 1, {
43020
+ clips: metadataArray,
43021
+ total: metadataArray.length
43022
+ });
43023
+ return;
43024
+ }
43025
+ console.log(`[handleNext] Reached live edge for ${currentFilter}, waiting for the next clip`);
43026
+ awaitingNextClipRef.current = true;
43027
+ navigationLockRef.current = false;
43028
+ setIsInitialLoading(false);
43029
+ setIsVideoBuffering(false);
43030
+ setIsPlaying(false);
43031
+ setIsNavigating(false);
43032
+ setIsTransitioning(false);
42791
43033
  }
42792
43034
  } catch (error2) {
42793
43035
  console.error(`[handleNext] Error navigating:`, error2);
@@ -42799,10 +43041,13 @@ var BottlenecksContent = ({
42799
43041
  });
42800
43042
  clearLoadingState();
42801
43043
  }
42802
- }, [clearLoadingState, s3ClipsService, loadCategoryMetadata, isPercentileCategory, workspaceId, currentClipId, effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId, mergedCounts]);
43044
+ }, [clearLoadingState, clearRetryTimeout, s3ClipsService, loadCategoryMetadata, loadAndPlayClipById, isPercentileCategory, workspaceId, currentClipId, effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId, mergedCounts]);
42803
43045
  const handlePrevious = React142.useCallback(async () => {
42804
- if (!isMountedRef.current) return;
43046
+ if (!isMountedRef.current || navigationLockRef.current) return;
42805
43047
  const currentFilter = activeFilterRef.current;
43048
+ navigationLockRef.current = true;
43049
+ clearRetryTimeout();
43050
+ setIsNavigating(true);
42806
43051
  setIsTransitioning(true);
42807
43052
  setIsInitialLoading(true);
42808
43053
  setError(null);
@@ -42900,7 +43145,7 @@ var BottlenecksContent = ({
42900
43145
  });
42901
43146
  clearLoadingState();
42902
43147
  }
42903
- }, [clearLoadingState, s3ClipsService, loadCategoryMetadata, isPercentileCategory, workspaceId, currentClipId, effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId, mergedCounts]);
43148
+ }, [clearLoadingState, clearRetryTimeout, s3ClipsService, loadCategoryMetadata, isPercentileCategory, workspaceId, currentClipId, effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId, mergedCounts]);
42904
43149
  const currentVideo = React142.useMemo(() => {
42905
43150
  if (!filteredVideos || filteredVideos.length === 0 || currentIndex >= filteredVideos.length) {
42906
43151
  return null;
@@ -43067,6 +43312,8 @@ var BottlenecksContent = ({
43067
43312
  }
43068
43313
  }, [error, playbackSpeed]);
43069
43314
  const handleVideoPlay = React142.useCallback(async (player) => {
43315
+ awaitingNextClipRef.current = false;
43316
+ clearRetryTimeout();
43070
43317
  setIsPlaying(true);
43071
43318
  setIsInitialLoading(false);
43072
43319
  if (currentVideo && !currentVideo.creation_timestamp && s3ClipsService) {
@@ -43088,7 +43335,7 @@ var BottlenecksContent = ({
43088
43335
  console.warn("[BottlenecksContent] Failed to load metadata for current video:", error2);
43089
43336
  }
43090
43337
  }
43091
- }, [currentVideo, s3ClipsService]);
43338
+ }, [currentVideo, s3ClipsService, clearRetryTimeout]);
43092
43339
  const handleVideoPause = React142.useCallback((player) => {
43093
43340
  setIsPlaying(false);
43094
43341
  }, []);
@@ -43099,8 +43346,10 @@ var BottlenecksContent = ({
43099
43346
  setDuration(duration2);
43100
43347
  }, []);
43101
43348
  const handleLoadedData = React142.useCallback((player) => {
43102
- console.log("Video data loaded - NOT clearing loading (wait for playing event)");
43103
- }, []);
43349
+ console.log("Video data loaded - clearing transition overlay");
43350
+ setIsInitialLoading(false);
43351
+ clearLoadingState();
43352
+ }, [clearLoadingState]);
43104
43353
  const handleVideoPlaying = React142.useCallback((player) => {
43105
43354
  console.log("Video playing - hiding transition overlay (most reliable)");
43106
43355
  clearLoadingState();
@@ -43121,6 +43370,7 @@ var BottlenecksContent = ({
43121
43370
  console.error("[BottlenecksContent] Video.js error:", errorInfo);
43122
43371
  setIsPlaying(false);
43123
43372
  setIsVideoBuffering(false);
43373
+ clearRetryTimeout();
43124
43374
  const errorCode = errorInfo?.code || 0;
43125
43375
  const canRetry = errorInfo?.canRetry ?? false;
43126
43376
  const errorMessage = errorInfo?.message || "Unknown error";
@@ -43148,17 +43398,24 @@ var BottlenecksContent = ({
43148
43398
  if (videoRetryCountRef.current < 3 && currentVideo) {
43149
43399
  videoRetryCountRef.current++;
43150
43400
  const retryDelay = 1e3 * videoRetryCountRef.current;
43401
+ const retryClipId = currentVideo.id;
43402
+ const retryCategory = activeFilterRef.current;
43151
43403
  console.log(`[Video Error] Recoverable error - Retrying... Attempt ${videoRetryCountRef.current}/3 in ${retryDelay}ms`);
43152
43404
  setError({
43153
43405
  type: "retrying",
43154
43406
  message: `Retrying... (${videoRetryCountRef.current}/3)`,
43155
43407
  isRetrying: true
43156
43408
  });
43157
- setTimeout(() => {
43158
- if (videoRef.current && currentVideo && isMountedRef.current) {
43159
- setError(null);
43160
- videoRef.current.dispose();
43409
+ retryTimeoutRef.current = setTimeout(() => {
43410
+ retryTimeoutRef.current = null;
43411
+ if (!isMountedRef.current) {
43412
+ return;
43161
43413
  }
43414
+ if (currentClipIdRef.current !== retryClipId || activeFilterRef.current !== retryCategory) {
43415
+ console.log("[Video Error] Skipping stale retry for previous clip");
43416
+ return;
43417
+ }
43418
+ restartCurrentClipPlayback();
43162
43419
  }, retryDelay);
43163
43420
  } else {
43164
43421
  console.log("[Video Error] Retries exhausted - showing final error overlay");
@@ -43180,7 +43437,7 @@ var BottlenecksContent = ({
43180
43437
  attempts: 3
43181
43438
  });
43182
43439
  }
43183
- }, [currentVideo, workspaceId, clearLoadingState]);
43440
+ }, [currentVideo, workspaceId, clearLoadingState, clearRetryTimeout, restartCurrentClipPlayback]);
43184
43441
  React142.useEffect(() => {
43185
43442
  isMountedRef.current = true;
43186
43443
  return () => {
@@ -43192,10 +43449,11 @@ var BottlenecksContent = ({
43192
43449
  clearTimeout(loadingTimeoutRef.current);
43193
43450
  loadingTimeoutRef.current = null;
43194
43451
  }
43452
+ clearRetryTimeout();
43195
43453
  setIsCategoryLoading(false);
43196
43454
  setIsNavigating(false);
43197
43455
  };
43198
- }, [s3ClipsService]);
43456
+ }, [s3ClipsService, clearRetryTimeout]);
43199
43457
  React142.useEffect(() => {
43200
43458
  if (filteredVideos.length > 0 && currentIndex < filteredVideos.length) {
43201
43459
  if (error && error.type === "fatal") {
@@ -43411,7 +43669,8 @@ var BottlenecksContent = ({
43411
43669
  isShareLoading,
43412
43670
  isShareCopied,
43413
43671
  options: videoPlayerOptions
43414
- }
43672
+ },
43673
+ `${currentVideo.id}-${playerInstanceNonce}-inline`
43415
43674
  )
43416
43675
  }
43417
43676
  ),
@@ -43442,11 +43701,8 @@ var BottlenecksContent = ({
43442
43701
  "button",
43443
43702
  {
43444
43703
  onClick: () => {
43445
- setError(null);
43446
43704
  videoRetryCountRef.current = 0;
43447
- if (videoRef.current) {
43448
- videoRef.current.dispose();
43449
- }
43705
+ restartCurrentClipPlayback();
43450
43706
  },
43451
43707
  className: "px-5 py-2.5 bg-gray-600 hover:bg-gray-700 rounded-md text-sm font-medium transition-colors",
43452
43708
  children: "Retry"
@@ -43678,7 +43934,7 @@ var BottlenecksContent = ({
43678
43934
  currentVideoId: currentVideo?.id,
43679
43935
  counts: mergedCounts,
43680
43936
  isReady: hasInitialLoad,
43681
- prefetchedPercentileCounts: prefetchedPercentileCounts || void 0,
43937
+ prefetchedPercentileCounts: isFastSlowClipFiltersEnabled ? prefetchedPercentileCounts || void 0 : void 0,
43682
43938
  workspaceId,
43683
43939
  date: effectiveDateString,
43684
43940
  shift: effectiveShiftId,
@@ -43687,6 +43943,7 @@ var BottlenecksContent = ({
43687
43943
  targetCycleTime: workspaceTargetCycleTime,
43688
43944
  clipClassifications,
43689
43945
  idleTimeVlmEnabled,
43946
+ showPercentileCycleFilters: isFastSlowClipFiltersEnabled,
43690
43947
  onFilterChange: (filterId) => {
43691
43948
  updateActiveFilter(filterId);
43692
43949
  const category = categoriesToShow.find((cat) => cat.type === filterId);
@@ -43835,7 +44092,8 @@ var BottlenecksContent = ({
43835
44092
  isShareLoading,
43836
44093
  isShareCopied,
43837
44094
  options: videoPlayerOptions
43838
- }
44095
+ },
44096
+ `${currentVideo.id}-${playerInstanceNonce}-fullscreen`
43839
44097
  )
43840
44098
  }
43841
44099
  ),
@@ -43899,6 +44157,7 @@ var BottlenecksContent = ({
43899
44157
  !triageMode && /* @__PURE__ */ jsxRuntime.jsx(
43900
44158
  AdvancedFilterDialog,
43901
44159
  {
44160
+ showPercentileCycleFilters: isFastSlowClipFiltersEnabled,
43902
44161
  onApply: () => {
43903
44162
  console.log("[BottlenecksContent] Advanced filters applied, will refresh clips...");
43904
44163
  }
@@ -50387,29 +50646,29 @@ var Legend5 = ({
50387
50646
  }) => {
50388
50647
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
50389
50648
  const exclamationLabel = useBottleneckLabel ? "Bottleneck" : "<50% efficiency";
50390
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-1.5 sm:gap-3 bg-white/95 rounded-lg shadow-sm px-2 sm:px-4 py-1 sm:py-1.5 border border-gray-200/60 backdrop-blur-sm text-[10px] sm:text-sm", children: [
50649
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 sm:gap-4 text-xs font-medium text-slate-600", children: [
50391
50650
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "font-medium text-gray-700 hidden sm:block", children: [
50392
50651
  metricLabel,
50393
50652
  ":"
50394
50653
  ] }),
50395
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 sm:gap-4", children: [
50396
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
50397
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 sm:w-2.5 sm:h-2.5 rounded-[12px] bg-[#00AB45]/90 ring-1 ring-[#00AB45]/20" }),
50398
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-600", children: formatPercentRange(effectiveLegend.green_min, effectiveLegend.green_max) })
50654
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 sm:gap-4", children: [
50655
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
50656
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full bg-[#00AB45]" }),
50657
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatPercentRange(effectiveLegend.green_min, effectiveLegend.green_max) })
50399
50658
  ] }),
50400
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
50401
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 sm:w-2.5 sm:h-2.5 rounded-[12px] bg-[#FFB020]/90 ring-1 ring-[#FFB020]/20" }),
50402
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-600", children: formatPercentRange(effectiveLegend.yellow_min, effectiveLegend.yellow_max) })
50659
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
50660
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full bg-[#FFB020]" }),
50661
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatPercentRange(effectiveLegend.yellow_min, effectiveLegend.yellow_max) })
50403
50662
  ] }),
50404
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
50405
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 sm:w-2.5 sm:h-2.5 rounded-[12px] bg-[#E34329]/90 ring-1 ring-[#E34329]/20" }),
50406
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-600", children: formatPercentRange(effectiveLegend.red_min, effectiveLegend.red_max) })
50663
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
50664
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 sm:w-2.5 sm:h-2.5 rounded-full bg-[#E34329]" }),
50665
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatPercentRange(effectiveLegend.red_min, effectiveLegend.red_max) })
50407
50666
  ] })
50408
50667
  ] }),
50409
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-6 bg-gray-200 mx-1" }),
50410
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
50411
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center w-3 h-3 sm:w-5 sm:h-5 bg-[#E34329]/90 rounded-full ring-1 ring-[#E34329]/20 text-white font-bold text-[8px] sm:text-xs", children: "!" }),
50412
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-600", children: exclamationLabel })
50668
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block w-px h-4 bg-slate-200 mx-1" }),
50669
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
50670
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center w-3 h-3 sm:w-4 sm:h-4 bg-[#E34329] rounded-full text-white font-bold text-[8px] sm:text-[10px]", children: "!" }),
50671
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: exclamationLabel })
50413
50672
  ] })
50414
50673
  ] });
50415
50674
  };
@@ -50519,6 +50778,7 @@ var WorkspaceGrid = React142__namespace.default.memo(({
50519
50778
  isPdfMode = false,
50520
50779
  customWorkspacePositions,
50521
50780
  lineNames = {},
50781
+ lineOrder = [],
50522
50782
  factoryView = "factory",
50523
50783
  line2Uuid = "line-2",
50524
50784
  className = "",
@@ -50529,7 +50789,8 @@ var WorkspaceGrid = React142__namespace.default.memo(({
50529
50789
  videoStreamsLoading = false,
50530
50790
  displayNames = {},
50531
50791
  onWorkspaceHover,
50532
- onWorkspaceHoverEnd
50792
+ onWorkspaceHoverEnd,
50793
+ toolbarRightContent
50533
50794
  }) => {
50534
50795
  const dashboardConfig = useDashboardConfig();
50535
50796
  const mapViewEnabled = dashboardConfig?.mapViewConfig?.enabled ?? false;
@@ -50563,29 +50824,30 @@ var WorkspaceGrid = React142__namespace.default.memo(({
50563
50824
  () => viewMode === "video" ? getVideoGridLegendLabel(workspaces) : MAP_GRID_LEGEND_LABEL,
50564
50825
  [viewMode, workspaces]
50565
50826
  );
50566
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative w-full h-full overflow-hidden ${className}`, children: [
50567
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-0 left-2 sm:left-4 right-2 sm:right-8 z-20", children: [
50568
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-row items-center justify-between py-1 sm:py-1.5 gap-2", children: [
50569
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) }),
50827
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col w-full h-full overflow-hidden bg-slate-50/50 ${className}`, children: [
50828
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-none px-4 py-3 z-20 flex flex-row items-center justify-between gap-4", children: [
50829
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex bg-white/95 rounded-lg shadow-sm px-4 py-2 border border-slate-200/60 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) }) }),
50830
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-3 shrink-0", children: [
50831
+ toolbarRightContent,
50570
50832
  mapViewEnabled && /* @__PURE__ */ jsxRuntime.jsx(
50571
50833
  "button",
50572
50834
  {
50573
50835
  onClick: handleViewModeToggle,
50574
- className: "flex items-center gap-2 px-3 py-1.5 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 transition-colors duration-200",
50836
+ className: "flex items-center gap-2 px-3 py-1.5 bg-white border border-slate-200 rounded-md shadow-sm hover:bg-slate-50 transition-colors duration-200 text-slate-700",
50575
50837
  title: viewMode === "video" ? "Switch to Map View" : "Switch to Video View",
50576
50838
  children: viewMode === "video" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
50577
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Map, { className: "w-4 h-4 text-gray-600" }),
50578
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline text-sm text-gray-700", children: "Map View" })
50839
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Map, { className: "w-4 h-4 text-slate-500" }),
50840
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline text-sm font-medium", children: "Map View" })
50579
50841
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
50580
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { className: "w-4 h-4 text-gray-600" }),
50581
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline text-sm text-gray-700", children: "Video View" })
50842
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { className: "w-4 h-4 text-slate-500" }),
50843
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline text-sm font-medium", children: "Video View" })
50582
50844
  ] })
50583
50845
  }
50584
50846
  )
50585
- ] }),
50586
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden mt-1 mr-32", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) })
50847
+ ] })
50587
50848
  ] }),
50588
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-14 sm:top-16 left-0 right-0 bottom-0", children: /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { mode: "wait", children: viewMode === "video" ? /* @__PURE__ */ jsxRuntime.jsx(
50849
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden px-3 py-2 bg-white border-b border-slate-200/60 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(Legend5, { legend, useBottleneckLabel: hasFlowBuffers, metricLabel: legendMetricLabel }) }),
50850
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 relative overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { mode: "wait", children: viewMode === "video" ? /* @__PURE__ */ jsxRuntime.jsx(
50589
50851
  motion.div,
50590
50852
  {
50591
50853
  initial: { opacity: 0 },
@@ -50597,6 +50859,8 @@ var WorkspaceGrid = React142__namespace.default.memo(({
50597
50859
  VideoGridViewComponent,
50598
50860
  {
50599
50861
  workspaces,
50862
+ lineNames,
50863
+ lineOrder,
50600
50864
  videoSources,
50601
50865
  videoStreamsByWorkspaceId,
50602
50866
  videoStreamsLoading,
@@ -59459,6 +59723,7 @@ function HomeView({
59459
59723
  [dbLines]
59460
59724
  );
59461
59725
  const isSupervisor = user?.role_level === "supervisor";
59726
+ const hasUser = Boolean(user);
59462
59727
  const visibleLineIds = React142.useMemo(() => {
59463
59728
  const scoped = Array.from(new Set(allLineIds.filter(Boolean)));
59464
59729
  if (enabledLineIdSet.size === 0) {
@@ -59468,57 +59733,100 @@ function HomeView({
59468
59733
  }, [allLineIds, enabledLineIdSet]);
59469
59734
  const fallbackLineId = visibleLineIds[0] || defaultLineId;
59470
59735
  const defaultHomeLineId = fallbackLineId;
59471
- const availableLineIds = React142.useMemo(() => {
59472
- if (visibleLineIds.length > 1) {
59473
- return [...visibleLineIds, factoryViewId];
59474
- }
59475
- return visibleLineIds;
59476
- }, [visibleLineIds, factoryViewId]);
59477
- const LINE_FILTER_STORAGE_KEY = "optifye_home_line_filter";
59478
- const [selectedLineId, setSelectedLineId] = React142.useState(() => {
59736
+ const visibleLineIdsKey = visibleLineIds.join(",");
59737
+ const normalizeSelectedLineIds = (lineIdsToNormalize) => {
59738
+ const allowedLineIds = new Set(visibleLineIds);
59739
+ const requestedLineIds = new Set((lineIdsToNormalize || []).filter(Boolean));
59740
+ const normalized = visibleLineIds.filter((lineId) => requestedLineIds.has(lineId) && allowedLineIds.has(lineId));
59741
+ if (normalized.length > 0) {
59742
+ return normalized;
59743
+ }
59744
+ if (defaultHomeLineId && allowedLineIds.has(defaultHomeLineId)) {
59745
+ return [defaultHomeLineId];
59746
+ }
59747
+ return visibleLineIds.length > 0 ? [visibleLineIds[0]] : [];
59748
+ };
59749
+ const selectedLineIdsEqual = (left, right) => {
59750
+ if (left.length !== right.length) return false;
59751
+ return left.every((value, index) => value === right[index]);
59752
+ };
59753
+ const isAllLinesSelection = (lineIdsToCheck) => visibleLineIds.length > 0 && lineIdsToCheck.length === visibleLineIds.length;
59754
+ const readPersistedSelectedLineIds = () => {
59479
59755
  if (typeof window === "undefined") {
59480
- return defaultHomeLineId;
59756
+ return null;
59481
59757
  }
59482
59758
  try {
59483
- const savedLineId = sessionStorage.getItem(LINE_FILTER_STORAGE_KEY);
59484
- if (savedLineId) {
59485
- if (availableLineIds.includes(savedLineId)) {
59486
- return savedLineId;
59759
+ const savedLineIds = sessionStorage.getItem("optifye_home_line_filters_v2");
59760
+ if (savedLineIds) {
59761
+ const parsedLineIds = JSON.parse(savedLineIds);
59762
+ if (Array.isArray(parsedLineIds)) {
59763
+ return normalizeSelectedLineIds(parsedLineIds.map((lineId) => String(lineId)));
59764
+ }
59765
+ }
59766
+ const legacyLineId = sessionStorage.getItem("optifye_home_line_filter");
59767
+ if (legacyLineId) {
59768
+ if (legacyLineId === factoryViewId) {
59769
+ return normalizeSelectedLineIds(visibleLineIds);
59487
59770
  }
59771
+ return normalizeSelectedLineIds([legacyLineId]);
59488
59772
  }
59489
59773
  } catch (error) {
59490
59774
  console.warn("Failed to read line filter from sessionStorage:", error);
59491
59775
  }
59492
- return defaultHomeLineId;
59493
- });
59776
+ return null;
59777
+ };
59778
+ const [selectedLineIds, setSelectedLineIds] = React142.useState(() => readPersistedSelectedLineIds() || normalizeSelectedLineIds([defaultHomeLineId]));
59779
+ const [isLineSelectorOpen, setIsLineSelectorOpen] = React142.useState(false);
59780
+ const [pendingSelectedLineIds, setPendingSelectedLineIds] = React142.useState([]);
59781
+ const lineSelectorRef = React142.useRef(null);
59782
+ React142.useEffect(() => {
59783
+ if (isLineSelectorOpen) {
59784
+ setPendingSelectedLineIds(selectedLineIds);
59785
+ }
59786
+ }, [isLineSelectorOpen, selectedLineIds]);
59494
59787
  React142.useEffect(() => {
59495
- if (!user || availableLineIds.length === 0) {
59788
+ if (!hasUser || visibleLineIds.length === 0) {
59496
59789
  return;
59497
59790
  }
59498
- try {
59499
- const savedLineId = sessionStorage.getItem(LINE_FILTER_STORAGE_KEY);
59500
- if (savedLineId && availableLineIds.includes(savedLineId)) {
59501
- if (savedLineId !== selectedLineId) {
59502
- setSelectedLineId(savedLineId);
59503
- }
59504
- return;
59791
+ const restoredLineIds = readPersistedSelectedLineIds();
59792
+ setSelectedLineIds((previousSelectedLineIds) => {
59793
+ if (restoredLineIds) {
59794
+ return selectedLineIdsEqual(restoredLineIds, previousSelectedLineIds) ? previousSelectedLineIds : restoredLineIds;
59505
59795
  }
59796
+ const normalizedCurrentLineIds = normalizeSelectedLineIds(previousSelectedLineIds);
59797
+ return selectedLineIdsEqual(normalizedCurrentLineIds, previousSelectedLineIds) ? previousSelectedLineIds : normalizedCurrentLineIds;
59798
+ });
59799
+ }, [hasUser, visibleLineIdsKey, defaultHomeLineId, factoryViewId]);
59800
+ React142.useEffect(() => {
59801
+ try {
59802
+ sessionStorage.setItem("optifye_home_line_filters_v2", JSON.stringify(selectedLineIds));
59803
+ sessionStorage.removeItem("optifye_home_line_filter");
59506
59804
  } catch (error) {
59507
- console.warn("Failed to read line filter from sessionStorage:", error);
59805
+ console.warn("Failed to save line filter to sessionStorage:", error);
59508
59806
  }
59509
- if (availableLineIds.includes(selectedLineId)) {
59807
+ }, [selectedLineIds]);
59808
+ React142.useEffect(() => {
59809
+ if (!isLineSelectorOpen) {
59510
59810
  return;
59511
59811
  }
59512
- if (defaultHomeLineId !== selectedLineId) {
59513
- setSelectedLineId(defaultHomeLineId);
59514
- }
59515
- }, [
59516
- user,
59517
- availableLineIds,
59518
- defaultHomeLineId,
59519
- selectedLineId,
59520
- LINE_FILTER_STORAGE_KEY
59521
- ]);
59812
+ const handleClickOutside = (event) => {
59813
+ if (lineSelectorRef.current && !lineSelectorRef.current.contains(event.target)) {
59814
+ setIsLineSelectorOpen(false);
59815
+ }
59816
+ };
59817
+ document.addEventListener("mousedown", handleClickOutside);
59818
+ return () => {
59819
+ document.removeEventListener("mousedown", handleClickOutside);
59820
+ };
59821
+ }, [isLineSelectorOpen]);
59822
+ const primarySelectedLineId = selectedLineIds[0] || defaultHomeLineId;
59823
+ const isMultiLineSelection = selectedLineIds.length > 1;
59824
+ const selectedLineIdsKey = selectedLineIds.join(",");
59825
+ const selectedLineIdSet = React142.useMemo(
59826
+ () => new Set(selectedLineIds),
59827
+ [selectedLineIds]
59828
+ );
59829
+ const metricsScopeLineId = isMultiLineSelection ? factoryViewId : primarySelectedLineId;
59522
59830
  const userCompanyId = React142.useMemo(() => {
59523
59831
  return user?.properties?.company_id || user?.company_id || entityConfig.companyId;
59524
59832
  }, [user, entityConfig.companyId]);
@@ -59538,12 +59846,8 @@ function HomeView({
59538
59846
  React142.useEffect(() => {
59539
59847
  const initDisplayNames = async () => {
59540
59848
  try {
59541
- if (selectedLineId === factoryViewId) {
59542
- for (const lineId of visibleLineIds) {
59543
- await preInitializeWorkspaceDisplayNames(lineId);
59544
- }
59545
- } else {
59546
- await preInitializeWorkspaceDisplayNames(selectedLineId);
59849
+ for (const lineId of selectedLineIds) {
59850
+ await preInitializeWorkspaceDisplayNames(lineId);
59547
59851
  }
59548
59852
  setDisplayNamesInitialized(true);
59549
59853
  } catch (error) {
@@ -59552,8 +59856,8 @@ function HomeView({
59552
59856
  }
59553
59857
  };
59554
59858
  initDisplayNames();
59555
- }, [selectedLineId, factoryViewId, visibleLineIds]);
59556
- const displayNameLineId = selectedLineId === factoryViewId ? void 0 : selectedLineId;
59859
+ }, [selectedLineIdsKey]);
59860
+ const displayNameLineId = isMultiLineSelection ? void 0 : primarySelectedLineId;
59557
59861
  const {
59558
59862
  displayNames: workspaceDisplayNames,
59559
59863
  loading: displayNamesLoading,
@@ -59582,7 +59886,8 @@ function HomeView({
59582
59886
  error: metricsError,
59583
59887
  refetch: refetchMetrics
59584
59888
  } = useDashboardMetrics({
59585
- lineId: selectedLineId,
59889
+ lineId: metricsScopeLineId,
59890
+ lineIds: selectedLineIds,
59586
59891
  onLineMetricsUpdate: handleLineMetricsUpdate,
59587
59892
  userAccessibleLineIds: visibleLineIds
59588
59893
  // Pass user's accessible lines for supervisor filtering
@@ -59590,10 +59895,8 @@ function HomeView({
59590
59895
  const trendGroups = React142.useMemo(() => {
59591
59896
  const lineMetricsRows = lineMetrics || [];
59592
59897
  if (!lineMetricsRows.length) return null;
59593
- if (selectedLineId === factoryViewId) {
59594
- const candidateLineIds = visibleLineIds.filter((id3) => id3 && id3 !== factoryViewId);
59595
- if (!candidateLineIds.length) return null;
59596
- const rowsForLines = lineMetricsRows.filter((row2) => candidateLineIds.includes(row2?.line_id));
59898
+ if (selectedLineIds.length > 1) {
59899
+ const rowsForLines = lineMetricsRows.filter((row2) => selectedLineIdSet.has(row2?.line_id));
59597
59900
  if (!rowsForLines.length) return null;
59598
59901
  const groupsMap = /* @__PURE__ */ new Map();
59599
59902
  rowsForLines.forEach((row2) => {
@@ -59616,16 +59919,16 @@ function HomeView({
59616
59919
  shiftId: group.shiftId
59617
59920
  }));
59618
59921
  }
59619
- const row = lineMetricsRows.find((r2) => r2?.line_id === selectedLineId);
59922
+ const row = lineMetricsRows.find((r2) => r2?.line_id === primarySelectedLineId);
59620
59923
  if (!row?.date || row?.shift_id === void 0 || row?.shift_id === null) {
59621
59924
  return null;
59622
59925
  }
59623
59926
  return [{
59624
- lineIds: [selectedLineId],
59927
+ lineIds: [primarySelectedLineId],
59625
59928
  date: row.date,
59626
59929
  shiftId: row.shift_id
59627
59930
  }];
59628
- }, [selectedLineId, factoryViewId, visibleLineIds, lineMetrics]);
59931
+ }, [lineMetrics, primarySelectedLineId, selectedLineIdSet, selectedLineIds.length]);
59629
59932
  const trendOptions = React142.useMemo(() => {
59630
59933
  if (!trendGroups || !userCompanyId) return null;
59631
59934
  return {
@@ -59653,17 +59956,18 @@ function HomeView({
59653
59956
  }, [workspaceMetrics, metricsLoading, metricsError]);
59654
59957
  const kpis = React142.useMemo(() => {
59655
59958
  const lineMetricsRows = lineMetrics || [];
59656
- if (selectedLineId === factoryViewId) {
59657
- if (metricsLoading && lineMetricsRows.length === 0) return null;
59658
- return aggregateKPIsFromLineMetricsRows(lineMetricsRows);
59959
+ if (selectedLineIds.length > 1) {
59960
+ const rowsForSelectedLines = lineMetricsRows.filter((row2) => selectedLineIdSet.has(row2?.line_id));
59961
+ if (metricsLoading && rowsForSelectedLines.length === 0) return null;
59962
+ return aggregateKPIsFromLineMetricsRows(rowsForSelectedLines);
59659
59963
  }
59660
- const row = lineMetricsRows.find((r2) => r2?.line_id === selectedLineId);
59964
+ const row = lineMetricsRows.find((r2) => r2?.line_id === primarySelectedLineId);
59661
59965
  if (!row) {
59662
59966
  if (metricsLoading) return null;
59663
59967
  return buildKPIsFromLineMetricsRow(null);
59664
59968
  }
59665
59969
  return buildKPIsFromLineMetricsRow(row);
59666
- }, [selectedLineId, factoryViewId, lineMetrics, metricsLoading]);
59970
+ }, [lineMetrics, metricsLoading, primarySelectedLineId, selectedLineIdSet, selectedLineIds.length]);
59667
59971
  const kpisWithTrend = React142.useMemo(() => {
59668
59972
  if (!kpis) return null;
59669
59973
  if (!kpiTrend) return kpis;
@@ -59688,30 +59992,30 @@ function HomeView({
59688
59992
  };
59689
59993
  }, [kpis, kpiTrend]);
59690
59994
  const selectedLineMeta = React142.useMemo(
59691
- () => dbLines.find((line) => line.id === selectedLineId),
59692
- [dbLines, selectedLineId]
59995
+ () => selectedLineIds.length === 1 ? dbLines.find((line) => line.id === primarySelectedLineId) : void 0,
59996
+ [dbLines, primarySelectedLineId, selectedLineIds.length]
59693
59997
  );
59694
- const selectedMonitoringMode = selectedLineId === factoryViewId ? "output" : selectedLineMeta?.monitoring_mode ?? "output";
59998
+ const selectedMonitoringMode = selectedLineIds.length === 1 ? selectedLineMeta?.monitoring_mode ?? "output" : "output";
59695
59999
  const isUptimeMode = selectedMonitoringMode === "uptime";
59696
60000
  const averageIdleTimeSeconds = React142.useMemo(() => {
59697
60001
  if (!isUptimeMode) return null;
59698
- const targetWorkspaces = selectedLineId === factoryViewId ? workspaceMetrics : workspaceMetrics.filter((ws) => ws.line_id === selectedLineId);
60002
+ const targetWorkspaces = workspaceMetrics.filter((ws) => ws.line_id === primarySelectedLineId);
59699
60003
  const idleValues = targetWorkspaces.map((ws) => ws.idle_time).filter((value) => Number.isFinite(value));
59700
60004
  if (idleValues.length === 0) return 0;
59701
60005
  const totalIdle = idleValues.reduce((sum, value) => sum + value, 0);
59702
60006
  return totalIdle / idleValues.length;
59703
- }, [isUptimeMode, selectedLineId, factoryViewId, workspaceMetrics]);
60007
+ }, [isUptimeMode, primarySelectedLineId, workspaceMetrics]);
59704
60008
  const {
59705
60009
  activeBreaks: allActiveBreaks,
59706
60010
  isLoading: breaksLoading,
59707
60011
  error: breaksError
59708
- } = useActiveBreaks(allLineIds);
60012
+ } = useActiveBreaks(visibleLineIds);
59709
60013
  const activeBreaks = React142.useMemo(() => {
59710
- if (selectedLineId === factoryViewId) {
60014
+ if (isAllLinesSelection(selectedLineIds)) {
59711
60015
  return allActiveBreaks;
59712
60016
  }
59713
- return allActiveBreaks.filter((breakItem) => breakItem.lineId === selectedLineId);
59714
- }, [allActiveBreaks, selectedLineId, factoryViewId]);
60017
+ return allActiveBreaks.filter((breakItem) => selectedLineIdSet.has(breakItem.lineId));
60018
+ }, [allActiveBreaks, selectedLineIdSet, selectedLineIds]);
59715
60019
  const activeBreakLineIds = React142.useMemo(
59716
60020
  () => new Set(activeBreaks.map((breakItem) => breakItem.lineId)),
59717
60021
  [activeBreaks]
@@ -60018,7 +60322,7 @@ function HomeView({
60018
60322
  // Round to 1 decimal
60019
60323
  kpisWithTrend?.avgCycleTime?.change,
60020
60324
  kpisWithTrend?.qualityCompliance?.value ? Math.round(kpisWithTrend.qualityCompliance.value) : null,
60021
- selectedLineId
60325
+ selectedLineIdsKey
60022
60326
  ]);
60023
60327
  React142.useEffect(() => {
60024
60328
  setIsHydrated(true);
@@ -60035,27 +60339,62 @@ function HomeView({
60035
60339
  setErrorMessage(null);
60036
60340
  }
60037
60341
  }, [metricsError]);
60038
- const handleLineChange = React142.useCallback((value) => {
60342
+ const getTrackedLineScope = React142.useCallback((lineIdsForScope) => {
60343
+ if (isAllLinesSelection(lineIdsForScope)) {
60344
+ return factoryViewId;
60345
+ }
60346
+ if (lineIdsForScope.length === 1) {
60347
+ return lineIdsForScope[0];
60348
+ }
60349
+ return "custom_multi";
60350
+ }, [factoryViewId, visibleLineIds.length]);
60351
+ const getLineSelectionLabel = React142.useCallback((lineIdsForScope) => {
60352
+ if (isAllLinesSelection(lineIdsForScope)) {
60353
+ return "All Lines";
60354
+ }
60355
+ if (lineIdsForScope.length === 1) {
60356
+ const lineId = lineIdsForScope[0];
60357
+ return mergedLineNames[lineId] || `Line ${lineId.substring(0, 4)}`;
60358
+ }
60359
+ return `${lineIdsForScope.length} Lines Selected`;
60360
+ }, [mergedLineNames, visibleLineIds.length]);
60361
+ const updateSelectedLineIds = React142.useCallback((nextLineIds) => {
60362
+ const normalizedLineIds = normalizeSelectedLineIds(nextLineIds);
60363
+ if (selectedLineIdsEqual(normalizedLineIds, selectedLineIds)) {
60364
+ return;
60365
+ }
60039
60366
  setIsChangingFilter(true);
60040
- setSelectedLineId(value);
60367
+ setSelectedLineIds(normalizedLineIds);
60041
60368
  trackCoreEvent("monitor line filter changed", {
60042
- previous_line_id: selectedLineId,
60043
- new_line_id: value,
60044
- line_name: mergedLineNames[value] || (value === factoryViewId ? "All Lines" : `Line ${value.substring(0, 4)}`)
60369
+ previous_line_id: getTrackedLineScope(selectedLineIds),
60370
+ new_line_id: getTrackedLineScope(normalizedLineIds),
60371
+ previous_line_ids: selectedLineIds,
60372
+ new_line_ids: normalizedLineIds,
60373
+ selected_line_count: normalizedLineIds.length,
60374
+ selection_mode: isAllLinesSelection(normalizedLineIds) ? "all" : normalizedLineIds.length === 1 ? "single" : "custom",
60375
+ line_name: getLineSelectionLabel(normalizedLineIds)
60045
60376
  });
60046
- try {
60047
- sessionStorage.setItem(LINE_FILTER_STORAGE_KEY, value);
60048
- } catch (error) {
60049
- console.warn("Failed to save line filter to sessionStorage:", error);
60377
+ }, [factoryViewId, getLineSelectionLabel, getTrackedLineScope, selectedLineIds, selectedLineIdsKey, visibleLineIds]);
60378
+ React142.useCallback(() => {
60379
+ updateSelectedLineIds(visibleLineIds);
60380
+ }, [updateSelectedLineIds, visibleLineIds]);
60381
+ React142.useCallback((lineId) => {
60382
+ const currentSelection = new Set(selectedLineIds);
60383
+ if (currentSelection.has(lineId)) {
60384
+ if (currentSelection.size <= 1) {
60385
+ return;
60386
+ }
60387
+ currentSelection.delete(lineId);
60388
+ } else {
60389
+ currentSelection.add(lineId);
60050
60390
  }
60051
- }, [LINE_FILTER_STORAGE_KEY, selectedLineId, mergedLineNames, factoryViewId]);
60391
+ updateSelectedLineIds(Array.from(currentSelection));
60392
+ }, [selectedLineIds, updateSelectedLineIds]);
60052
60393
  React142.useEffect(() => {
60053
60394
  if (!metricsLoading && isChangingFilter) {
60054
- if (workspaceMetrics.length > 0 || selectedLineId === factoryViewId) {
60055
- setIsChangingFilter(false);
60056
- }
60395
+ setIsChangingFilter(false);
60057
60396
  }
60058
- }, [metricsLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
60397
+ }, [metricsLoading, isChangingFilter]);
60059
60398
  React142.useEffect(() => {
60060
60399
  if (!metricsLoading && !hasInitialDataLoaded) {
60061
60400
  setHasInitialDataLoaded(true);
@@ -60074,11 +60413,107 @@ function HomeView({
60074
60413
  if (visibleLineIds.length <= 1) {
60075
60414
  return null;
60076
60415
  }
60077
- return /* @__PURE__ */ jsxRuntime.jsxs(Select, { onValueChange: handleLineChange, value: selectedLineId, children: [
60078
- /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-full sm:w-[200px] bg-white border border-gray-200 shadow-sm rounded-md h-9 sm:h-9 text-xs sm:text-sm px-2 sm:px-3", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Select a line" }) }),
60079
- /* @__PURE__ */ jsxRuntime.jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: id3, children: mergedLineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
60416
+ const allLinesSelected = isAllLinesSelection(pendingSelectedLineIds);
60417
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: lineSelectorRef, className: "relative", children: [
60418
+ /* @__PURE__ */ jsxRuntime.jsxs(
60419
+ "button",
60420
+ {
60421
+ type: "button",
60422
+ onClick: () => setIsLineSelectorOpen((previous) => !previous),
60423
+ className: "flex min-w-[180px] items-center justify-between gap-2 rounded-md border border-slate-200 bg-white px-3 py-1.5 text-left text-sm font-medium shadow-sm transition-colors hover:bg-slate-50 text-slate-700",
60424
+ "aria-haspopup": "menu",
60425
+ "aria-expanded": isLineSelectorOpen,
60426
+ "aria-label": "Select lines",
60427
+ children: [
60428
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: getLineSelectionLabel(selectedLineIds) }),
60429
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `h-4 w-4 text-slate-400 transition-transform ${isLineSelectorOpen ? "rotate-180" : ""}` })
60430
+ ]
60431
+ }
60432
+ ),
60433
+ isLineSelectorOpen ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 top-full z-50 mt-2 w-[280px] rounded-lg border border-slate-200 bg-white p-3 shadow-xl flex flex-col gap-2", children: [
60434
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between pb-2 border-b border-slate-100", children: [
60435
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-slate-800", children: "Select Lines" }),
60436
+ /* @__PURE__ */ jsxRuntime.jsx(
60437
+ "button",
60438
+ {
60439
+ type: "button",
60440
+ onClick: () => setPendingSelectedLineIds([]),
60441
+ className: "text-xs font-medium text-blue-600 hover:text-blue-700 transition-colors",
60442
+ children: "Clear All"
60443
+ }
60444
+ )
60445
+ ] }),
60446
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 text-sm font-medium text-slate-900 hover:bg-slate-50 transition-colors", children: [
60447
+ /* @__PURE__ */ jsxRuntime.jsx(
60448
+ "input",
60449
+ {
60450
+ type: "checkbox",
60451
+ className: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500",
60452
+ checked: allLinesSelected,
60453
+ onChange: () => setPendingSelectedLineIds(allLinesSelected ? [] : visibleLineIds)
60454
+ }
60455
+ ),
60456
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "All Lines" })
60457
+ ] }),
60458
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-56 space-y-0.5 overflow-y-auto pr-1", children: visibleLineIds.map((lineId) => {
60459
+ const isChecked = pendingSelectedLineIds.includes(lineId);
60460
+ return /* @__PURE__ */ jsxRuntime.jsxs(
60461
+ "label",
60462
+ {
60463
+ className: "flex cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 text-sm transition-colors text-slate-700 hover:bg-slate-50",
60464
+ children: [
60465
+ /* @__PURE__ */ jsxRuntime.jsx(
60466
+ "input",
60467
+ {
60468
+ type: "checkbox",
60469
+ className: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500",
60470
+ checked: isChecked,
60471
+ onChange: () => {
60472
+ setPendingSelectedLineIds((prev) => {
60473
+ const current = new Set(prev);
60474
+ if (current.has(lineId)) {
60475
+ current.delete(lineId);
60476
+ } else {
60477
+ current.add(lineId);
60478
+ }
60479
+ return Array.from(current);
60480
+ });
60481
+ }
60482
+ }
60483
+ ),
60484
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: mergedLineNames[lineId] || `Line ${lineId.substring(0, 4)}` })
60485
+ ]
60486
+ },
60487
+ lineId
60488
+ );
60489
+ }) }),
60490
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-3 pb-1 mt-1 border-t border-slate-100 flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
60491
+ "button",
60492
+ {
60493
+ type: "button",
60494
+ onClick: () => {
60495
+ if (pendingSelectedLineIds.length > 0) {
60496
+ updateSelectedLineIds(pendingSelectedLineIds);
60497
+ }
60498
+ setIsLineSelectorOpen(false);
60499
+ },
60500
+ disabled: pendingSelectedLineIds.length === 0,
60501
+ className: "bg-blue-600 text-white px-4 py-1.5 rounded-md text-sm font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors w-full",
60502
+ children: "Apply"
60503
+ }
60504
+ ) })
60505
+ ] }) : null
60080
60506
  ] });
60081
- }, [availableLineIds, handleLineChange, selectedLineId, mergedLineNames, factoryViewId, visibleLineIds.length]);
60507
+ }, [
60508
+ getLineSelectionLabel,
60509
+ isLineSelectorOpen,
60510
+ mergedLineNames,
60511
+ selectedLineIds,
60512
+ pendingSelectedLineIds,
60513
+ visibleLineIds,
60514
+ updateSelectedLineIds,
60515
+ isAllLinesSelection
60516
+ ]);
60082
60517
  const useSmoothLoading = (isLoading, minDuration = 400) => {
60083
60518
  const [showLoading, setShowLoading] = React142.useState(isLoading);
60084
60519
  const loadingStartRef2 = React142.useRef(null);
@@ -60124,11 +60559,11 @@ function HomeView({
60124
60559
  const isDataLoading = metricsLoading || displayNamesLoading && workspaceMetrics.length === 0;
60125
60560
  const hasKpiDataReady = React142.useMemo(() => {
60126
60561
  const lineMetricsRows = lineMetrics || [];
60127
- if (selectedLineId === factoryViewId) {
60128
- return lineMetricsRows.length > 0;
60562
+ if (selectedLineIds.length > 1) {
60563
+ return lineMetricsRows.some((row) => selectedLineIdSet.has(row?.line_id));
60129
60564
  }
60130
- return lineMetricsRows.some((row) => row?.line_id === selectedLineId);
60131
- }, [lineMetrics, selectedLineId, factoryViewId]);
60565
+ return lineMetricsRows.some((row) => row?.line_id === primarySelectedLineId);
60566
+ }, [lineMetrics, primarySelectedLineId, selectedLineIdSet, selectedLineIds.length]);
60132
60567
  const isKpiLoading = !hasKpiDataReady;
60133
60568
  React142.useEffect(() => {
60134
60569
  const minLoadingDurationMs = 250;
@@ -60190,76 +60625,77 @@ function HomeView({
60190
60625
  DashboardHeader,
60191
60626
  {
60192
60627
  lineTitle,
60193
- lineId: selectedLineId === factoryViewId ? allLineIds[0] : selectedLineId,
60628
+ lineId: primarySelectedLineId,
60194
60629
  className: "w-full",
60195
60630
  headerControls: kpiSectionControl
60196
60631
  }
60197
60632
  ) }) }),
60198
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto sm:overflow-hidden relative", children: [
60199
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-2 top-1 sm:right-6 sm:top-3 z-30 flex items-center space-x-2", children: lineSelectorComponent && lineSelectorComponent }),
60200
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full sm:h-full min-h-[calc(100vh-100px)] sm:min-h-0", children: workspaceMetricsWithBreakState.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
60201
- motion.div,
60202
- {
60203
- initial: { opacity: 0, scale: 0.98 },
60204
- animate: { opacity: 1, scale: 1 },
60205
- transition: { duration: 0.3 },
60633
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto sm:overflow-hidden relative flex flex-col", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[calc(100vh-100px)] sm:min-h-0", children: workspaceMetricsWithBreakState.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
60634
+ motion.div,
60635
+ {
60636
+ initial: { opacity: 0, scale: 0.98 },
60637
+ animate: { opacity: 1, scale: 1 },
60638
+ transition: { duration: 0.3 },
60639
+ className: "h-full",
60640
+ children: React142__namespace.default.createElement(WorkspaceGrid, {
60641
+ workspaces: workspaceMetricsWithBreakState,
60642
+ lineNames: mergedLineNames,
60643
+ lineOrder: selectedLineIds,
60644
+ factoryView: factoryViewId,
60645
+ legend: efficiencyLegend,
60646
+ videoSources,
60647
+ videoStreamsByWorkspaceId,
60648
+ videoStreamsLoading,
60649
+ displayNames: workspaceDisplayNames,
60650
+ hasFlowBuffers,
60206
60651
  className: "h-full",
60207
- children: React142__namespace.default.createElement(WorkspaceGrid, {
60208
- workspaces: workspaceMetricsWithBreakState,
60209
- lineNames,
60210
- factoryView: factoryViewId,
60211
- legend: efficiencyLegend,
60212
- videoSources,
60213
- videoStreamsByWorkspaceId,
60214
- videoStreamsLoading,
60215
- displayNames: workspaceDisplayNames,
60216
- hasFlowBuffers,
60217
- className: "h-full",
60218
- onWorkspaceHover: handleWorkspaceHover,
60219
- onWorkspaceHoverEnd: handleWorkspaceHoverEnd
60220
- })
60221
- },
60222
- selectedLineId
60223
- ) : !shouldShowDataLoading && hasInitialDataLoaded ? /* @__PURE__ */ jsxRuntime.jsx(
60224
- motion.div,
60225
- {
60226
- initial: { opacity: 0 },
60227
- animate: { opacity: 1 },
60228
- transition: { duration: 0.3 },
60229
- children: /* @__PURE__ */ jsxRuntime.jsx(NoWorkspaceData, { message: "No workspace data available. Select another line or check configurations." })
60230
- }
60231
- ) : /* @__PURE__ */ jsxRuntime.jsx(
60232
- motion.div,
60233
- {
60234
- initial: { opacity: 0, scale: 0.98 },
60235
- animate: { opacity: 1, scale: 1 },
60236
- transition: { duration: 0.3 },
60652
+ toolbarRightContent: lineSelectorComponent,
60653
+ onWorkspaceHover: handleWorkspaceHover,
60654
+ onWorkspaceHoverEnd: handleWorkspaceHoverEnd
60655
+ })
60656
+ },
60657
+ selectedLineIdsKey
60658
+ ) : !shouldShowDataLoading && hasInitialDataLoaded ? /* @__PURE__ */ jsxRuntime.jsx(
60659
+ motion.div,
60660
+ {
60661
+ initial: { opacity: 0 },
60662
+ animate: { opacity: 1 },
60663
+ transition: { duration: 0.3 },
60664
+ children: /* @__PURE__ */ jsxRuntime.jsx(NoWorkspaceData, { message: "No workspace data available. Adjust the selected lines or check configurations." })
60665
+ }
60666
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
60667
+ motion.div,
60668
+ {
60669
+ initial: { opacity: 0, scale: 0.98 },
60670
+ animate: { opacity: 1, scale: 1 },
60671
+ transition: { duration: 0.3 },
60672
+ className: "h-full",
60673
+ children: React142__namespace.default.createElement(WorkspaceGrid, {
60674
+ workspaces: [],
60675
+ // Show empty grid while loading
60676
+ lineNames: mergedLineNames,
60677
+ lineOrder: selectedLineIds,
60678
+ factoryView: factoryViewId,
60679
+ legend: efficiencyLegend,
60680
+ videoSources,
60681
+ videoStreamsByWorkspaceId,
60682
+ videoStreamsLoading,
60683
+ displayNames: workspaceDisplayNames,
60684
+ hasFlowBuffers,
60237
60685
  className: "h-full",
60238
- children: React142__namespace.default.createElement(WorkspaceGrid, {
60239
- workspaces: [],
60240
- // Show empty grid while loading
60241
- lineNames,
60242
- factoryView: factoryViewId,
60243
- legend: efficiencyLegend,
60244
- videoSources,
60245
- videoStreamsByWorkspaceId,
60246
- videoStreamsLoading,
60247
- displayNames: workspaceDisplayNames,
60248
- hasFlowBuffers,
60249
- className: "h-full",
60250
- onWorkspaceHover: handleWorkspaceHover,
60251
- onWorkspaceHoverEnd: handleWorkspaceHoverEnd
60252
- })
60253
- },
60254
- selectedLineId
60255
- ) })
60256
- ] })
60686
+ toolbarRightContent: lineSelectorComponent,
60687
+ onWorkspaceHover: handleWorkspaceHover,
60688
+ onWorkspaceHoverEnd: handleWorkspaceHoverEnd
60689
+ })
60690
+ },
60691
+ selectedLineIdsKey
60692
+ ) }) })
60257
60693
  ] }),
60258
60694
  /* @__PURE__ */ jsxRuntime.jsx(
60259
60695
  BreakNotificationPopup,
60260
60696
  {
60261
60697
  activeBreaks,
60262
- lineNames,
60698
+ lineNames: mergedLineNames,
60263
60699
  isVisible: !breaksLoading && !breaksError && !breakNotificationsDismissed,
60264
60700
  onDismiss: () => setBreakNotificationsDismissed(true)
60265
60701
  }
@@ -69398,6 +69834,10 @@ var WorkspaceDetailView = ({
69398
69834
  shiftId: selectedShift,
69399
69835
  companyId: dashboardConfig?.entityConfig?.companyId
69400
69836
  });
69837
+ const {
69838
+ isFastSlowClipFiltersEnabled,
69839
+ isResolved: isFastSlowClipFiltersResolved
69840
+ } = useCompanyFastSlowClipFiltersEnabled();
69401
69841
  const isClipsEnabled = dashboardConfig?.clipsConfig?.enabled ?? true;
69402
69842
  dashboardConfig?.supervisorConfig?.enabled || false;
69403
69843
  const effectiveLineId = lineId || selectedLineId;
@@ -69484,15 +69924,19 @@ var WorkspaceDetailView = ({
69484
69924
  return `${workspaceId}:${percentileDate}:${percentileShiftId.toString()}:10`;
69485
69925
  }, [workspaceId, percentileDate, percentileShiftId]);
69486
69926
  React142.useEffect(() => {
69487
- if (!percentileCountsKey) {
69927
+ if (!isFastSlowClipFiltersEnabled || !percentileCountsKey) {
69488
69928
  setPrefetchedPercentileCounts(null);
69489
69929
  return;
69490
69930
  }
69491
69931
  if (prefetchedPercentileCounts && prefetchedPercentileCounts.key !== percentileCountsKey) {
69492
69932
  setPrefetchedPercentileCounts(null);
69493
69933
  }
69494
- }, [percentileCountsKey, prefetchedPercentileCounts]);
69934
+ }, [isFastSlowClipFiltersEnabled, percentileCountsKey, prefetchedPercentileCounts]);
69495
69935
  React142.useEffect(() => {
69936
+ if (!isFastSlowClipFiltersEnabled || !isFastSlowClipFiltersResolved) {
69937
+ setPrefetchedPercentileCounts(null);
69938
+ return;
69939
+ }
69496
69940
  if (!percentileCountsKey || !percentileDate || percentileShiftId === null || percentileShiftId === void 0) {
69497
69941
  return;
69498
69942
  }
@@ -69559,7 +70003,16 @@ var WorkspaceDetailView = ({
69559
70003
  return () => {
69560
70004
  controller.abort();
69561
70005
  };
69562
- }, [percentileCountsKey, percentileDate, percentileShiftId, workspaceId, supabase, prefetchedPercentileCounts]);
70006
+ }, [
70007
+ isFastSlowClipFiltersEnabled,
70008
+ isFastSlowClipFiltersResolved,
70009
+ percentileCountsKey,
70010
+ percentileDate,
70011
+ percentileShiftId,
70012
+ workspaceId,
70013
+ supabase,
70014
+ prefetchedPercentileCounts
70015
+ ]);
69563
70016
  const {
69564
70017
  metrics: historicMetrics,
69565
70018
  isLoading: historicLoading,
@@ -70835,7 +71288,7 @@ var WorkspaceDetailView = ({
70835
71288
  shift,
70836
71289
  totalOutput: workspace?.total_actions,
70837
71290
  workspaceMetrics: detailedWorkspaceMetrics || void 0,
70838
- prefetchedPercentileCounts,
71291
+ prefetchedPercentileCounts: isFastSlowClipFiltersEnabled ? prefetchedPercentileCounts : null,
70839
71292
  className: "h-[calc(100vh-10rem)]"
70840
71293
  }
70841
71294
  ) })
@@ -80242,6 +80695,7 @@ exports.useClipTypes = useClipTypes;
80242
80695
  exports.useClipTypesWithCounts = useClipTypesWithCounts;
80243
80696
  exports.useClipsInit = useClipsInit;
80244
80697
  exports.useCompanyClipsCost = useCompanyClipsCost;
80698
+ exports.useCompanyFastSlowClipFiltersEnabled = useCompanyFastSlowClipFiltersEnabled;
80245
80699
  exports.useCompanyHasVlmEnabledLine = useCompanyHasVlmEnabledLine;
80246
80700
  exports.useCompanyUsersUsage = useCompanyUsersUsage;
80247
80701
  exports.useComponentOverride = useComponentOverride;