@optifye/dashboard-core 6.11.25 → 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,
@@ -20256,6 +20257,17 @@ function useCompanyHasVlmEnabledLine(options = {}) {
20256
20257
  };
20257
20258
  }
20258
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
+
20259
20271
  // src/lib/utils/api.ts
20260
20272
  var apiUtils = {
20261
20273
  /**
@@ -38226,14 +38238,15 @@ var HlsVideoPlayer = React142.forwardRef(({
38226
38238
  };
38227
38239
  }, [effectiveSrc, initializePlayer, cleanupBlobUrl]);
38228
38240
  React142.useEffect(() => {
38229
- if (videoRef.current) {
38230
- if (autoplay) {
38231
- videoRef.current.play().catch((err) => {
38232
- console.warn("[HlsVideoPlayer] Autoplay failed:", err);
38233
- });
38234
- }
38241
+ if (!videoRef.current || !autoplay || !effectiveSrc) {
38242
+ return;
38235
38243
  }
38236
- }, [autoplay]);
38244
+ videoRef.current.play().catch((err) => {
38245
+ if (err?.name !== "AbortError") {
38246
+ console.warn("[HlsVideoPlayer] Autoplay failed:", err);
38247
+ }
38248
+ });
38249
+ }, [autoplay, effectiveSrc]);
38237
38250
  const resetControlsTimeout = React142.useCallback(() => {
38238
38251
  if (controlsPinned) {
38239
38252
  setShowControls(true);
@@ -40154,7 +40167,8 @@ var FileManagerFilters = ({
40154
40167
  snapshotClipId,
40155
40168
  className = "",
40156
40169
  targetCycleTime = null,
40157
- idleTimeVlmEnabled = false
40170
+ idleTimeVlmEnabled = false,
40171
+ showPercentileCycleFilters = true
40158
40172
  }) => {
40159
40173
  const [expandedNodes, setExpandedNodes] = React142.useState(/* @__PURE__ */ new Set());
40160
40174
  const [startTime, setStartTime] = React142.useState("");
@@ -40433,6 +40447,9 @@ var FileManagerFilters = ({
40433
40447
  }
40434
40448
  }, [ensureAllIdleTimeClipMetadataLoaded]);
40435
40449
  const fetchPercentileClips = React142.useCallback(async (type) => {
40450
+ if (!showPercentileCycleFilters && type !== "idle-times") {
40451
+ return;
40452
+ }
40436
40453
  if (!workspaceId || !date || shift === void 0) {
40437
40454
  console.warn("[FileManager] Missing required params for percentile clips fetch");
40438
40455
  return;
@@ -40479,7 +40496,7 @@ var FileManagerFilters = ({
40479
40496
  } catch (error) {
40480
40497
  console.error(`[FileManager] Error fetching ${type} clips:`, error);
40481
40498
  }
40482
- }, [workspaceId, date, shift, filterState.percentile, supabase]);
40499
+ }, [workspaceId, date, shift, filterState.percentile, showPercentileCycleFilters, supabase]);
40483
40500
  const percentileCountsKey = React142.useMemo(() => {
40484
40501
  if (!workspaceId || !date || shift === void 0) {
40485
40502
  return null;
@@ -40487,7 +40504,7 @@ var FileManagerFilters = ({
40487
40504
  return `${workspaceId}:${date}:${shift}:${filterState.percentile}`;
40488
40505
  }, [workspaceId, date, shift, filterState.percentile]);
40489
40506
  React142.useEffect(() => {
40490
- if (!percentileCountsKey) {
40507
+ if (!showPercentileCycleFilters || !percentileCountsKey) {
40491
40508
  percentileCountsKeyRef.current = null;
40492
40509
  percentilePrefetchRef.current = { key: null, types: /* @__PURE__ */ new Set() };
40493
40510
  setPercentileCounts({
@@ -40507,8 +40524,11 @@ var FileManagerFilters = ({
40507
40524
  "slow-cycles": null
40508
40525
  });
40509
40526
  setPercentileClips({});
40510
- }, [percentileCountsKey]);
40527
+ }, [showPercentileCycleFilters, percentileCountsKey]);
40511
40528
  React142.useEffect(() => {
40529
+ if (!showPercentileCycleFilters) {
40530
+ return;
40531
+ }
40512
40532
  if (!prefetchedPercentileCounts || !percentileCountsKey) {
40513
40533
  return;
40514
40534
  }
@@ -40523,8 +40543,11 @@ var FileManagerFilters = ({
40523
40543
  "fast-cycles": typeof prefetchedPercentileCounts.counts["fast-cycles"] === "number" ? prefetchedPercentileCounts.counts["fast-cycles"] : prev["fast-cycles"],
40524
40544
  "slow-cycles": typeof prefetchedPercentileCounts.counts["slow-cycles"] === "number" ? prefetchedPercentileCounts.counts["slow-cycles"] : prev["slow-cycles"]
40525
40545
  }));
40526
- }, [prefetchedPercentileCounts, percentileCountsKey, filterState.percentile]);
40546
+ }, [showPercentileCycleFilters, prefetchedPercentileCounts, percentileCountsKey, filterState.percentile]);
40527
40547
  const fetchPercentileCounts = React142.useCallback(async (options) => {
40548
+ if (!showPercentileCycleFilters) {
40549
+ return;
40550
+ }
40528
40551
  if (!workspaceId || !date || shift === void 0) {
40529
40552
  console.warn("[FileManager] Missing required params for percentile counts fetch");
40530
40553
  return;
@@ -40578,9 +40601,9 @@ var FileManagerFilters = ({
40578
40601
  } catch (error) {
40579
40602
  console.error("[FileManager] Error fetching percentile counts:", error);
40580
40603
  }
40581
- }, [workspaceId, date, shift, filterState.percentile, supabase, percentileCounts, percentileClips, fetchPercentileClips]);
40604
+ }, [workspaceId, date, shift, filterState.percentile, showPercentileCycleFilters, supabase, percentileCounts, percentileClips, fetchPercentileClips]);
40582
40605
  React142.useEffect(() => {
40583
- if (!isReady || !percentileCountsKey) {
40606
+ if (!showPercentileCycleFilters || !isReady || !percentileCountsKey) {
40584
40607
  return;
40585
40608
  }
40586
40609
  const schedule = () => {
@@ -40591,13 +40614,13 @@ var FileManagerFilters = ({
40591
40614
  } else {
40592
40615
  setTimeout(schedule, 0);
40593
40616
  }
40594
- }, [isReady, percentileCountsKey, fetchPercentileCounts]);
40617
+ }, [showPercentileCycleFilters, isReady, percentileCountsKey, fetchPercentileCounts]);
40595
40618
  const shouldShowCategory = React142.useCallback((categoryId) => {
40596
40619
  switch (categoryId) {
40597
40620
  case "fast-cycles":
40598
- return filterState.showFastCycles;
40621
+ return showPercentileCycleFilters && filterState.showFastCycles;
40599
40622
  case "slow-cycles":
40600
- return filterState.showSlowCycles;
40623
+ return showPercentileCycleFilters && filterState.showSlowCycles;
40601
40624
  case "longest-idles":
40602
40625
  return false;
40603
40626
  // filterState.showLongestIdles; // Temporarily disabled
@@ -40610,7 +40633,7 @@ var FileManagerFilters = ({
40610
40633
  default:
40611
40634
  return true;
40612
40635
  }
40613
- }, [filterState]);
40636
+ }, [filterState, showPercentileCycleFilters]);
40614
40637
  const getPercentileIcon = React142.useCallback((type, isExpanded, colorClasses) => {
40615
40638
  const iconMap = {
40616
40639
  "fast-cycles": { icon: lucideReact.TrendingUp, color: "text-green-600" },
@@ -40781,7 +40804,7 @@ var FileManagerFilters = ({
40781
40804
  const filteredSlowCycles = (percentileClips["slow-cycles"] || []).filter((clip) => isClipInTimeRange(clip.creation_timestamp || ""));
40782
40805
  const fastCount = typeof percentileCounts["fast-cycles"] === "number" ? percentileCounts["fast-cycles"] : null;
40783
40806
  const slowCount = typeof percentileCounts["slow-cycles"] === "number" ? percentileCounts["slow-cycles"] : null;
40784
- const percentileCategories = [
40807
+ const percentileCategories = showPercentileCycleFilters ? [
40785
40808
  {
40786
40809
  id: "fast-cycles",
40787
40810
  label: "Fast Cycles",
@@ -40879,7 +40902,7 @@ var FileManagerFilters = ({
40879
40902
  };
40880
40903
  })
40881
40904
  } */
40882
- ];
40905
+ ] : [];
40883
40906
  const orderedIds = ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
40884
40907
  orderedIds.forEach((orderedId) => {
40885
40908
  const percentileCategory = percentileCategories.find((cat) => cat.id === orderedId);
@@ -40904,7 +40927,7 @@ var FileManagerFilters = ({
40904
40927
  }
40905
40928
  });
40906
40929
  return tree;
40907
- }, [categories, expandedNodes, counts, clipMetadata, percentileCounts, percentileClips, shouldShowCategory, getPercentileIcon, isClipInTimeRange, isTimeFilterActive]);
40930
+ }, [categories, expandedNodes, counts, clipMetadata, percentileCounts, percentileClips, shouldShowCategory, getPercentileIcon, isClipInTimeRange, isTimeFilterActive, showPercentileCycleFilters]);
40908
40931
  const toggleExpanded = (nodeId) => {
40909
40932
  const newExpanded = new Set(expandedNodes);
40910
40933
  if (newExpanded.has(nodeId)) {
@@ -40916,10 +40939,10 @@ var FileManagerFilters = ({
40916
40939
  console.log(`[FileManager] Fetching clips for expanded category: ${nodeId}`);
40917
40940
  fetchClipMetadata(nodeId, 1);
40918
40941
  }
40919
- if (nodeId === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40942
+ if (showPercentileCycleFilters && nodeId === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40920
40943
  fetchPercentileClips("fast-cycles");
40921
40944
  }
40922
- if (nodeId === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40945
+ if (showPercentileCycleFilters && nodeId === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40923
40946
  fetchPercentileClips("slow-cycles");
40924
40947
  }
40925
40948
  }
@@ -40937,10 +40960,10 @@ var FileManagerFilters = ({
40937
40960
  console.log(`[FileManager] Fetching clips for expanded category: ${node.id}`);
40938
40961
  fetchClipMetadata(node.id, 1);
40939
40962
  }
40940
- if (node.id === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40963
+ if (showPercentileCycleFilters && node.id === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40941
40964
  fetchPercentileClips("fast-cycles");
40942
40965
  }
40943
- if (node.id === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40966
+ if (showPercentileCycleFilters && node.id === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40944
40967
  fetchPercentileClips("slow-cycles");
40945
40968
  }
40946
40969
  }
@@ -41378,7 +41401,8 @@ var FileManagerFilters = ({
41378
41401
  };
41379
41402
  var PERCENTILE_PRESETS = [5, 10, 15, 20, 25, 30];
41380
41403
  var AdvancedFilterDialog = ({
41381
- onApply
41404
+ onApply,
41405
+ showPercentileCycleFilters = true
41382
41406
  }) => {
41383
41407
  const {
41384
41408
  state,
@@ -41445,7 +41469,7 @@ var AdvancedFilterDialog = ({
41445
41469
  )
41446
41470
  ] }) }),
41447
41471
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 space-y-8 overflow-y-auto max-h-[60vh]", children: [
41448
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
41472
+ showPercentileCycleFilters && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
41449
41473
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-3", children: [
41450
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" }) }),
41451
41475
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -41538,7 +41562,7 @@ var AdvancedFilterDialog = ({
41538
41562
  ] })
41539
41563
  ] }),
41540
41564
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [
41541
- /* @__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: [
41542
41566
  /* @__PURE__ */ jsxRuntime.jsxs("h4", { className: "text-sm font-bold text-gray-700 uppercase tracking-wide flex items-center space-x-2", children: [
41543
41567
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-4 w-4 text-emerald-600" }),
41544
41568
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Performance Extremes" })
@@ -41819,6 +41843,7 @@ var BottlenecksContent = ({
41819
41843
  const supabase = useSupabase();
41820
41844
  const timezone = useAppTimezone();
41821
41845
  const { isIdleTimeVlmEnabled } = useIdleTimeVlmConfig();
41846
+ const { isFastSlowClipFiltersEnabled } = useCompanyFastSlowClipFiltersEnabled();
41822
41847
  const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineId);
41823
41848
  const { effectiveShift, effectiveDate } = React142.useMemo(() => {
41824
41849
  if (shift !== void 0 && shift !== null && date) {
@@ -41875,12 +41900,16 @@ var BottlenecksContent = ({
41875
41900
  }), []);
41876
41901
  const [initialFilter, setInitialFilter] = React142.useState("");
41877
41902
  const currentIndexRef = React142.useRef(0);
41903
+ const currentClipIdRef = React142.useRef(null);
41878
41904
  const currentPositionRef = React142.useRef(0);
41879
41905
  const currentTotalRef = React142.useRef(0);
41880
41906
  const activeFilterRef = React142.useRef(initialFilter);
41881
41907
  const isMountedRef = React142.useRef(true);
41882
41908
  const fetchInProgressRef = React142.useRef(/* @__PURE__ */ new Set());
41883
41909
  const refreshCountsTimeoutRef = React142.useRef(null);
41910
+ const awaitingNextClipRef = React142.useRef(false);
41911
+ const retryTimeoutRef = React142.useRef(null);
41912
+ const navigationLockRef = React142.useRef(false);
41884
41913
  const [isPlaying, setIsPlaying] = React142.useState(false);
41885
41914
  const [currentTime, setCurrentTime] = React142.useState(0);
41886
41915
  const [duration, setDuration] = React142.useState(0);
@@ -41889,6 +41918,7 @@ var BottlenecksContent = ({
41889
41918
  const [playbackSpeed, setPlaybackSpeed] = React142.useState(1);
41890
41919
  const [currentPosition, setCurrentPosition] = React142.useState(0);
41891
41920
  const [currentTotal, setCurrentTotal] = React142.useState(0);
41921
+ const [playerInstanceNonce, setPlayerInstanceNonce] = React142.useState(0);
41892
41922
  const [isTransitioning, setIsTransitioning] = React142.useState(false);
41893
41923
  const [pendingVideo, setPendingVideo] = React142.useState(null);
41894
41924
  const [isVideoBuffering, setIsVideoBuffering] = React142.useState(false);
@@ -41945,14 +41975,29 @@ var BottlenecksContent = ({
41945
41975
  const [isFullscreen, setIsFullscreen] = React142.useState(false);
41946
41976
  const categoryMetadataRef = React142.useRef([]);
41947
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
+ }, []);
41948
41987
  const updateActiveFilter = React142.useCallback((newFilter) => {
41949
41988
  console.log(`[BottlenecksContent] Updating active filter: ${activeFilterRef.current} -> ${newFilter}`);
41989
+ awaitingNextClipRef.current = false;
41990
+ navigationLockRef.current = false;
41991
+ clearRetryTimeout();
41950
41992
  setActiveFilter(newFilter);
41951
41993
  activeFilterRef.current = newFilter;
41952
- }, []);
41994
+ }, [clearRetryTimeout]);
41953
41995
  React142.useEffect(() => {
41954
41996
  currentIndexRef.current = currentIndex;
41955
41997
  }, [currentIndex]);
41998
+ React142.useEffect(() => {
41999
+ currentClipIdRef.current = currentClipId;
42000
+ }, [currentClipId]);
41956
42001
  React142.useEffect(() => {
41957
42002
  currentPositionRef.current = currentPosition;
41958
42003
  }, [currentPosition]);
@@ -42361,8 +42406,11 @@ var BottlenecksContent = ({
42361
42406
  }
42362
42407
  }, [activeFilter, s3ClipsService, mergedCounts, loadFirstVideoForCategory, allVideos, firstClip]);
42363
42408
  const isPercentileCategory = React142.useCallback((categoryId) => {
42409
+ if (!isFastSlowClipFiltersEnabled) {
42410
+ return false;
42411
+ }
42364
42412
  return ["fast-cycles", "slow-cycles", "longest-idles"].includes(categoryId);
42365
- }, []);
42413
+ }, [isFastSlowClipFiltersEnabled]);
42366
42414
  const getMetadataCacheKey = React142.useCallback((categoryId) => {
42367
42415
  return `${categoryId}-${effectiveDateString}-${effectiveShiftId}-${snapshotDateTime ?? "nosnap"}-${snapshotClipId ?? "nosnap"}`;
42368
42416
  }, [effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId]);
@@ -42394,6 +42442,21 @@ var BottlenecksContent = ({
42394
42442
  return [categoryId];
42395
42443
  }
42396
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]);
42397
42460
  React142.useCallback((categoryId) => {
42398
42461
  if (isPercentileCategory(categoryId)) {
42399
42462
  return categoryMetadata.length;
@@ -42423,6 +42486,7 @@ var BottlenecksContent = ({
42423
42486
  }
42424
42487
  }, [isNavigating, currentIndex, filteredVideos.length]);
42425
42488
  const clearLoadingState = React142.useCallback(() => {
42489
+ navigationLockRef.current = false;
42426
42490
  setIsTransitioning(false);
42427
42491
  setIsNavigating(false);
42428
42492
  if (loadingTimeoutRef.current) {
@@ -42434,6 +42498,11 @@ var BottlenecksContent = ({
42434
42498
  if (!workspaceId) {
42435
42499
  return;
42436
42500
  }
42501
+ if (!isFastSlowClipFiltersEnabled && (categoryId === "fast-cycles" || categoryId === "slow-cycles")) {
42502
+ setCategoryMetadata([]);
42503
+ categoryMetadataRef.current = [];
42504
+ return;
42505
+ }
42437
42506
  if (!isEffectiveShiftReady) {
42438
42507
  console.log("[BottlenecksContent] Skipping metadata load - shift/date not ready");
42439
42508
  return;
@@ -42586,7 +42655,7 @@ var BottlenecksContent = ({
42586
42655
  } finally {
42587
42656
  setIsCategoryLoading(false);
42588
42657
  }
42589
- }, [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]);
42590
42659
  React142.useEffect(() => {
42591
42660
  if (previousFilterRef.current !== activeFilter) {
42592
42661
  console.log(`Filter changed from ${previousFilterRef.current} to ${activeFilter} - resetting to first video`);
@@ -42637,8 +42706,12 @@ var BottlenecksContent = ({
42637
42706
  const loadAndPlayClipById = React142.useCallback(async (clipId, categoryId, position, metadataContext) => {
42638
42707
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
42639
42708
  console.log(`[BottlenecksContent] Loading clip by ID: ${clipId}, category=${categoryId}, position=${position}`);
42709
+ awaitingNextClipRef.current = false;
42710
+ navigationLockRef.current = true;
42711
+ clearRetryTimeout();
42640
42712
  setIsTransitioning(true);
42641
42713
  setIsInitialLoading(true);
42714
+ setIsNavigating(true);
42642
42715
  setError(null);
42643
42716
  if (videoRef.current?.player) {
42644
42717
  try {
@@ -42736,7 +42809,67 @@ var BottlenecksContent = ({
42736
42809
  clearLoadingState();
42737
42810
  }
42738
42811
  }
42739
- }, [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
+ ]);
42740
42873
  React142.useCallback(async (categoryId, clipIndex) => {
42741
42874
  console.warn("[BottlenecksContent] loadAndPlayClip is deprecated, use loadAndPlayClipById instead");
42742
42875
  if (!workspaceId || !s3ClipsService || !isMountedRef.current || !isEffectiveShiftReady) return;
@@ -42765,8 +42898,11 @@ var BottlenecksContent = ({
42765
42898
  }
42766
42899
  }, [workspaceId, s3ClipsService, effectiveDateString, effectiveShiftId, loadAndPlayClipById, isEffectiveShiftReady]);
42767
42900
  const handleNext = React142.useCallback(async () => {
42768
- if (!isMountedRef.current) return;
42901
+ if (!isMountedRef.current || navigationLockRef.current) return;
42769
42902
  const currentFilter = activeFilterRef.current;
42903
+ navigationLockRef.current = true;
42904
+ clearRetryTimeout();
42905
+ setIsNavigating(true);
42770
42906
  setIsTransitioning(true);
42771
42907
  setIsInitialLoading(true);
42772
42908
  setError(null);
@@ -42796,10 +42932,32 @@ var BottlenecksContent = ({
42796
42932
  );
42797
42933
  const olderClip = neighbors.previous;
42798
42934
  if (!olderClip) {
42799
- console.log("[handleNext] Already at last clip in category");
42800
- 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);
42801
42958
  return;
42802
42959
  }
42960
+ awaitingNextClipRef.current = false;
42803
42961
  setPendingVideo(olderClip);
42804
42962
  setCurrentClipId(olderClip.id || null);
42805
42963
  setAllVideos([olderClip]);
@@ -42855,8 +43013,23 @@ var BottlenecksContent = ({
42855
43013
  clearLoadingState();
42856
43014
  }
42857
43015
  } else {
42858
- console.log(`[handleNext] Already at last clip in category`);
42859
- 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);
42860
43033
  }
42861
43034
  } catch (error2) {
42862
43035
  console.error(`[handleNext] Error navigating:`, error2);
@@ -42868,10 +43041,13 @@ var BottlenecksContent = ({
42868
43041
  });
42869
43042
  clearLoadingState();
42870
43043
  }
42871
- }, [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]);
42872
43045
  const handlePrevious = React142.useCallback(async () => {
42873
- if (!isMountedRef.current) return;
43046
+ if (!isMountedRef.current || navigationLockRef.current) return;
42874
43047
  const currentFilter = activeFilterRef.current;
43048
+ navigationLockRef.current = true;
43049
+ clearRetryTimeout();
43050
+ setIsNavigating(true);
42875
43051
  setIsTransitioning(true);
42876
43052
  setIsInitialLoading(true);
42877
43053
  setError(null);
@@ -42969,7 +43145,7 @@ var BottlenecksContent = ({
42969
43145
  });
42970
43146
  clearLoadingState();
42971
43147
  }
42972
- }, [clearLoadingState, s3ClipsService, loadCategoryMetadata, isPercentileCategory, workspaceId, currentClipId, effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId, mergedCounts]);
43148
+ }, [clearLoadingState, clearRetryTimeout, s3ClipsService, loadCategoryMetadata, isPercentileCategory, workspaceId, currentClipId, effectiveDateString, effectiveShiftId, snapshotDateTime, snapshotClipId, mergedCounts]);
42973
43149
  const currentVideo = React142.useMemo(() => {
42974
43150
  if (!filteredVideos || filteredVideos.length === 0 || currentIndex >= filteredVideos.length) {
42975
43151
  return null;
@@ -43136,6 +43312,8 @@ var BottlenecksContent = ({
43136
43312
  }
43137
43313
  }, [error, playbackSpeed]);
43138
43314
  const handleVideoPlay = React142.useCallback(async (player) => {
43315
+ awaitingNextClipRef.current = false;
43316
+ clearRetryTimeout();
43139
43317
  setIsPlaying(true);
43140
43318
  setIsInitialLoading(false);
43141
43319
  if (currentVideo && !currentVideo.creation_timestamp && s3ClipsService) {
@@ -43157,7 +43335,7 @@ var BottlenecksContent = ({
43157
43335
  console.warn("[BottlenecksContent] Failed to load metadata for current video:", error2);
43158
43336
  }
43159
43337
  }
43160
- }, [currentVideo, s3ClipsService]);
43338
+ }, [currentVideo, s3ClipsService, clearRetryTimeout]);
43161
43339
  const handleVideoPause = React142.useCallback((player) => {
43162
43340
  setIsPlaying(false);
43163
43341
  }, []);
@@ -43168,8 +43346,10 @@ var BottlenecksContent = ({
43168
43346
  setDuration(duration2);
43169
43347
  }, []);
43170
43348
  const handleLoadedData = React142.useCallback((player) => {
43171
- console.log("Video data loaded - NOT clearing loading (wait for playing event)");
43172
- }, []);
43349
+ console.log("Video data loaded - clearing transition overlay");
43350
+ setIsInitialLoading(false);
43351
+ clearLoadingState();
43352
+ }, [clearLoadingState]);
43173
43353
  const handleVideoPlaying = React142.useCallback((player) => {
43174
43354
  console.log("Video playing - hiding transition overlay (most reliable)");
43175
43355
  clearLoadingState();
@@ -43190,6 +43370,7 @@ var BottlenecksContent = ({
43190
43370
  console.error("[BottlenecksContent] Video.js error:", errorInfo);
43191
43371
  setIsPlaying(false);
43192
43372
  setIsVideoBuffering(false);
43373
+ clearRetryTimeout();
43193
43374
  const errorCode = errorInfo?.code || 0;
43194
43375
  const canRetry = errorInfo?.canRetry ?? false;
43195
43376
  const errorMessage = errorInfo?.message || "Unknown error";
@@ -43217,17 +43398,24 @@ var BottlenecksContent = ({
43217
43398
  if (videoRetryCountRef.current < 3 && currentVideo) {
43218
43399
  videoRetryCountRef.current++;
43219
43400
  const retryDelay = 1e3 * videoRetryCountRef.current;
43401
+ const retryClipId = currentVideo.id;
43402
+ const retryCategory = activeFilterRef.current;
43220
43403
  console.log(`[Video Error] Recoverable error - Retrying... Attempt ${videoRetryCountRef.current}/3 in ${retryDelay}ms`);
43221
43404
  setError({
43222
43405
  type: "retrying",
43223
43406
  message: `Retrying... (${videoRetryCountRef.current}/3)`,
43224
43407
  isRetrying: true
43225
43408
  });
43226
- setTimeout(() => {
43227
- if (videoRef.current && currentVideo && isMountedRef.current) {
43228
- setError(null);
43229
- videoRef.current.dispose();
43409
+ retryTimeoutRef.current = setTimeout(() => {
43410
+ retryTimeoutRef.current = null;
43411
+ if (!isMountedRef.current) {
43412
+ return;
43413
+ }
43414
+ if (currentClipIdRef.current !== retryClipId || activeFilterRef.current !== retryCategory) {
43415
+ console.log("[Video Error] Skipping stale retry for previous clip");
43416
+ return;
43230
43417
  }
43418
+ restartCurrentClipPlayback();
43231
43419
  }, retryDelay);
43232
43420
  } else {
43233
43421
  console.log("[Video Error] Retries exhausted - showing final error overlay");
@@ -43249,7 +43437,7 @@ var BottlenecksContent = ({
43249
43437
  attempts: 3
43250
43438
  });
43251
43439
  }
43252
- }, [currentVideo, workspaceId, clearLoadingState]);
43440
+ }, [currentVideo, workspaceId, clearLoadingState, clearRetryTimeout, restartCurrentClipPlayback]);
43253
43441
  React142.useEffect(() => {
43254
43442
  isMountedRef.current = true;
43255
43443
  return () => {
@@ -43261,10 +43449,11 @@ var BottlenecksContent = ({
43261
43449
  clearTimeout(loadingTimeoutRef.current);
43262
43450
  loadingTimeoutRef.current = null;
43263
43451
  }
43452
+ clearRetryTimeout();
43264
43453
  setIsCategoryLoading(false);
43265
43454
  setIsNavigating(false);
43266
43455
  };
43267
- }, [s3ClipsService]);
43456
+ }, [s3ClipsService, clearRetryTimeout]);
43268
43457
  React142.useEffect(() => {
43269
43458
  if (filteredVideos.length > 0 && currentIndex < filteredVideos.length) {
43270
43459
  if (error && error.type === "fatal") {
@@ -43480,7 +43669,8 @@ var BottlenecksContent = ({
43480
43669
  isShareLoading,
43481
43670
  isShareCopied,
43482
43671
  options: videoPlayerOptions
43483
- }
43672
+ },
43673
+ `${currentVideo.id}-${playerInstanceNonce}-inline`
43484
43674
  )
43485
43675
  }
43486
43676
  ),
@@ -43511,11 +43701,8 @@ var BottlenecksContent = ({
43511
43701
  "button",
43512
43702
  {
43513
43703
  onClick: () => {
43514
- setError(null);
43515
43704
  videoRetryCountRef.current = 0;
43516
- if (videoRef.current) {
43517
- videoRef.current.dispose();
43518
- }
43705
+ restartCurrentClipPlayback();
43519
43706
  },
43520
43707
  className: "px-5 py-2.5 bg-gray-600 hover:bg-gray-700 rounded-md text-sm font-medium transition-colors",
43521
43708
  children: "Retry"
@@ -43747,7 +43934,7 @@ var BottlenecksContent = ({
43747
43934
  currentVideoId: currentVideo?.id,
43748
43935
  counts: mergedCounts,
43749
43936
  isReady: hasInitialLoad,
43750
- prefetchedPercentileCounts: prefetchedPercentileCounts || void 0,
43937
+ prefetchedPercentileCounts: isFastSlowClipFiltersEnabled ? prefetchedPercentileCounts || void 0 : void 0,
43751
43938
  workspaceId,
43752
43939
  date: effectiveDateString,
43753
43940
  shift: effectiveShiftId,
@@ -43756,6 +43943,7 @@ var BottlenecksContent = ({
43756
43943
  targetCycleTime: workspaceTargetCycleTime,
43757
43944
  clipClassifications,
43758
43945
  idleTimeVlmEnabled,
43946
+ showPercentileCycleFilters: isFastSlowClipFiltersEnabled,
43759
43947
  onFilterChange: (filterId) => {
43760
43948
  updateActiveFilter(filterId);
43761
43949
  const category = categoriesToShow.find((cat) => cat.type === filterId);
@@ -43904,7 +44092,8 @@ var BottlenecksContent = ({
43904
44092
  isShareLoading,
43905
44093
  isShareCopied,
43906
44094
  options: videoPlayerOptions
43907
- }
44095
+ },
44096
+ `${currentVideo.id}-${playerInstanceNonce}-fullscreen`
43908
44097
  )
43909
44098
  }
43910
44099
  ),
@@ -43968,6 +44157,7 @@ var BottlenecksContent = ({
43968
44157
  !triageMode && /* @__PURE__ */ jsxRuntime.jsx(
43969
44158
  AdvancedFilterDialog,
43970
44159
  {
44160
+ showPercentileCycleFilters: isFastSlowClipFiltersEnabled,
43971
44161
  onApply: () => {
43972
44162
  console.log("[BottlenecksContent] Advanced filters applied, will refresh clips...");
43973
44163
  }
@@ -69644,6 +69834,10 @@ var WorkspaceDetailView = ({
69644
69834
  shiftId: selectedShift,
69645
69835
  companyId: dashboardConfig?.entityConfig?.companyId
69646
69836
  });
69837
+ const {
69838
+ isFastSlowClipFiltersEnabled,
69839
+ isResolved: isFastSlowClipFiltersResolved
69840
+ } = useCompanyFastSlowClipFiltersEnabled();
69647
69841
  const isClipsEnabled = dashboardConfig?.clipsConfig?.enabled ?? true;
69648
69842
  dashboardConfig?.supervisorConfig?.enabled || false;
69649
69843
  const effectiveLineId = lineId || selectedLineId;
@@ -69730,15 +69924,19 @@ var WorkspaceDetailView = ({
69730
69924
  return `${workspaceId}:${percentileDate}:${percentileShiftId.toString()}:10`;
69731
69925
  }, [workspaceId, percentileDate, percentileShiftId]);
69732
69926
  React142.useEffect(() => {
69733
- if (!percentileCountsKey) {
69927
+ if (!isFastSlowClipFiltersEnabled || !percentileCountsKey) {
69734
69928
  setPrefetchedPercentileCounts(null);
69735
69929
  return;
69736
69930
  }
69737
69931
  if (prefetchedPercentileCounts && prefetchedPercentileCounts.key !== percentileCountsKey) {
69738
69932
  setPrefetchedPercentileCounts(null);
69739
69933
  }
69740
- }, [percentileCountsKey, prefetchedPercentileCounts]);
69934
+ }, [isFastSlowClipFiltersEnabled, percentileCountsKey, prefetchedPercentileCounts]);
69741
69935
  React142.useEffect(() => {
69936
+ if (!isFastSlowClipFiltersEnabled || !isFastSlowClipFiltersResolved) {
69937
+ setPrefetchedPercentileCounts(null);
69938
+ return;
69939
+ }
69742
69940
  if (!percentileCountsKey || !percentileDate || percentileShiftId === null || percentileShiftId === void 0) {
69743
69941
  return;
69744
69942
  }
@@ -69805,7 +70003,16 @@ var WorkspaceDetailView = ({
69805
70003
  return () => {
69806
70004
  controller.abort();
69807
70005
  };
69808
- }, [percentileCountsKey, percentileDate, percentileShiftId, workspaceId, supabase, prefetchedPercentileCounts]);
70006
+ }, [
70007
+ isFastSlowClipFiltersEnabled,
70008
+ isFastSlowClipFiltersResolved,
70009
+ percentileCountsKey,
70010
+ percentileDate,
70011
+ percentileShiftId,
70012
+ workspaceId,
70013
+ supabase,
70014
+ prefetchedPercentileCounts
70015
+ ]);
69809
70016
  const {
69810
70017
  metrics: historicMetrics,
69811
70018
  isLoading: historicLoading,
@@ -71081,7 +71288,7 @@ var WorkspaceDetailView = ({
71081
71288
  shift,
71082
71289
  totalOutput: workspace?.total_actions,
71083
71290
  workspaceMetrics: detailedWorkspaceMetrics || void 0,
71084
- prefetchedPercentileCounts,
71291
+ prefetchedPercentileCounts: isFastSlowClipFiltersEnabled ? prefetchedPercentileCounts : null,
71085
71292
  className: "h-[calc(100vh-10rem)]"
71086
71293
  }
71087
71294
  ) })
@@ -80488,6 +80695,7 @@ exports.useClipTypes = useClipTypes;
80488
80695
  exports.useClipTypesWithCounts = useClipTypesWithCounts;
80489
80696
  exports.useClipsInit = useClipsInit;
80490
80697
  exports.useCompanyClipsCost = useCompanyClipsCost;
80698
+ exports.useCompanyFastSlowClipFiltersEnabled = useCompanyFastSlowClipFiltersEnabled;
80491
80699
  exports.useCompanyHasVlmEnabledLine = useCompanyHasVlmEnabledLine;
80492
80700
  exports.useCompanyUsersUsage = useCompanyUsersUsage;
80493
80701
  exports.useComponentOverride = useComponentOverride;