@optifye/dashboard-core 6.9.9 → 6.9.11

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.css CHANGED
@@ -5607,10 +5607,6 @@ input[type=range]:active::-moz-range-thumb {
5607
5607
  font-size: 1.875rem;
5608
5608
  line-height: 2.25rem;
5609
5609
  }
5610
- .lg\:text-4xl {
5611
- font-size: 2.25rem;
5612
- line-height: 2.5rem;
5613
- }
5614
5610
  .lg\:text-7xl {
5615
5611
  font-size: 4.5rem;
5616
5612
  line-height: 1;
package/dist/index.js CHANGED
@@ -23099,7 +23099,7 @@ var OutputProgressChartComponent = ({
23099
23099
  ];
23100
23100
  const COLORS = ["#00AB45", "#f3f4f6"];
23101
23101
  const percentage = (currentOutput / targetOutput * 100).toFixed(1);
23102
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-square max-h-full", style: { maxWidth: "min(100%, 280px)" }, children: [
23102
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-square max-h-full", style: { maxWidth: "min(100%, 280px)", containerType: "inline-size" }, children: [
23103
23103
  /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.PieChart, { children: /* @__PURE__ */ jsxRuntime.jsx(
23104
23104
  recharts.Pie,
23105
23105
  {
@@ -23123,16 +23123,33 @@ var OutputProgressChartComponent = ({
23123
23123
  ))
23124
23124
  }
23125
23125
  ) }) }),
23126
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
23127
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl sm:text-3xl lg:text-4xl font-bold text-gray-800", children: [
23128
- percentage,
23129
- "%"
23130
- ] }),
23131
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs sm:text-sm lg:text-base text-gray-500 mt-1 sm:mt-2", children: [
23132
- currentOutput,
23133
- " / ",
23134
- Math.round(targetOutput)
23135
- ] })
23126
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
23127
+ /* @__PURE__ */ jsxRuntime.jsxs(
23128
+ "div",
23129
+ {
23130
+ className: "font-bold text-gray-800",
23131
+ style: { fontSize: "clamp(1.25rem, 8cqw, 2.5rem)" },
23132
+ children: [
23133
+ percentage,
23134
+ "%"
23135
+ ]
23136
+ }
23137
+ ),
23138
+ /* @__PURE__ */ jsxRuntime.jsxs(
23139
+ "div",
23140
+ {
23141
+ className: "text-gray-500",
23142
+ style: {
23143
+ fontSize: "clamp(0.7rem, 3.5cqw, 1rem)",
23144
+ marginTop: "clamp(0.125rem, 1cqw, 0.5rem)"
23145
+ },
23146
+ children: [
23147
+ currentOutput,
23148
+ " / ",
23149
+ Math.round(targetOutput)
23150
+ ]
23151
+ }
23152
+ )
23136
23153
  ] }) })
23137
23154
  ] }) });
23138
23155
  };
@@ -23150,7 +23167,7 @@ var LargeOutputProgressChart = ({
23150
23167
  ];
23151
23168
  const COLORS = ["#00AB45", "#f3f4f6"];
23152
23169
  const percentage = (currentOutput / targetOutput * 100).toFixed(1);
23153
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full", style: { minHeight: "100px", minWidth: "100px" }, children: [
23170
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full", style: { minHeight: "100px", minWidth: "100px", containerType: "inline-size" }, children: [
23154
23171
  /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.PieChart, { children: /* @__PURE__ */ jsxRuntime.jsx(
23155
23172
  recharts.Pie,
23156
23173
  {
@@ -23174,16 +23191,40 @@ var LargeOutputProgressChart = ({
23174
23191
  ))
23175
23192
  }
23176
23193
  ) }) }),
23177
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
23178
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-4xl sm:text-5xl font-bold text-gray-900", children: currentOutput }),
23179
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-gray-500", children: [
23180
- "of ",
23181
- targetOutput
23182
- ] }),
23183
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-base font-medium text-gray-600 mt-1", children: [
23184
- percentage,
23185
- "%"
23186
- ] })
23194
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
23195
+ /* @__PURE__ */ jsxRuntime.jsx(
23196
+ "div",
23197
+ {
23198
+ className: "font-bold text-gray-900",
23199
+ style: { fontSize: "clamp(1.5rem, 10cqw, 3rem)" },
23200
+ children: currentOutput
23201
+ }
23202
+ ),
23203
+ /* @__PURE__ */ jsxRuntime.jsxs(
23204
+ "div",
23205
+ {
23206
+ className: "text-gray-500",
23207
+ style: { fontSize: "clamp(0.75rem, 3.5cqw, 1rem)" },
23208
+ children: [
23209
+ "of ",
23210
+ targetOutput
23211
+ ]
23212
+ }
23213
+ ),
23214
+ /* @__PURE__ */ jsxRuntime.jsxs(
23215
+ "div",
23216
+ {
23217
+ className: "font-medium text-gray-600",
23218
+ style: {
23219
+ fontSize: "clamp(0.875rem, 4.5cqw, 1.25rem)",
23220
+ marginTop: "clamp(0.125rem, 1cqw, 0.5rem)"
23221
+ },
23222
+ children: [
23223
+ percentage,
23224
+ "%"
23225
+ ]
23226
+ }
23227
+ )
23187
23228
  ] }) })
23188
23229
  ] }) });
23189
23230
  };
@@ -25277,7 +25318,7 @@ var GaugeChart = ({
25277
25318
  };
25278
25319
  const gaugeColor = getColor();
25279
25320
  const targetAngle = target !== void 0 ? 180 - (target - min) / (max - min) * 180 : null;
25280
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `relative w-full h-full flex flex-col items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full max-w-[280px] aspect-square", style: { minHeight: "100px", minWidth: "100px" }, children: [
25321
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `relative w-full h-full flex flex-col items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full max-w-[280px] aspect-square", style: { minHeight: "100px", minWidth: "100px", containerType: "inline-size" }, children: [
25281
25322
  /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.PieChart, { children: /* @__PURE__ */ jsxRuntime.jsxs(
25282
25323
  recharts.Pie,
25283
25324
  {
@@ -25310,17 +25351,44 @@ var GaugeChart = ({
25310
25351
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute -top-1 -left-1.5 w-3 h-3 bg-gray-800 rounded-full" })
25311
25352
  }
25312
25353
  ),
25313
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
25314
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-3xl font-bold text-gray-800", children: [
25315
- value.toFixed(unit === "%" ? 1 : 0),
25316
- unit
25317
- ] }),
25318
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-600 mt-1 font-medium", children: label }),
25319
- target !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mt-1", children: [
25320
- "Target: ",
25321
- target,
25322
- unit
25323
- ] })
25354
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
25355
+ /* @__PURE__ */ jsxRuntime.jsxs(
25356
+ "div",
25357
+ {
25358
+ className: "font-bold text-gray-800",
25359
+ style: { fontSize: "clamp(1.25rem, 8cqw, 2rem)" },
25360
+ children: [
25361
+ value.toFixed(unit === "%" ? 1 : 0),
25362
+ unit
25363
+ ]
25364
+ }
25365
+ ),
25366
+ /* @__PURE__ */ jsxRuntime.jsx(
25367
+ "div",
25368
+ {
25369
+ className: "text-gray-600 font-medium",
25370
+ style: {
25371
+ fontSize: "clamp(0.75rem, 3.5cqw, 1rem)",
25372
+ marginTop: "clamp(0.125rem, 1cqw, 0.25rem)"
25373
+ },
25374
+ children: label
25375
+ }
25376
+ ),
25377
+ target !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(
25378
+ "div",
25379
+ {
25380
+ className: "text-gray-500",
25381
+ style: {
25382
+ fontSize: "clamp(0.7rem, 3cqw, 0.875rem)",
25383
+ marginTop: "clamp(0.125rem, 1cqw, 0.25rem)"
25384
+ },
25385
+ children: [
25386
+ "Target: ",
25387
+ target,
25388
+ unit
25389
+ ]
25390
+ }
25391
+ )
25324
25392
  ] }) }),
25325
25393
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-[15%] left-[15%] text-xs text-gray-500", children: [
25326
25394
  min,
@@ -29214,6 +29282,31 @@ var BottlenecksContent = ({
29214
29282
  const [categoryMetadata, setCategoryMetadata] = React23.useState([]);
29215
29283
  const [currentMetadataIndex, setCurrentMetadataIndex] = React23.useState(0);
29216
29284
  const [metadataCache, setMetadataCache] = React23.useState({});
29285
+ const invalidateMetadataCache = React23.useCallback((categories) => {
29286
+ setMetadataCache((prevCache) => {
29287
+ if (!prevCache || Object.keys(prevCache).length === 0) {
29288
+ return prevCache;
29289
+ }
29290
+ const targetCategories = categories ? (Array.isArray(categories) ? categories : [categories]).filter(Boolean) : null;
29291
+ let updatedCache = null;
29292
+ const shouldInvalidate = (key) => {
29293
+ if (!targetCategories || targetCategories.length === 0) {
29294
+ return true;
29295
+ }
29296
+ const [categoryId] = key.split("-");
29297
+ return targetCategories.includes(categoryId);
29298
+ };
29299
+ Object.keys(prevCache).forEach((cacheKey) => {
29300
+ if (shouldInvalidate(cacheKey)) {
29301
+ if (!updatedCache) {
29302
+ updatedCache = { ...prevCache };
29303
+ }
29304
+ delete updatedCache[cacheKey];
29305
+ }
29306
+ });
29307
+ return updatedCache || prevCache;
29308
+ });
29309
+ }, []);
29217
29310
  const [triageClips, setTriageClips] = React23.useState([]);
29218
29311
  const [isLoadingTriageClips, setIsLoadingTriageClips] = React23.useState(false);
29219
29312
  const [isFullscreen, setIsFullscreen] = React23.useState(false);
@@ -29233,6 +29326,12 @@ var BottlenecksContent = ({
29233
29326
  onNewClips: (notification) => {
29234
29327
  console.log(`[BottlenecksContent] New clips detected:`, notification);
29235
29328
  if (notification.clips.length > 0) {
29329
+ const categoryIds = notification.clips.map((clip) => clip.clip_type).filter(Boolean).map((value) => String(value));
29330
+ if (categoryIds.length > 0) {
29331
+ invalidateMetadataCache(categoryIds);
29332
+ } else {
29333
+ invalidateMetadataCache();
29334
+ }
29236
29335
  fetchClipCounts();
29237
29336
  }
29238
29337
  }
@@ -29279,24 +29378,37 @@ var BottlenecksContent = ({
29279
29378
  shift: shift || "0"
29280
29379
  });
29281
29380
  React23.useEffect(() => {
29282
- if (clipTypes.length > 0 && !initialFilter) {
29283
- const priorityOrder = ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
29284
- let selectedType = null;
29285
- for (const priorityType of priorityOrder) {
29286
- const type = clipTypes.find((t) => t.type === priorityType && (dynamicCounts[t.type] || 0) > 0);
29287
- if (type) {
29288
- selectedType = type;
29289
- break;
29381
+ if (clipTypes.length > 0) {
29382
+ const currentFilterCount = initialFilter ? dynamicCounts[initialFilter] || 0 : 0;
29383
+ const hasAnyCounts = Object.values(dynamicCounts).some((c) => c > 0);
29384
+ const userHasNotNavigated = !initialFilter || activeFilterRef.current === initialFilter;
29385
+ const shouldRunSelection = !initialFilter || userHasNotNavigated && currentFilterCount === 0 && hasAnyCounts;
29386
+ if (shouldRunSelection) {
29387
+ let selectedType = null;
29388
+ if (clipTypes.length === 1) {
29389
+ selectedType = clipTypes[0];
29390
+ } else {
29391
+ const priorityOrder = ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
29392
+ for (const priorityType of priorityOrder) {
29393
+ const type = clipTypes.find((t) => t.type === priorityType && (dynamicCounts[t.type] || 0) > 0);
29394
+ if (type) {
29395
+ selectedType = type;
29396
+ break;
29397
+ }
29398
+ }
29399
+ if (!selectedType) {
29400
+ selectedType = clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0);
29401
+ }
29402
+ if (!selectedType) {
29403
+ selectedType = clipTypes[0];
29404
+ }
29405
+ }
29406
+ if (selectedType && selectedType.type !== initialFilter) {
29407
+ console.log(`[BottlenecksContent] Auto-selecting filter: ${selectedType.type} (count: ${dynamicCounts[selectedType.type] || 0})`);
29408
+ setInitialFilter(selectedType.type);
29409
+ setActiveFilter(selectedType.type);
29410
+ activeFilterRef.current = selectedType.type;
29290
29411
  }
29291
- }
29292
- if (!selectedType) {
29293
- selectedType = clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0);
29294
- }
29295
- const firstType = selectedType || clipTypes[0];
29296
- if (firstType) {
29297
- setInitialFilter(firstType.type);
29298
- setActiveFilter(firstType.type);
29299
- activeFilterRef.current = firstType.type;
29300
29412
  }
29301
29413
  }
29302
29414
  }, [clipTypes, dynamicCounts, initialFilter]);
@@ -29348,7 +29460,7 @@ var BottlenecksContent = ({
29348
29460
  } finally {
29349
29461
  fetchInProgressRef.current.delete(operationKey);
29350
29462
  }
29351
- }, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts]);
29463
+ }, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts, timezone, totalOutput]);
29352
29464
  const loadingCategoryRef = React23.useRef(null);
29353
29465
  const loadFirstVideoForCategory = React23.useCallback(async (category) => {
29354
29466
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
@@ -29438,15 +29550,16 @@ var BottlenecksContent = ({
29438
29550
  loadingCategoryRef.current = null;
29439
29551
  fetchInProgressRef.current.delete(operationKey);
29440
29552
  }
29441
- }, [workspaceId, date, s3ClipsService, mergedCounts, effectiveShift]);
29553
+ }, [workspaceId, date, s3ClipsService, mergedCounts, effectiveShift, timezone]);
29442
29554
  const handleRefreshClips = React23.useCallback(async () => {
29443
29555
  console.log("[BottlenecksContent] Refreshing clips after new additions");
29444
29556
  acknowledgeNewClips();
29557
+ invalidateMetadataCache();
29445
29558
  await fetchClipCounts();
29446
29559
  if (activeFilter && mergedCounts[activeFilter] > 0) {
29447
29560
  await loadFirstVideoForCategory(activeFilter);
29448
29561
  }
29449
- }, [acknowledgeNewClips, fetchClipCounts, activeFilter, mergedCounts, loadFirstVideoForCategory]);
29562
+ }, [acknowledgeNewClips, fetchClipCounts, activeFilter, mergedCounts, loadFirstVideoForCategory, invalidateMetadataCache]);
29450
29563
  React23.useEffect(() => {
29451
29564
  if (s3ClipsService) {
29452
29565
  fetchClipCounts();
@@ -29616,38 +29729,38 @@ var BottlenecksContent = ({
29616
29729
  loadingTimeoutRef.current = null;
29617
29730
  }
29618
29731
  }, []);
29619
- const loadCategoryMetadata = React23.useCallback(async (categoryId, autoLoadFirstVideo = false) => {
29732
+ const loadCategoryMetadata = React23.useCallback(async (categoryId, autoLoadFirstVideo = false, forceRefresh = false) => {
29620
29733
  if (!workspaceId) {
29621
29734
  return;
29622
29735
  }
29623
- const cacheKey = `${categoryId}-${date || getOperationalDate(timezone)}-${effectiveShift}`;
29624
- if (metadataCache[cacheKey]) {
29625
- console.log(`[BottlenecksContent] Using cached metadata for ${categoryId}`);
29626
- setCategoryMetadata(metadataCache[cacheKey]);
29627
- categoryMetadataRef.current = metadataCache[cacheKey];
29628
- if (autoLoadFirstVideo && metadataCache[cacheKey].length > 0 && s3ClipsService) {
29629
- const firstClipMeta = metadataCache[cacheKey][0];
29630
- try {
29631
- const video = await s3ClipsService.getClipById(firstClipMeta.clipId);
29632
- if (video && isMountedRef.current) {
29633
- setCurrentClipId(firstClipMeta.clipId);
29634
- setAllVideos([video]);
29635
- setCurrentIndex(0);
29636
- setCurrentMetadataIndex(0);
29637
- currentMetadataIndexRef.current = 0;
29638
- setIsCategoryLoading(false);
29639
- console.log(`[BottlenecksContent] Auto-loaded first video from cache: ${video.id} (1/${metadataCache[cacheKey].length})`);
29736
+ const resolvedDate = date || getOperationalDate(timezone);
29737
+ const cacheKey = `${categoryId}-${resolvedDate}-${effectiveShift}`;
29738
+ const cachedMetadata = !forceRefresh ? metadataCache[cacheKey] : void 0;
29739
+ try {
29740
+ if (cachedMetadata) {
29741
+ console.log(`[BottlenecksContent] Using cached metadata for ${categoryId}`);
29742
+ setCategoryMetadata(cachedMetadata);
29743
+ categoryMetadataRef.current = cachedMetadata;
29744
+ if (autoLoadFirstVideo && cachedMetadata.length > 0 && s3ClipsService) {
29745
+ const firstClipMeta = cachedMetadata[0];
29746
+ try {
29747
+ const video = await s3ClipsService.getClipById(firstClipMeta.clipId);
29748
+ if (video && isMountedRef.current) {
29749
+ setCurrentClipId(firstClipMeta.clipId);
29750
+ setAllVideos([video]);
29751
+ setCurrentIndex(0);
29752
+ setCurrentMetadataIndex(0);
29753
+ currentMetadataIndexRef.current = 0;
29754
+ console.log(`[BottlenecksContent] Auto-loaded first video from cache: ${video.id} (1/${cachedMetadata.length})`);
29755
+ }
29756
+ } catch (error2) {
29757
+ console.error(`[BottlenecksContent] Error loading first video from cache:`, error2);
29758
+ clearLoadingState();
29640
29759
  }
29641
- } catch (error2) {
29642
- console.error(`[BottlenecksContent] Error loading first video from cache:`, error2);
29643
- setIsCategoryLoading(false);
29644
- clearLoadingState();
29645
29760
  }
29761
+ return;
29646
29762
  }
29647
- return;
29648
- }
29649
- try {
29650
- console.log(`[BottlenecksContent] Loading metadata for category: ${categoryId}`);
29763
+ console.log(`[BottlenecksContent] Loading metadata for category: ${categoryId}${forceRefresh ? " (force refresh)" : ""}`);
29651
29764
  const { createClient: createClient5 } = await import('@supabase/supabase-js');
29652
29765
  const supabase = createClient5(
29653
29766
  process.env.NEXT_PUBLIC_SUPABASE_URL || "",
@@ -29672,8 +29785,8 @@ var BottlenecksContent = ({
29672
29785
  action: "percentile-clips",
29673
29786
  percentileAction: percentileType,
29674
29787
  workspaceId,
29675
- startDate: `${date || getOperationalDate(timezone)}T00:00:00Z`,
29676
- endDate: `${date || getOperationalDate(timezone)}T23:59:59Z`,
29788
+ startDate: `${resolvedDate}T00:00:00Z`,
29789
+ endDate: `${resolvedDate}T23:59:59Z`,
29677
29790
  percentile: 10,
29678
29791
  shiftId: effectiveShift,
29679
29792
  limit: 100
@@ -29689,7 +29802,7 @@ var BottlenecksContent = ({
29689
29802
  body: JSON.stringify({
29690
29803
  action: "clip-metadata",
29691
29804
  workspaceId,
29692
- date: date || getOperationalDate(timezone),
29805
+ date: resolvedDate,
29693
29806
  shift: effectiveShift,
29694
29807
  category: categoryId,
29695
29808
  page: 1,
@@ -29734,19 +29847,22 @@ var BottlenecksContent = ({
29734
29847
  setCurrentIndex(0);
29735
29848
  setCurrentMetadataIndex(0);
29736
29849
  currentMetadataIndexRef.current = 0;
29737
- setIsCategoryLoading(false);
29738
29850
  console.log(`[BottlenecksContent] Auto-loaded first video: ${video.id} (1/${metadataClips.length})`);
29739
29851
  }
29740
29852
  } catch (error2) {
29741
29853
  console.error(`[BottlenecksContent] Error loading first video:`, error2);
29742
- setIsCategoryLoading(false);
29743
29854
  }
29744
29855
  }
29856
+ } else {
29857
+ setCategoryMetadata([]);
29858
+ categoryMetadataRef.current = [];
29745
29859
  }
29746
29860
  } catch (error2) {
29747
29861
  console.error(`[BottlenecksContent] Error loading category metadata:`, error2);
29862
+ } finally {
29863
+ setIsCategoryLoading(false);
29748
29864
  }
29749
- }, [workspaceId, date, effectiveShift, isPercentileCategory, metadataCache, s3ClipsService]);
29865
+ }, [workspaceId, date, effectiveShift, isPercentileCategory, metadataCache, s3ClipsService, timezone, clearLoadingState]);
29750
29866
  const loadAndPlayClipById = React23.useCallback(async (clipId, categoryId, position) => {
29751
29867
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
29752
29868
  console.log(`[BottlenecksContent] Loading clip by ID: ${clipId}, category=${categoryId}, position=${position}`);
@@ -29770,21 +29886,31 @@ var BottlenecksContent = ({
29770
29886
  }
29771
29887
  try {
29772
29888
  await loadCategoryMetadata(categoryId, false);
29773
- const metadataArray = categoryMetadataRef.current;
29774
- if (metadataArray.length > 0) {
29775
- const clickedClipIndex = metadataArray.findIndex((clip) => clip.clipId === clipId);
29776
- if (clickedClipIndex !== -1) {
29777
- setCurrentMetadataIndex(clickedClipIndex);
29778
- currentMetadataIndexRef.current = clickedClipIndex;
29779
- const video = await s3ClipsService.getClipById(clipId);
29780
- if (video) {
29781
- setPendingVideo(video);
29782
- setCurrentClipId(clipId);
29783
- setAllVideos([video]);
29784
- setCurrentIndex(0);
29785
- console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
29786
- }
29787
- }
29889
+ let metadataArray = categoryMetadataRef.current;
29890
+ const clipExistsInMetadata = metadataArray.some((clip) => clip.clipId === clipId);
29891
+ if (metadataArray.length === 0 || !clipExistsInMetadata) {
29892
+ console.warn(`[BottlenecksContent] Clip ${clipId} not found in metadata for ${categoryId} (cache hit: ${metadataArray.length > 0}) - forcing refresh`);
29893
+ await loadCategoryMetadata(categoryId, false, true);
29894
+ metadataArray = categoryMetadataRef.current;
29895
+ }
29896
+ if (metadataArray.length === 0) {
29897
+ throw new Error(`No metadata available for category ${categoryId}`);
29898
+ }
29899
+ const clickedClipIndex = metadataArray.findIndex((clip) => clip.clipId === clipId);
29900
+ if (clickedClipIndex === -1) {
29901
+ throw new Error(`Clip ${clipId} not found after metadata refresh`);
29902
+ }
29903
+ setCurrentMetadataIndex(clickedClipIndex);
29904
+ currentMetadataIndexRef.current = clickedClipIndex;
29905
+ const video = await s3ClipsService.getClipById(clipId);
29906
+ if (video) {
29907
+ setPendingVideo(video);
29908
+ setCurrentClipId(clipId);
29909
+ setAllVideos([video]);
29910
+ setCurrentIndex(0);
29911
+ console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
29912
+ } else {
29913
+ throw new Error(`Failed to load video data for clip ${clipId}`);
29788
29914
  }
29789
29915
  } catch (error2) {
29790
29916
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
@@ -29798,7 +29924,7 @@ var BottlenecksContent = ({
29798
29924
  clearLoadingState();
29799
29925
  }
29800
29926
  }
29801
- }, [workspaceId, s3ClipsService, date, effectiveShift, updateActiveFilter, clearLoadingState]);
29927
+ }, [workspaceId, s3ClipsService, updateActiveFilter, clearLoadingState, loadCategoryMetadata]);
29802
29928
  React23.useCallback(async (categoryId, clipIndex) => {
29803
29929
  console.warn("[BottlenecksContent] loadAndPlayClip is deprecated, use loadAndPlayClipById instead");
29804
29930
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
@@ -29825,7 +29951,7 @@ var BottlenecksContent = ({
29825
29951
  });
29826
29952
  setIsNavigating(false);
29827
29953
  }
29828
- }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById]);
29954
+ }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById, timezone]);
29829
29955
  const handleNext = React23.useCallback(async () => {
29830
29956
  if (!isMountedRef.current) return;
29831
29957
  const currentFilter = activeFilterRef.current;
@@ -29842,8 +29968,18 @@ var BottlenecksContent = ({
29842
29968
  }
29843
29969
  try {
29844
29970
  const currentMetaIndex = currentMetadataIndexRef.current;
29845
- const metadataArray = categoryMetadataRef.current;
29971
+ let metadataArray = categoryMetadataRef.current;
29972
+ if (metadataArray.length === 0) {
29973
+ console.log(`[handleNext] Metadata empty for ${currentFilter}, loading before navigation`);
29974
+ await loadCategoryMetadata(currentFilter, false);
29975
+ metadataArray = categoryMetadataRef.current;
29976
+ }
29846
29977
  console.log(`[handleNext] Unified navigation: ${currentFilter}, metadata index: ${currentMetaIndex}/${metadataArray.length}`);
29978
+ if (metadataArray.length === 0) {
29979
+ console.warn("[handleNext] No metadata available after refresh - stopping navigation");
29980
+ clearLoadingState();
29981
+ return;
29982
+ }
29847
29983
  if (currentMetaIndex < metadataArray.length - 1) {
29848
29984
  const nextMetadataIndex = currentMetaIndex + 1;
29849
29985
  const nextClipMeta = metadataArray[nextMetadataIndex];
@@ -29878,7 +30014,7 @@ var BottlenecksContent = ({
29878
30014
  });
29879
30015
  clearLoadingState();
29880
30016
  }
29881
- }, [clearLoadingState, s3ClipsService]);
30017
+ }, [clearLoadingState, s3ClipsService, loadCategoryMetadata]);
29882
30018
  const handlePrevious = React23.useCallback(async () => {
29883
30019
  if (!isMountedRef.current) return;
29884
30020
  const currentFilter = activeFilterRef.current;
@@ -29895,8 +30031,18 @@ var BottlenecksContent = ({
29895
30031
  }
29896
30032
  try {
29897
30033
  const currentMetaIndex = currentMetadataIndexRef.current;
29898
- const metadataArray = categoryMetadataRef.current;
30034
+ let metadataArray = categoryMetadataRef.current;
30035
+ if (metadataArray.length === 0) {
30036
+ console.log(`[handlePrevious] Metadata empty for ${currentFilter}, loading before navigation`);
30037
+ await loadCategoryMetadata(currentFilter, false);
30038
+ metadataArray = categoryMetadataRef.current;
30039
+ }
29899
30040
  console.log(`[handlePrevious] Unified navigation: ${currentFilter}, metadata index: ${currentMetaIndex}/${metadataArray.length}`);
30041
+ if (metadataArray.length === 0) {
30042
+ console.warn("[handlePrevious] No metadata available after refresh - stopping navigation");
30043
+ clearLoadingState();
30044
+ return;
30045
+ }
29900
30046
  if (currentMetaIndex > 0) {
29901
30047
  const prevMetadataIndex = currentMetaIndex - 1;
29902
30048
  const prevClipMeta = metadataArray[prevMetadataIndex];
@@ -29927,7 +30073,7 @@ var BottlenecksContent = ({
29927
30073
  });
29928
30074
  clearLoadingState();
29929
30075
  }
29930
- }, [clearLoadingState, s3ClipsService]);
30076
+ }, [clearLoadingState, s3ClipsService, loadCategoryMetadata]);
29931
30077
  const currentVideo = React23.useMemo(() => {
29932
30078
  if (!filteredVideos || filteredVideos.length === 0 || currentIndex >= filteredVideos.length) {
29933
30079
  return null;
package/dist/index.mjs CHANGED
@@ -23070,7 +23070,7 @@ var OutputProgressChartComponent = ({
23070
23070
  ];
23071
23071
  const COLORS = ["#00AB45", "#f3f4f6"];
23072
23072
  const percentage = (currentOutput / targetOutput * 100).toFixed(1);
23073
- return /* @__PURE__ */ jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full aspect-square max-h-full", style: { maxWidth: "min(100%, 280px)" }, children: [
23073
+ return /* @__PURE__ */ jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full aspect-square max-h-full", style: { maxWidth: "min(100%, 280px)", containerType: "inline-size" }, children: [
23074
23074
  /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsx(PieChart, { children: /* @__PURE__ */ jsx(
23075
23075
  Pie,
23076
23076
  {
@@ -23094,16 +23094,33 @@ var OutputProgressChartComponent = ({
23094
23094
  ))
23095
23095
  }
23096
23096
  ) }) }),
23097
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
23098
- /* @__PURE__ */ jsxs("div", { className: "text-2xl sm:text-3xl lg:text-4xl font-bold text-gray-800", children: [
23099
- percentage,
23100
- "%"
23101
- ] }),
23102
- /* @__PURE__ */ jsxs("div", { className: "text-xs sm:text-sm lg:text-base text-gray-500 mt-1 sm:mt-2", children: [
23103
- currentOutput,
23104
- " / ",
23105
- Math.round(targetOutput)
23106
- ] })
23097
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
23098
+ /* @__PURE__ */ jsxs(
23099
+ "div",
23100
+ {
23101
+ className: "font-bold text-gray-800",
23102
+ style: { fontSize: "clamp(1.25rem, 8cqw, 2.5rem)" },
23103
+ children: [
23104
+ percentage,
23105
+ "%"
23106
+ ]
23107
+ }
23108
+ ),
23109
+ /* @__PURE__ */ jsxs(
23110
+ "div",
23111
+ {
23112
+ className: "text-gray-500",
23113
+ style: {
23114
+ fontSize: "clamp(0.7rem, 3.5cqw, 1rem)",
23115
+ marginTop: "clamp(0.125rem, 1cqw, 0.5rem)"
23116
+ },
23117
+ children: [
23118
+ currentOutput,
23119
+ " / ",
23120
+ Math.round(targetOutput)
23121
+ ]
23122
+ }
23123
+ )
23107
23124
  ] }) })
23108
23125
  ] }) });
23109
23126
  };
@@ -23121,7 +23138,7 @@ var LargeOutputProgressChart = ({
23121
23138
  ];
23122
23139
  const COLORS = ["#00AB45", "#f3f4f6"];
23123
23140
  const percentage = (currentOutput / targetOutput * 100).toFixed(1);
23124
- return /* @__PURE__ */ jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full", style: { minHeight: "100px", minWidth: "100px" }, children: [
23141
+ return /* @__PURE__ */ jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full", style: { minHeight: "100px", minWidth: "100px", containerType: "inline-size" }, children: [
23125
23142
  /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsx(PieChart, { children: /* @__PURE__ */ jsx(
23126
23143
  Pie,
23127
23144
  {
@@ -23145,16 +23162,40 @@ var LargeOutputProgressChart = ({
23145
23162
  ))
23146
23163
  }
23147
23164
  ) }) }),
23148
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
23149
- /* @__PURE__ */ jsx("div", { className: "text-4xl sm:text-5xl font-bold text-gray-900", children: currentOutput }),
23150
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-500", children: [
23151
- "of ",
23152
- targetOutput
23153
- ] }),
23154
- /* @__PURE__ */ jsxs("div", { className: "text-base font-medium text-gray-600 mt-1", children: [
23155
- percentage,
23156
- "%"
23157
- ] })
23165
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
23166
+ /* @__PURE__ */ jsx(
23167
+ "div",
23168
+ {
23169
+ className: "font-bold text-gray-900",
23170
+ style: { fontSize: "clamp(1.5rem, 10cqw, 3rem)" },
23171
+ children: currentOutput
23172
+ }
23173
+ ),
23174
+ /* @__PURE__ */ jsxs(
23175
+ "div",
23176
+ {
23177
+ className: "text-gray-500",
23178
+ style: { fontSize: "clamp(0.75rem, 3.5cqw, 1rem)" },
23179
+ children: [
23180
+ "of ",
23181
+ targetOutput
23182
+ ]
23183
+ }
23184
+ ),
23185
+ /* @__PURE__ */ jsxs(
23186
+ "div",
23187
+ {
23188
+ className: "font-medium text-gray-600",
23189
+ style: {
23190
+ fontSize: "clamp(0.875rem, 4.5cqw, 1.25rem)",
23191
+ marginTop: "clamp(0.125rem, 1cqw, 0.5rem)"
23192
+ },
23193
+ children: [
23194
+ percentage,
23195
+ "%"
23196
+ ]
23197
+ }
23198
+ )
23158
23199
  ] }) })
23159
23200
  ] }) });
23160
23201
  };
@@ -25248,7 +25289,7 @@ var GaugeChart = ({
25248
25289
  };
25249
25290
  const gaugeColor = getColor();
25250
25291
  const targetAngle = target !== void 0 ? 180 - (target - min) / (max - min) * 180 : null;
25251
- return /* @__PURE__ */ jsx("div", { className: `relative w-full h-full flex flex-col items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-[280px] aspect-square", style: { minHeight: "100px", minWidth: "100px" }, children: [
25292
+ return /* @__PURE__ */ jsx("div", { className: `relative w-full h-full flex flex-col items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-[280px] aspect-square", style: { minHeight: "100px", minWidth: "100px", containerType: "inline-size" }, children: [
25252
25293
  /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsx(PieChart, { children: /* @__PURE__ */ jsxs(
25253
25294
  Pie,
25254
25295
  {
@@ -25281,17 +25322,44 @@ var GaugeChart = ({
25281
25322
  children: /* @__PURE__ */ jsx("div", { className: "absolute -top-1 -left-1.5 w-3 h-3 bg-gray-800 rounded-full" })
25282
25323
  }
25283
25324
  ),
25284
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
25285
- /* @__PURE__ */ jsxs("div", { className: "text-3xl font-bold text-gray-800", children: [
25286
- value.toFixed(unit === "%" ? 1 : 0),
25287
- unit
25288
- ] }),
25289
- /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-600 mt-1 font-medium", children: label }),
25290
- target !== void 0 && /* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-500 mt-1", children: [
25291
- "Target: ",
25292
- target,
25293
- unit
25294
- ] })
25325
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
25326
+ /* @__PURE__ */ jsxs(
25327
+ "div",
25328
+ {
25329
+ className: "font-bold text-gray-800",
25330
+ style: { fontSize: "clamp(1.25rem, 8cqw, 2rem)" },
25331
+ children: [
25332
+ value.toFixed(unit === "%" ? 1 : 0),
25333
+ unit
25334
+ ]
25335
+ }
25336
+ ),
25337
+ /* @__PURE__ */ jsx(
25338
+ "div",
25339
+ {
25340
+ className: "text-gray-600 font-medium",
25341
+ style: {
25342
+ fontSize: "clamp(0.75rem, 3.5cqw, 1rem)",
25343
+ marginTop: "clamp(0.125rem, 1cqw, 0.25rem)"
25344
+ },
25345
+ children: label
25346
+ }
25347
+ ),
25348
+ target !== void 0 && /* @__PURE__ */ jsxs(
25349
+ "div",
25350
+ {
25351
+ className: "text-gray-500",
25352
+ style: {
25353
+ fontSize: "clamp(0.7rem, 3cqw, 0.875rem)",
25354
+ marginTop: "clamp(0.125rem, 1cqw, 0.25rem)"
25355
+ },
25356
+ children: [
25357
+ "Target: ",
25358
+ target,
25359
+ unit
25360
+ ]
25361
+ }
25362
+ )
25295
25363
  ] }) }),
25296
25364
  /* @__PURE__ */ jsxs("div", { className: "absolute bottom-[15%] left-[15%] text-xs text-gray-500", children: [
25297
25365
  min,
@@ -29185,6 +29253,31 @@ var BottlenecksContent = ({
29185
29253
  const [categoryMetadata, setCategoryMetadata] = useState([]);
29186
29254
  const [currentMetadataIndex, setCurrentMetadataIndex] = useState(0);
29187
29255
  const [metadataCache, setMetadataCache] = useState({});
29256
+ const invalidateMetadataCache = useCallback((categories) => {
29257
+ setMetadataCache((prevCache) => {
29258
+ if (!prevCache || Object.keys(prevCache).length === 0) {
29259
+ return prevCache;
29260
+ }
29261
+ const targetCategories = categories ? (Array.isArray(categories) ? categories : [categories]).filter(Boolean) : null;
29262
+ let updatedCache = null;
29263
+ const shouldInvalidate = (key) => {
29264
+ if (!targetCategories || targetCategories.length === 0) {
29265
+ return true;
29266
+ }
29267
+ const [categoryId] = key.split("-");
29268
+ return targetCategories.includes(categoryId);
29269
+ };
29270
+ Object.keys(prevCache).forEach((cacheKey) => {
29271
+ if (shouldInvalidate(cacheKey)) {
29272
+ if (!updatedCache) {
29273
+ updatedCache = { ...prevCache };
29274
+ }
29275
+ delete updatedCache[cacheKey];
29276
+ }
29277
+ });
29278
+ return updatedCache || prevCache;
29279
+ });
29280
+ }, []);
29188
29281
  const [triageClips, setTriageClips] = useState([]);
29189
29282
  const [isLoadingTriageClips, setIsLoadingTriageClips] = useState(false);
29190
29283
  const [isFullscreen, setIsFullscreen] = useState(false);
@@ -29204,6 +29297,12 @@ var BottlenecksContent = ({
29204
29297
  onNewClips: (notification) => {
29205
29298
  console.log(`[BottlenecksContent] New clips detected:`, notification);
29206
29299
  if (notification.clips.length > 0) {
29300
+ const categoryIds = notification.clips.map((clip) => clip.clip_type).filter(Boolean).map((value) => String(value));
29301
+ if (categoryIds.length > 0) {
29302
+ invalidateMetadataCache(categoryIds);
29303
+ } else {
29304
+ invalidateMetadataCache();
29305
+ }
29207
29306
  fetchClipCounts();
29208
29307
  }
29209
29308
  }
@@ -29250,24 +29349,37 @@ var BottlenecksContent = ({
29250
29349
  shift: shift || "0"
29251
29350
  });
29252
29351
  useEffect(() => {
29253
- if (clipTypes.length > 0 && !initialFilter) {
29254
- const priorityOrder = ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
29255
- let selectedType = null;
29256
- for (const priorityType of priorityOrder) {
29257
- const type = clipTypes.find((t) => t.type === priorityType && (dynamicCounts[t.type] || 0) > 0);
29258
- if (type) {
29259
- selectedType = type;
29260
- break;
29352
+ if (clipTypes.length > 0) {
29353
+ const currentFilterCount = initialFilter ? dynamicCounts[initialFilter] || 0 : 0;
29354
+ const hasAnyCounts = Object.values(dynamicCounts).some((c) => c > 0);
29355
+ const userHasNotNavigated = !initialFilter || activeFilterRef.current === initialFilter;
29356
+ const shouldRunSelection = !initialFilter || userHasNotNavigated && currentFilterCount === 0 && hasAnyCounts;
29357
+ if (shouldRunSelection) {
29358
+ let selectedType = null;
29359
+ if (clipTypes.length === 1) {
29360
+ selectedType = clipTypes[0];
29361
+ } else {
29362
+ const priorityOrder = ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
29363
+ for (const priorityType of priorityOrder) {
29364
+ const type = clipTypes.find((t) => t.type === priorityType && (dynamicCounts[t.type] || 0) > 0);
29365
+ if (type) {
29366
+ selectedType = type;
29367
+ break;
29368
+ }
29369
+ }
29370
+ if (!selectedType) {
29371
+ selectedType = clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0);
29372
+ }
29373
+ if (!selectedType) {
29374
+ selectedType = clipTypes[0];
29375
+ }
29376
+ }
29377
+ if (selectedType && selectedType.type !== initialFilter) {
29378
+ console.log(`[BottlenecksContent] Auto-selecting filter: ${selectedType.type} (count: ${dynamicCounts[selectedType.type] || 0})`);
29379
+ setInitialFilter(selectedType.type);
29380
+ setActiveFilter(selectedType.type);
29381
+ activeFilterRef.current = selectedType.type;
29261
29382
  }
29262
- }
29263
- if (!selectedType) {
29264
- selectedType = clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0);
29265
- }
29266
- const firstType = selectedType || clipTypes[0];
29267
- if (firstType) {
29268
- setInitialFilter(firstType.type);
29269
- setActiveFilter(firstType.type);
29270
- activeFilterRef.current = firstType.type;
29271
29383
  }
29272
29384
  }
29273
29385
  }, [clipTypes, dynamicCounts, initialFilter]);
@@ -29319,7 +29431,7 @@ var BottlenecksContent = ({
29319
29431
  } finally {
29320
29432
  fetchInProgressRef.current.delete(operationKey);
29321
29433
  }
29322
- }, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts]);
29434
+ }, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts, timezone, totalOutput]);
29323
29435
  const loadingCategoryRef = useRef(null);
29324
29436
  const loadFirstVideoForCategory = useCallback(async (category) => {
29325
29437
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
@@ -29409,15 +29521,16 @@ var BottlenecksContent = ({
29409
29521
  loadingCategoryRef.current = null;
29410
29522
  fetchInProgressRef.current.delete(operationKey);
29411
29523
  }
29412
- }, [workspaceId, date, s3ClipsService, mergedCounts, effectiveShift]);
29524
+ }, [workspaceId, date, s3ClipsService, mergedCounts, effectiveShift, timezone]);
29413
29525
  const handleRefreshClips = useCallback(async () => {
29414
29526
  console.log("[BottlenecksContent] Refreshing clips after new additions");
29415
29527
  acknowledgeNewClips();
29528
+ invalidateMetadataCache();
29416
29529
  await fetchClipCounts();
29417
29530
  if (activeFilter && mergedCounts[activeFilter] > 0) {
29418
29531
  await loadFirstVideoForCategory(activeFilter);
29419
29532
  }
29420
- }, [acknowledgeNewClips, fetchClipCounts, activeFilter, mergedCounts, loadFirstVideoForCategory]);
29533
+ }, [acknowledgeNewClips, fetchClipCounts, activeFilter, mergedCounts, loadFirstVideoForCategory, invalidateMetadataCache]);
29421
29534
  useEffect(() => {
29422
29535
  if (s3ClipsService) {
29423
29536
  fetchClipCounts();
@@ -29587,38 +29700,38 @@ var BottlenecksContent = ({
29587
29700
  loadingTimeoutRef.current = null;
29588
29701
  }
29589
29702
  }, []);
29590
- const loadCategoryMetadata = useCallback(async (categoryId, autoLoadFirstVideo = false) => {
29703
+ const loadCategoryMetadata = useCallback(async (categoryId, autoLoadFirstVideo = false, forceRefresh = false) => {
29591
29704
  if (!workspaceId) {
29592
29705
  return;
29593
29706
  }
29594
- const cacheKey = `${categoryId}-${date || getOperationalDate(timezone)}-${effectiveShift}`;
29595
- if (metadataCache[cacheKey]) {
29596
- console.log(`[BottlenecksContent] Using cached metadata for ${categoryId}`);
29597
- setCategoryMetadata(metadataCache[cacheKey]);
29598
- categoryMetadataRef.current = metadataCache[cacheKey];
29599
- if (autoLoadFirstVideo && metadataCache[cacheKey].length > 0 && s3ClipsService) {
29600
- const firstClipMeta = metadataCache[cacheKey][0];
29601
- try {
29602
- const video = await s3ClipsService.getClipById(firstClipMeta.clipId);
29603
- if (video && isMountedRef.current) {
29604
- setCurrentClipId(firstClipMeta.clipId);
29605
- setAllVideos([video]);
29606
- setCurrentIndex(0);
29607
- setCurrentMetadataIndex(0);
29608
- currentMetadataIndexRef.current = 0;
29609
- setIsCategoryLoading(false);
29610
- console.log(`[BottlenecksContent] Auto-loaded first video from cache: ${video.id} (1/${metadataCache[cacheKey].length})`);
29707
+ const resolvedDate = date || getOperationalDate(timezone);
29708
+ const cacheKey = `${categoryId}-${resolvedDate}-${effectiveShift}`;
29709
+ const cachedMetadata = !forceRefresh ? metadataCache[cacheKey] : void 0;
29710
+ try {
29711
+ if (cachedMetadata) {
29712
+ console.log(`[BottlenecksContent] Using cached metadata for ${categoryId}`);
29713
+ setCategoryMetadata(cachedMetadata);
29714
+ categoryMetadataRef.current = cachedMetadata;
29715
+ if (autoLoadFirstVideo && cachedMetadata.length > 0 && s3ClipsService) {
29716
+ const firstClipMeta = cachedMetadata[0];
29717
+ try {
29718
+ const video = await s3ClipsService.getClipById(firstClipMeta.clipId);
29719
+ if (video && isMountedRef.current) {
29720
+ setCurrentClipId(firstClipMeta.clipId);
29721
+ setAllVideos([video]);
29722
+ setCurrentIndex(0);
29723
+ setCurrentMetadataIndex(0);
29724
+ currentMetadataIndexRef.current = 0;
29725
+ console.log(`[BottlenecksContent] Auto-loaded first video from cache: ${video.id} (1/${cachedMetadata.length})`);
29726
+ }
29727
+ } catch (error2) {
29728
+ console.error(`[BottlenecksContent] Error loading first video from cache:`, error2);
29729
+ clearLoadingState();
29611
29730
  }
29612
- } catch (error2) {
29613
- console.error(`[BottlenecksContent] Error loading first video from cache:`, error2);
29614
- setIsCategoryLoading(false);
29615
- clearLoadingState();
29616
29731
  }
29732
+ return;
29617
29733
  }
29618
- return;
29619
- }
29620
- try {
29621
- console.log(`[BottlenecksContent] Loading metadata for category: ${categoryId}`);
29734
+ console.log(`[BottlenecksContent] Loading metadata for category: ${categoryId}${forceRefresh ? " (force refresh)" : ""}`);
29622
29735
  const { createClient: createClient5 } = await import('@supabase/supabase-js');
29623
29736
  const supabase = createClient5(
29624
29737
  process.env.NEXT_PUBLIC_SUPABASE_URL || "",
@@ -29643,8 +29756,8 @@ var BottlenecksContent = ({
29643
29756
  action: "percentile-clips",
29644
29757
  percentileAction: percentileType,
29645
29758
  workspaceId,
29646
- startDate: `${date || getOperationalDate(timezone)}T00:00:00Z`,
29647
- endDate: `${date || getOperationalDate(timezone)}T23:59:59Z`,
29759
+ startDate: `${resolvedDate}T00:00:00Z`,
29760
+ endDate: `${resolvedDate}T23:59:59Z`,
29648
29761
  percentile: 10,
29649
29762
  shiftId: effectiveShift,
29650
29763
  limit: 100
@@ -29660,7 +29773,7 @@ var BottlenecksContent = ({
29660
29773
  body: JSON.stringify({
29661
29774
  action: "clip-metadata",
29662
29775
  workspaceId,
29663
- date: date || getOperationalDate(timezone),
29776
+ date: resolvedDate,
29664
29777
  shift: effectiveShift,
29665
29778
  category: categoryId,
29666
29779
  page: 1,
@@ -29705,19 +29818,22 @@ var BottlenecksContent = ({
29705
29818
  setCurrentIndex(0);
29706
29819
  setCurrentMetadataIndex(0);
29707
29820
  currentMetadataIndexRef.current = 0;
29708
- setIsCategoryLoading(false);
29709
29821
  console.log(`[BottlenecksContent] Auto-loaded first video: ${video.id} (1/${metadataClips.length})`);
29710
29822
  }
29711
29823
  } catch (error2) {
29712
29824
  console.error(`[BottlenecksContent] Error loading first video:`, error2);
29713
- setIsCategoryLoading(false);
29714
29825
  }
29715
29826
  }
29827
+ } else {
29828
+ setCategoryMetadata([]);
29829
+ categoryMetadataRef.current = [];
29716
29830
  }
29717
29831
  } catch (error2) {
29718
29832
  console.error(`[BottlenecksContent] Error loading category metadata:`, error2);
29833
+ } finally {
29834
+ setIsCategoryLoading(false);
29719
29835
  }
29720
- }, [workspaceId, date, effectiveShift, isPercentileCategory, metadataCache, s3ClipsService]);
29836
+ }, [workspaceId, date, effectiveShift, isPercentileCategory, metadataCache, s3ClipsService, timezone, clearLoadingState]);
29721
29837
  const loadAndPlayClipById = useCallback(async (clipId, categoryId, position) => {
29722
29838
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
29723
29839
  console.log(`[BottlenecksContent] Loading clip by ID: ${clipId}, category=${categoryId}, position=${position}`);
@@ -29741,21 +29857,31 @@ var BottlenecksContent = ({
29741
29857
  }
29742
29858
  try {
29743
29859
  await loadCategoryMetadata(categoryId, false);
29744
- const metadataArray = categoryMetadataRef.current;
29745
- if (metadataArray.length > 0) {
29746
- const clickedClipIndex = metadataArray.findIndex((clip) => clip.clipId === clipId);
29747
- if (clickedClipIndex !== -1) {
29748
- setCurrentMetadataIndex(clickedClipIndex);
29749
- currentMetadataIndexRef.current = clickedClipIndex;
29750
- const video = await s3ClipsService.getClipById(clipId);
29751
- if (video) {
29752
- setPendingVideo(video);
29753
- setCurrentClipId(clipId);
29754
- setAllVideos([video]);
29755
- setCurrentIndex(0);
29756
- console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
29757
- }
29758
- }
29860
+ let metadataArray = categoryMetadataRef.current;
29861
+ const clipExistsInMetadata = metadataArray.some((clip) => clip.clipId === clipId);
29862
+ if (metadataArray.length === 0 || !clipExistsInMetadata) {
29863
+ console.warn(`[BottlenecksContent] Clip ${clipId} not found in metadata for ${categoryId} (cache hit: ${metadataArray.length > 0}) - forcing refresh`);
29864
+ await loadCategoryMetadata(categoryId, false, true);
29865
+ metadataArray = categoryMetadataRef.current;
29866
+ }
29867
+ if (metadataArray.length === 0) {
29868
+ throw new Error(`No metadata available for category ${categoryId}`);
29869
+ }
29870
+ const clickedClipIndex = metadataArray.findIndex((clip) => clip.clipId === clipId);
29871
+ if (clickedClipIndex === -1) {
29872
+ throw new Error(`Clip ${clipId} not found after metadata refresh`);
29873
+ }
29874
+ setCurrentMetadataIndex(clickedClipIndex);
29875
+ currentMetadataIndexRef.current = clickedClipIndex;
29876
+ const video = await s3ClipsService.getClipById(clipId);
29877
+ if (video) {
29878
+ setPendingVideo(video);
29879
+ setCurrentClipId(clipId);
29880
+ setAllVideos([video]);
29881
+ setCurrentIndex(0);
29882
+ console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
29883
+ } else {
29884
+ throw new Error(`Failed to load video data for clip ${clipId}`);
29759
29885
  }
29760
29886
  } catch (error2) {
29761
29887
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
@@ -29769,7 +29895,7 @@ var BottlenecksContent = ({
29769
29895
  clearLoadingState();
29770
29896
  }
29771
29897
  }
29772
- }, [workspaceId, s3ClipsService, date, effectiveShift, updateActiveFilter, clearLoadingState]);
29898
+ }, [workspaceId, s3ClipsService, updateActiveFilter, clearLoadingState, loadCategoryMetadata]);
29773
29899
  useCallback(async (categoryId, clipIndex) => {
29774
29900
  console.warn("[BottlenecksContent] loadAndPlayClip is deprecated, use loadAndPlayClipById instead");
29775
29901
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
@@ -29796,7 +29922,7 @@ var BottlenecksContent = ({
29796
29922
  });
29797
29923
  setIsNavigating(false);
29798
29924
  }
29799
- }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById]);
29925
+ }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById, timezone]);
29800
29926
  const handleNext = useCallback(async () => {
29801
29927
  if (!isMountedRef.current) return;
29802
29928
  const currentFilter = activeFilterRef.current;
@@ -29813,8 +29939,18 @@ var BottlenecksContent = ({
29813
29939
  }
29814
29940
  try {
29815
29941
  const currentMetaIndex = currentMetadataIndexRef.current;
29816
- const metadataArray = categoryMetadataRef.current;
29942
+ let metadataArray = categoryMetadataRef.current;
29943
+ if (metadataArray.length === 0) {
29944
+ console.log(`[handleNext] Metadata empty for ${currentFilter}, loading before navigation`);
29945
+ await loadCategoryMetadata(currentFilter, false);
29946
+ metadataArray = categoryMetadataRef.current;
29947
+ }
29817
29948
  console.log(`[handleNext] Unified navigation: ${currentFilter}, metadata index: ${currentMetaIndex}/${metadataArray.length}`);
29949
+ if (metadataArray.length === 0) {
29950
+ console.warn("[handleNext] No metadata available after refresh - stopping navigation");
29951
+ clearLoadingState();
29952
+ return;
29953
+ }
29818
29954
  if (currentMetaIndex < metadataArray.length - 1) {
29819
29955
  const nextMetadataIndex = currentMetaIndex + 1;
29820
29956
  const nextClipMeta = metadataArray[nextMetadataIndex];
@@ -29849,7 +29985,7 @@ var BottlenecksContent = ({
29849
29985
  });
29850
29986
  clearLoadingState();
29851
29987
  }
29852
- }, [clearLoadingState, s3ClipsService]);
29988
+ }, [clearLoadingState, s3ClipsService, loadCategoryMetadata]);
29853
29989
  const handlePrevious = useCallback(async () => {
29854
29990
  if (!isMountedRef.current) return;
29855
29991
  const currentFilter = activeFilterRef.current;
@@ -29866,8 +30002,18 @@ var BottlenecksContent = ({
29866
30002
  }
29867
30003
  try {
29868
30004
  const currentMetaIndex = currentMetadataIndexRef.current;
29869
- const metadataArray = categoryMetadataRef.current;
30005
+ let metadataArray = categoryMetadataRef.current;
30006
+ if (metadataArray.length === 0) {
30007
+ console.log(`[handlePrevious] Metadata empty for ${currentFilter}, loading before navigation`);
30008
+ await loadCategoryMetadata(currentFilter, false);
30009
+ metadataArray = categoryMetadataRef.current;
30010
+ }
29870
30011
  console.log(`[handlePrevious] Unified navigation: ${currentFilter}, metadata index: ${currentMetaIndex}/${metadataArray.length}`);
30012
+ if (metadataArray.length === 0) {
30013
+ console.warn("[handlePrevious] No metadata available after refresh - stopping navigation");
30014
+ clearLoadingState();
30015
+ return;
30016
+ }
29871
30017
  if (currentMetaIndex > 0) {
29872
30018
  const prevMetadataIndex = currentMetaIndex - 1;
29873
30019
  const prevClipMeta = metadataArray[prevMetadataIndex];
@@ -29898,7 +30044,7 @@ var BottlenecksContent = ({
29898
30044
  });
29899
30045
  clearLoadingState();
29900
30046
  }
29901
- }, [clearLoadingState, s3ClipsService]);
30047
+ }, [clearLoadingState, s3ClipsService, loadCategoryMetadata]);
29902
30048
  const currentVideo = useMemo(() => {
29903
30049
  if (!filteredVideos || filteredVideos.length === 0 || currentIndex >= filteredVideos.length) {
29904
30050
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.9.9",
3
+ "version": "6.9.11",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",