@optifye/dashboard-core 6.6.9 → 6.6.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
@@ -2148,6 +2148,9 @@ body {
2148
2148
  .bg-black\/80 {
2149
2149
  background-color: rgb(0 0 0 / 0.8);
2150
2150
  }
2151
+ .bg-black\/90 {
2152
+ background-color: rgb(0 0 0 / 0.9);
2153
+ }
2151
2154
  .bg-blue-100 {
2152
2155
  --tw-bg-opacity: 1;
2153
2156
  background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
package/dist/index.d.mts CHANGED
@@ -1299,11 +1299,11 @@ declare class S3ClipsSupabaseService {
1299
1299
  /**
1300
1300
  * Get clip counts with optional video index
1301
1301
  */
1302
- getClipCountsCacheFirst(workspaceId: string, date: string, shiftId: string | number, buildIndex?: boolean): Promise<ClipCountsWithIndex | Record<string, number>>;
1302
+ getClipCountsCacheFirst(workspaceId: string, date: string, shiftId: string | number, buildIndex?: boolean, totalOutput?: number): Promise<ClipCountsWithIndex | Record<string, number>>;
1303
1303
  /**
1304
1304
  * Get clip counts (simplified version)
1305
1305
  */
1306
- getClipCounts(workspaceId: string, date: string, shiftId: string | number): Promise<Record<string, number>>;
1306
+ getClipCounts(workspaceId: string, date: string, shiftId: string | number, totalOutput?: number): Promise<Record<string, number>>;
1307
1307
  /**
1308
1308
  * Get clip by ID - stable navigation method
1309
1309
  * This ensures navigation works even when new clips are added
@@ -3257,7 +3257,7 @@ declare function useClipTypes(): UseClipTypesResult;
3257
3257
  /**
3258
3258
  * Hook to get clip types with counts for a specific workspace/date/shift
3259
3259
  */
3260
- declare function useClipTypesWithCounts(workspaceId: string, date: string, shiftId: string | number): UseClipTypesResult & {
3260
+ declare function useClipTypesWithCounts(workspaceId: string, date: string, shiftId: string | number, totalOutput?: number): UseClipTypesResult & {
3261
3261
  counts: Record<string, number>;
3262
3262
  };
3263
3263
 
@@ -5334,6 +5334,10 @@ interface BottlenecksContentProps {
5334
5334
  * Optional className for styling
5335
5335
  */
5336
5336
  className?: string;
5337
+ /**
5338
+ * Total output from workspace metrics for cycle completion adjustment
5339
+ */
5340
+ totalOutput?: number;
5337
5341
  }
5338
5342
  /**
5339
5343
  * Filter type for bottleneck clips - expanded for new video types
package/dist/index.d.ts CHANGED
@@ -1299,11 +1299,11 @@ declare class S3ClipsSupabaseService {
1299
1299
  /**
1300
1300
  * Get clip counts with optional video index
1301
1301
  */
1302
- getClipCountsCacheFirst(workspaceId: string, date: string, shiftId: string | number, buildIndex?: boolean): Promise<ClipCountsWithIndex | Record<string, number>>;
1302
+ getClipCountsCacheFirst(workspaceId: string, date: string, shiftId: string | number, buildIndex?: boolean, totalOutput?: number): Promise<ClipCountsWithIndex | Record<string, number>>;
1303
1303
  /**
1304
1304
  * Get clip counts (simplified version)
1305
1305
  */
1306
- getClipCounts(workspaceId: string, date: string, shiftId: string | number): Promise<Record<string, number>>;
1306
+ getClipCounts(workspaceId: string, date: string, shiftId: string | number, totalOutput?: number): Promise<Record<string, number>>;
1307
1307
  /**
1308
1308
  * Get clip by ID - stable navigation method
1309
1309
  * This ensures navigation works even when new clips are added
@@ -3257,7 +3257,7 @@ declare function useClipTypes(): UseClipTypesResult;
3257
3257
  /**
3258
3258
  * Hook to get clip types with counts for a specific workspace/date/shift
3259
3259
  */
3260
- declare function useClipTypesWithCounts(workspaceId: string, date: string, shiftId: string | number): UseClipTypesResult & {
3260
+ declare function useClipTypesWithCounts(workspaceId: string, date: string, shiftId: string | number, totalOutput?: number): UseClipTypesResult & {
3261
3261
  counts: Record<string, number>;
3262
3262
  };
3263
3263
 
@@ -5334,6 +5334,10 @@ interface BottlenecksContentProps {
5334
5334
  * Optional className for styling
5335
5335
  */
5336
5336
  className?: string;
5337
+ /**
5338
+ * Total output from workspace metrics for cycle completion adjustment
5339
+ */
5340
+ totalOutput?: number;
5337
5341
  }
5338
5342
  /**
5339
5343
  * Filter type for bottleneck clips - expanded for new video types
package/dist/index.js CHANGED
@@ -3765,7 +3765,7 @@ var S3ClipsSupabaseService = class {
3765
3765
  /**
3766
3766
  * Get clip counts with optional video index
3767
3767
  */
3768
- async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex = false) {
3768
+ async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex = false, totalOutput) {
3769
3769
  const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
3770
3770
  return this.deduplicate(cacheKey, async () => {
3771
3771
  console.log(`[S3ClipsSupabase] Fetching clip counts from Supabase for:`, {
@@ -3776,7 +3776,8 @@ var S3ClipsSupabaseService = class {
3776
3776
  const response = await this.fetchWithAuth("count", {
3777
3777
  workspaceId,
3778
3778
  date,
3779
- shift: shiftId.toString()
3779
+ shift: shiftId.toString(),
3780
+ totalOutput
3780
3781
  });
3781
3782
  console.log(`[S3ClipsSupabase] Count API response:`, response);
3782
3783
  const counts = response.counts || {};
@@ -3828,8 +3829,8 @@ var S3ClipsSupabaseService = class {
3828
3829
  /**
3829
3830
  * Get clip counts (simplified version)
3830
3831
  */
3831
- async getClipCounts(workspaceId, date, shiftId) {
3832
- const result = await this.getClipCountsCacheFirst(workspaceId, date, shiftId, false);
3832
+ async getClipCounts(workspaceId, date, shiftId, totalOutput) {
3833
+ const result = await this.getClipCountsCacheFirst(workspaceId, date, shiftId, false, totalOutput);
3833
3834
  if (typeof result === "object" && "counts" in result) {
3834
3835
  return result.counts;
3835
3836
  }
@@ -9020,7 +9021,7 @@ function useClipTypes() {
9020
9021
  refresh: fetchClipTypes
9021
9022
  };
9022
9023
  }
9023
- function useClipTypesWithCounts(workspaceId, date, shiftId) {
9024
+ function useClipTypesWithCounts(workspaceId, date, shiftId, totalOutput) {
9024
9025
  const { clipTypes, isLoading: typesLoading, error: typesError, refresh } = useClipTypes();
9025
9026
  const [counts, setCounts] = React21.useState({});
9026
9027
  const [countsLoading, setCountsLoading] = React21.useState(false);
@@ -9045,7 +9046,8 @@ function useClipTypesWithCounts(workspaceId, date, shiftId) {
9045
9046
  const clipCounts = await s3Service.getClipCounts(
9046
9047
  workspaceId,
9047
9048
  date,
9048
- shiftId.toString()
9049
+ shiftId.toString(),
9050
+ totalOutput
9049
9051
  );
9050
9052
  console.log("[useClipTypesWithCounts] Received counts:", clipCounts);
9051
9053
  setCounts(clipCounts);
@@ -9056,7 +9058,7 @@ function useClipTypesWithCounts(workspaceId, date, shiftId) {
9056
9058
  }
9057
9059
  };
9058
9060
  fetchCounts();
9059
- }, [s3Service, workspaceId, date, shiftId]);
9061
+ }, [s3Service, workspaceId, date, shiftId, totalOutput]);
9060
9062
  return {
9061
9063
  clipTypes: clipTypes.map((type) => ({
9062
9064
  ...type,
@@ -22277,7 +22279,7 @@ var WorkspaceMetricCardsImpl = ({
22277
22279
  /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
22278
22280
  /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
22279
22281
  /* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
22280
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold text-green-500`, children: workspace.avg_cycle_time.toFixed(1) }),
22282
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold ${workspace.avg_cycle_time > (workspace.ideal_cycle_time || 0) ? "text-red-500" : "text-green-500"}`, children: workspace.avg_cycle_time.toFixed(1) }),
22281
22283
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
22282
22284
  "Standard: ",
22283
22285
  workspace.ideal_cycle_time?.toFixed(1) || 0,
@@ -25957,8 +25959,19 @@ var WorkspaceMonthlyHistory = ({
25957
25959
  const daysInMonth = new Date(year, month + 1, 0).getDate();
25958
25960
  const dailyData = [];
25959
25961
  let maxOutput = 0;
25960
- let totalIdealOutput = 0;
25961
- let validDaysCount = 0;
25962
+ let lastSetTarget = 0;
25963
+ for (let day = daysInMonth; day >= 1; day--) {
25964
+ const dayData = data.find((d) => {
25965
+ const date = new Date(d.date);
25966
+ return date.getDate() === day;
25967
+ });
25968
+ const shiftData = dayData ? selectedShift === "day" ? dayData.dayShift : dayData.nightShift : null;
25969
+ const idealOutput = shiftData ? shiftData.idealOutput : 0;
25970
+ if (idealOutput > 0) {
25971
+ lastSetTarget = idealOutput;
25972
+ break;
25973
+ }
25974
+ }
25962
25975
  for (let day = 1; day <= daysInMonth; day++) {
25963
25976
  const dayData = data.find((d) => {
25964
25977
  const date = new Date(d.date);
@@ -25968,11 +25981,7 @@ var WorkspaceMonthlyHistory = ({
25968
25981
  const output = shiftData && hasRealData(shiftData) ? shiftData.output : 0;
25969
25982
  const idealOutput = shiftData ? shiftData.idealOutput : 0;
25970
25983
  if (output > maxOutput) maxOutput = output;
25971
- if (idealOutput > 0) {
25972
- totalIdealOutput += idealOutput;
25973
- validDaysCount++;
25974
- }
25975
- const color2 = output >= idealOutput ? "#00AB45" : "#E34329";
25984
+ const color2 = output >= lastSetTarget ? "#00AB45" : "#E34329";
25976
25985
  dailyData.push({
25977
25986
  hour: getOrdinal(day),
25978
25987
  // Using ordinal format (1st, 2nd, 3rd, etc.)
@@ -25987,14 +25996,13 @@ var WorkspaceMonthlyHistory = ({
25987
25996
  // Not used but keeps structure consistent
25988
25997
  });
25989
25998
  }
25990
- const avgIdealOutput = validDaysCount > 0 ? totalIdealOutput / validDaysCount : 0;
25991
- const calculatedMax = Math.max(maxOutput, avgIdealOutput);
25999
+ const calculatedMax = Math.max(maxOutput, lastSetTarget);
25992
26000
  const yAxisMax = calculatedMax > 0 ? calculatedMax * 1.1 : 100;
25993
- return { data: dailyData, maxOutput, avgIdealOutput, yAxisMax };
26001
+ return { data: dailyData, maxOutput, lastSetTarget, yAxisMax };
25994
26002
  }, [data, month, year, selectedShift]);
25995
26003
  const yAxisTicks = React21.useMemo(() => {
25996
26004
  const max = chartData.yAxisMax;
25997
- const target = chartData.avgIdealOutput;
26005
+ const target = chartData.lastSetTarget;
25998
26006
  if (!max || max <= 0) return void 0;
25999
26007
  const desiredIntervals = 4;
26000
26008
  const roughStep = max / desiredIntervals;
@@ -26012,7 +26020,7 @@ var WorkspaceMonthlyHistory = ({
26012
26020
  ticks.push(Math.round(target));
26013
26021
  }
26014
26022
  return Array.from(new Set(ticks)).filter((v) => v >= 0 && v <= max).sort((a, b) => a - b);
26015
- }, [chartData.yAxisMax, chartData.avgIdealOutput]);
26023
+ }, [chartData.yAxisMax, chartData.lastSetTarget]);
26016
26024
  const pieChartData = React21.useMemo(() => {
26017
26025
  const validShifts = data.map((d) => selectedShift === "day" ? d.dayShift : d.nightShift).filter(hasRealData);
26018
26026
  if (validShifts.length === 0) return [];
@@ -26324,7 +26332,7 @@ var WorkspaceMonthlyHistory = ({
26324
26332
  tick: (props) => {
26325
26333
  const { x, y, payload } = props;
26326
26334
  const value = Math.round(payload.value);
26327
- const targetValue = Math.round(chartData.avgIdealOutput);
26335
+ const targetValue = Math.round(chartData.lastSetTarget);
26328
26336
  const isTarget = Math.abs(value - targetValue) < 1 && targetValue > 0;
26329
26337
  return /* @__PURE__ */ jsxRuntime.jsx(
26330
26338
  "text",
@@ -26348,10 +26356,10 @@ var WorkspaceMonthlyHistory = ({
26348
26356
  content: CustomTooltip
26349
26357
  }
26350
26358
  ),
26351
- chartData.avgIdealOutput > 0 && /* @__PURE__ */ jsxRuntime.jsx(
26359
+ chartData.lastSetTarget > 0 && /* @__PURE__ */ jsxRuntime.jsx(
26352
26360
  recharts.ReferenceLine,
26353
26361
  {
26354
- y: chartData.avgIdealOutput,
26362
+ y: chartData.lastSetTarget,
26355
26363
  stroke: "#E34329",
26356
26364
  strokeDasharray: "5 5",
26357
26365
  strokeWidth: 2
@@ -26409,11 +26417,11 @@ var WorkspaceMonthlyHistory = ({
26409
26417
  ]
26410
26418
  }
26411
26419
  ) }) }),
26412
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center items-center gap-6 mt-3", children: chartData.avgIdealOutput > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
26420
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center items-center gap-6 mt-3", children: chartData.lastSetTarget > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
26413
26421
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-0.5 border-t-2 border-dashed", style: { borderColor: "#E34329" } }),
26414
26422
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-gray-600", children: [
26415
26423
  "Target: ",
26416
- Math.round(chartData.avgIdealOutput),
26424
+ Math.round(chartData.lastSetTarget),
26417
26425
  " units/day"
26418
26426
  ] })
26419
26427
  ] }) })
@@ -27259,6 +27267,36 @@ var TimePickerDropdown = ({
27259
27267
  ] })
27260
27268
  ] });
27261
27269
  };
27270
+ var ERROR_MAPPING = {
27271
+ 1: {
27272
+ // MEDIA_ERR_ABORTED
27273
+ code: 1,
27274
+ type: "recoverable" /* RECOVERABLE */,
27275
+ message: "Video loading was interrupted",
27276
+ canRetry: true
27277
+ },
27278
+ 2: {
27279
+ // MEDIA_ERR_NETWORK
27280
+ code: 2,
27281
+ type: "recoverable" /* RECOVERABLE */,
27282
+ message: "Network error - please check your internet connection",
27283
+ canRetry: true
27284
+ },
27285
+ 3: {
27286
+ // MEDIA_ERR_DECODE
27287
+ code: 3,
27288
+ type: "non_recoverable" /* NON_RECOVERABLE */,
27289
+ message: "Stream corrupted due to internet connection",
27290
+ canRetry: false
27291
+ },
27292
+ 4: {
27293
+ // MEDIA_ERR_SRC_NOT_SUPPORTED
27294
+ code: 4,
27295
+ type: "non_recoverable" /* NON_RECOVERABLE */,
27296
+ message: "Video format not supported by your browser. Please use Google Chrome.",
27297
+ canRetry: false
27298
+ }
27299
+ };
27262
27300
  var videoPlayerStyles = `
27263
27301
  .video-player-container {
27264
27302
  width: 100%;
@@ -27489,8 +27527,21 @@ var VideoPlayer = React21__namespace.default.forwardRef(({
27489
27527
  player.on("seeked", () => onSeeked?.(player));
27490
27528
  player.on("error", () => {
27491
27529
  const error = player.error();
27492
- console.error("Video.js error:", error);
27493
- onError?.(player, error);
27530
+ const errorCode = error?.code ?? 0;
27531
+ const errorInfo = ERROR_MAPPING[errorCode] || {
27532
+ code: errorCode || 0,
27533
+ type: "non_recoverable" /* NON_RECOVERABLE */,
27534
+ message: "Unknown playback error occurred",
27535
+ canRetry: false
27536
+ };
27537
+ console.error("[VideoPlayer] Video.js error:", {
27538
+ code: errorCode,
27539
+ type: errorInfo.type,
27540
+ message: errorInfo.message,
27541
+ canRetry: errorInfo.canRetry,
27542
+ originalError: error
27543
+ });
27544
+ onError?.(player, errorInfo);
27494
27545
  });
27495
27546
  if (src) {
27496
27547
  const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
@@ -28668,7 +28719,7 @@ var FileManagerFilters = ({
28668
28719
  ];
28669
28720
  percentileCategories.forEach((category) => {
28670
28721
  if (category.count > 0 && shouldShowCategory(category.id)) {
28671
- tree.unshift(category);
28722
+ tree.push(category);
28672
28723
  }
28673
28724
  });
28674
28725
  return tree;
@@ -29223,7 +29274,8 @@ var BottlenecksContent = ({
29223
29274
  workspaceName,
29224
29275
  date,
29225
29276
  shift,
29226
- className
29277
+ className,
29278
+ totalOutput
29227
29279
  }) => {
29228
29280
  const dashboardConfig = useDashboardConfig();
29229
29281
  const timezone = useAppTimezone();
@@ -29322,8 +29374,9 @@ var BottlenecksContent = ({
29322
29374
  } = useClipTypesWithCounts(
29323
29375
  workspaceId,
29324
29376
  date || getOperationalDate(timezone),
29325
- effectiveShift
29377
+ effectiveShift,
29326
29378
  // Use same shift as video loading for consistency
29379
+ totalOutput
29327
29380
  );
29328
29381
  console.log("[BottlenecksContent] Clip types data:", {
29329
29382
  clipTypes,
@@ -29368,8 +29421,9 @@ var BottlenecksContent = ({
29368
29421
  const fullResult = await s3ClipsService.getClipCounts(
29369
29422
  workspaceId,
29370
29423
  operationalDate,
29371
- shiftStr
29372
- // Don't build index - use pagination for cost efficiency
29424
+ shiftStr,
29425
+ totalOutput
29426
+ // Pass totalOutput for cycle completion adjustment
29373
29427
  );
29374
29428
  console.log(`[BottlenecksContent] Direct fetch result:`, fullResult);
29375
29429
  if (fullResult) {
@@ -29382,7 +29436,12 @@ var BottlenecksContent = ({
29382
29436
  } catch (err) {
29383
29437
  console.error("[BottlenecksContent] Error fetching clip counts:", err);
29384
29438
  if (isMountedRef.current) {
29385
- setError("Failed to load clip counts. Please try again.");
29439
+ setError({
29440
+ type: "fatal",
29441
+ message: "Failed to load clip counts. Please try again.",
29442
+ canSkip: false,
29443
+ canRetry: true
29444
+ });
29386
29445
  setIsLoading(false);
29387
29446
  }
29388
29447
  } finally {
@@ -29466,7 +29525,12 @@ var BottlenecksContent = ({
29466
29525
  } catch (err) {
29467
29526
  console.error("Error loading first video for category:", err);
29468
29527
  if (isMountedRef.current) {
29469
- setError("Failed to load clips. Please try again.");
29528
+ setError({
29529
+ type: "fatal",
29530
+ message: "Failed to load clips. Please try again.",
29531
+ canSkip: false,
29532
+ canRetry: true
29533
+ });
29470
29534
  setIsCategoryLoading(false);
29471
29535
  }
29472
29536
  } finally {
@@ -29748,7 +29812,12 @@ var BottlenecksContent = ({
29748
29812
  } catch (error2) {
29749
29813
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
29750
29814
  if (isMountedRef.current) {
29751
- setError("Failed to load selected clip. Please try again.");
29815
+ setError({
29816
+ type: "fatal",
29817
+ message: "Failed to load selected clip. Please try again.",
29818
+ canSkip: true,
29819
+ canRetry: true
29820
+ });
29752
29821
  clearLoadingState();
29753
29822
  }
29754
29823
  }
@@ -29771,7 +29840,12 @@ var BottlenecksContent = ({
29771
29840
  }
29772
29841
  } catch (error2) {
29773
29842
  console.error(`[BottlenecksContent] Error in legacy loadAndPlayClip:`, error2);
29774
- setError("Failed to load selected clip. Please try again.");
29843
+ setError({
29844
+ type: "fatal",
29845
+ message: "Failed to load selected clip. Please try again.",
29846
+ canSkip: true,
29847
+ canRetry: true
29848
+ });
29775
29849
  setIsNavigating(false);
29776
29850
  }
29777
29851
  }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById]);
@@ -29811,7 +29885,12 @@ var BottlenecksContent = ({
29811
29885
  }
29812
29886
  } catch (error2) {
29813
29887
  console.error(`[handleNext] Error navigating:`, error2);
29814
- setError("Failed to navigate to next clip");
29888
+ setError({
29889
+ type: "fatal",
29890
+ message: "Failed to navigate to next clip",
29891
+ canSkip: true,
29892
+ canRetry: true
29893
+ });
29815
29894
  clearLoadingState();
29816
29895
  }
29817
29896
  }, [clearLoadingState, s3ClipsService]);
@@ -29847,7 +29926,12 @@ var BottlenecksContent = ({
29847
29926
  }
29848
29927
  } catch (error2) {
29849
29928
  console.error(`[handlePrevious] Error navigating:`, error2);
29850
- setError("Failed to navigate to previous clip");
29929
+ setError({
29930
+ type: "fatal",
29931
+ message: "Failed to navigate to previous clip",
29932
+ canSkip: true,
29933
+ canRetry: true
29934
+ });
29851
29935
  clearLoadingState();
29852
29936
  }
29853
29937
  }, [clearLoadingState, s3ClipsService]);
@@ -29860,7 +29944,7 @@ var BottlenecksContent = ({
29860
29944
  const handleVideoReady = React21.useCallback((player) => {
29861
29945
  console.log("Video.js player ready - NOT clearing loading (wait for playing event)");
29862
29946
  videoRetryCountRef.current = 0;
29863
- if (error?.includes("Retrying")) {
29947
+ if (error?.isRetrying) {
29864
29948
  setError(null);
29865
29949
  }
29866
29950
  }, [error]);
@@ -29905,20 +29989,53 @@ var BottlenecksContent = ({
29905
29989
  }, [clearLoadingState]);
29906
29990
  const handleVideoLoadingChange = React21.useCallback((isLoading2) => {
29907
29991
  console.log(`[BottlenecksContent] Video loading state changed: ${isLoading2}`);
29992
+ if (error && error.type === "fatal") {
29993
+ console.log(`[BottlenecksContent] Ignoring loading state change - fatal error is showing`);
29994
+ return;
29995
+ }
29908
29996
  setIsVideoBuffering(isLoading2);
29909
- }, []);
29997
+ }, [error]);
29910
29998
  const handleVideoEnded = React21.useCallback((player) => {
29911
29999
  handleNext();
29912
30000
  }, [handleNext]);
29913
30001
  const videoRetryCountRef = React21.useRef(0);
29914
- const handleVideoError = React21.useCallback((player, error2) => {
29915
- console.error("Video.js error:", error2);
30002
+ const handleVideoError = React21.useCallback((player, errorInfo) => {
30003
+ console.error("[BottlenecksContent] Video.js error:", errorInfo);
29916
30004
  setIsPlaying(false);
30005
+ setIsVideoBuffering(false);
30006
+ const errorCode = errorInfo?.code || 0;
30007
+ const canRetry = errorInfo?.canRetry ?? false;
30008
+ const errorMessage = errorInfo?.message || "Unknown error";
30009
+ console.log(`[Video Error] Code: ${errorCode}, Can Retry: ${canRetry}, Message: ${errorMessage}`);
30010
+ if (!canRetry) {
30011
+ console.log("[Video Error] Non-recoverable error - showing error overlay immediately");
30012
+ setError({
30013
+ type: "fatal",
30014
+ code: errorCode,
30015
+ message: errorMessage,
30016
+ canSkip: true,
30017
+ canRetry: false
30018
+ });
30019
+ clearLoadingState();
30020
+ videoRetryCountRef.current = 0;
30021
+ trackCoreEvent("clips_video_error_non_recoverable", {
30022
+ workspaceId,
30023
+ category: activeFilterRef.current,
30024
+ videoId: currentVideo?.id,
30025
+ errorCode,
30026
+ errorMessage
30027
+ });
30028
+ return;
30029
+ }
29917
30030
  if (videoRetryCountRef.current < 3 && currentVideo) {
29918
30031
  videoRetryCountRef.current++;
29919
30032
  const retryDelay = 1e3 * videoRetryCountRef.current;
29920
- console.log(`[Video Error] Retrying... Attempt ${videoRetryCountRef.current}/3 in ${retryDelay}ms`);
29921
- setError(`Retrying... (${videoRetryCountRef.current}/3)`);
30033
+ console.log(`[Video Error] Recoverable error - Retrying... Attempt ${videoRetryCountRef.current}/3 in ${retryDelay}ms`);
30034
+ setError({
30035
+ type: "retrying",
30036
+ message: `Retrying... (${videoRetryCountRef.current}/3)`,
30037
+ isRetrying: true
30038
+ });
29922
30039
  setTimeout(() => {
29923
30040
  if (videoRef.current && currentVideo && isMountedRef.current) {
29924
30041
  setError(null);
@@ -29926,16 +30043,26 @@ var BottlenecksContent = ({
29926
30043
  }
29927
30044
  }, retryDelay);
29928
30045
  } else {
29929
- setError("Video failed to load after 3 attempts. The stream may be corrupted.");
30046
+ console.log("[Video Error] Retries exhausted - showing final error overlay");
30047
+ setError({
30048
+ type: "fatal",
30049
+ code: errorCode,
30050
+ message: errorMessage,
30051
+ canSkip: true,
30052
+ canRetry: true
30053
+ // Allow manual retry for network errors
30054
+ });
29930
30055
  videoRetryCountRef.current = 0;
29931
30056
  trackCoreEvent("clips_video_error_final", {
29932
30057
  workspaceId,
29933
30058
  category: activeFilterRef.current,
29934
30059
  videoId: currentVideo?.id,
29935
- attempts: videoRetryCountRef.current
30060
+ errorCode,
30061
+ errorMessage,
30062
+ attempts: 3
29936
30063
  });
29937
30064
  }
29938
- }, [currentVideo, workspaceId]);
30065
+ }, [currentVideo, workspaceId, clearLoadingState]);
29939
30066
  React21.useEffect(() => {
29940
30067
  isMountedRef.current = true;
29941
30068
  return () => {
@@ -29953,9 +30080,13 @@ var BottlenecksContent = ({
29953
30080
  }, [s3ClipsService]);
29954
30081
  React21.useEffect(() => {
29955
30082
  if (filteredVideos.length > 0 && currentIndex < filteredVideos.length) {
30083
+ if (error && error.type === "fatal") {
30084
+ console.log("[BottlenecksContent] Not clearing fatal error on video change - let user handle it");
30085
+ return;
30086
+ }
29956
30087
  setError(null);
29957
30088
  }
29958
- }, [currentIndex, filteredVideos]);
30089
+ }, [currentIndex, filteredVideos, error]);
29959
30090
  React21.useEffect(() => {
29960
30091
  if (!isTransitioning && pendingVideo) {
29961
30092
  const timer = setTimeout(() => {
@@ -30030,11 +30161,11 @@ var BottlenecksContent = ({
30030
30161
  if ((isLoading || clipTypesLoading) && allVideos.length === 0 && Object.keys(mergedCounts).length === 0) {
30031
30162
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-grow p-4 flex items-center justify-center h-[calc(100vh-12rem)]", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading clips..." }) });
30032
30163
  }
30033
- if (error || clipTypesError) {
30164
+ if (error && error.type === "fatal" && !hasInitialLoad || clipTypesError) {
30034
30165
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-grow p-4 flex flex-col items-center justify-center h-[calc(100vh-12rem)] text-center", children: [
30035
30166
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
30036
30167
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-red-700 mb-1", children: "Error Loading Clips" }),
30037
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 max-w-md", children: error || clipTypesError })
30168
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 max-w-md", children: error?.message || clipTypesError })
30038
30169
  ] });
30039
30170
  }
30040
30171
  const categoriesToShow = clipTypes.length > 0 ? clipTypes : [];
@@ -30081,7 +30212,7 @@ var BottlenecksContent = ({
30081
30212
  ),
30082
30213
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [
30083
30214
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm px-2 py-1 bg-blue-50 text-blue-700 rounded-full font-medium tabular-nums", children: categoryMetadata.length > 0 ? `${currentMetadataIndex + 1} / ${categoryMetadata.length}` : "0 / 0" }),
30084
- error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-red-600 font-medium", children: error })
30215
+ error && error.type === "retrying" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-orange-600 font-medium", children: error.message })
30085
30216
  ] }),
30086
30217
  /* @__PURE__ */ jsxRuntime.jsx(
30087
30218
  "button",
@@ -30095,7 +30226,6 @@ var BottlenecksContent = ({
30095
30226
  )
30096
30227
  ] })
30097
30228
  ] }) }),
30098
- /* Show video if we have filtered videos and current video, or show loading */
30099
30229
  filteredVideos.length > 0 && currentVideo ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-full group", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900", children: [
30100
30230
  /* @__PURE__ */ jsxRuntime.jsx(
30101
30231
  CroppedVideoPlayer,
@@ -30127,30 +30257,44 @@ var BottlenecksContent = ({
30127
30257
  }
30128
30258
  }
30129
30259
  ),
30130
- (isTransitioning || isVideoBuffering && isInitialLoading) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
30131
- !isTransitioning && isVideoBuffering && !isInitialLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
30132
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/80 text-white p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center max-w-md", children: [
30260
+ (isTransitioning || isVideoBuffering && isInitialLoading) && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
30261
+ !isTransitioning && isVideoBuffering && !isInitialLoading && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
30262
+ error && error.type === "retrying" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-40 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
30263
+ /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md" }),
30264
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-white text-sm mt-4 font-medium", children: error.message })
30265
+ ] }) }),
30266
+ error && error.type === "fatal" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-50 flex items-center justify-center bg-black/90 text-white p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center max-w-md", children: [
30133
30267
  /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-16 h-16 mx-auto mb-4 text-red-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 16.5c-.77.833.192 2.5 1.732 2.5z" }) }),
30134
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-2", children: "Video Stream Error" }),
30135
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-300 mb-4", children: "The video stream appears to be corrupted or unavailable. This may be due to:" }),
30136
- /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "text-sm text-gray-300 text-left space-y-1 mb-4", children: [
30137
- /* @__PURE__ */ jsxRuntime.jsx("li", { children: "\u2022 Incomplete video encoding" }),
30138
- /* @__PURE__ */ jsxRuntime.jsx("li", { children: "\u2022 Network connectivity issues" }),
30139
- /* @__PURE__ */ jsxRuntime.jsx("li", { children: "\u2022 Server processing errors" })
30140
- ] }),
30141
- /* @__PURE__ */ jsxRuntime.jsx(
30142
- "button",
30143
- {
30144
- onClick: () => {
30145
- setError(null);
30146
- if (videoRef.current) {
30147
- videoRef.current.dispose();
30148
- }
30149
- },
30150
- className: "px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm font-medium",
30151
- children: "Retry"
30152
- }
30153
- )
30268
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-2", children: error.code === 3 ? "Stream Corrupted" : error.code === 4 ? "Format Not Supported" : error.code === 2 ? "Network Error" : error.code === 1 ? "Loading Interrupted" : "Playback Error" }),
30269
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-300 mb-6", children: error.message }),
30270
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 justify-center", children: [
30271
+ error.canSkip && /* @__PURE__ */ jsxRuntime.jsx(
30272
+ "button",
30273
+ {
30274
+ onClick: () => {
30275
+ setError(null);
30276
+ videoRetryCountRef.current = 0;
30277
+ handleNext();
30278
+ },
30279
+ className: "px-5 py-2.5 bg-blue-600 hover:bg-blue-700 rounded-md text-sm font-medium transition-colors",
30280
+ children: "Skip to Next Clip"
30281
+ }
30282
+ ),
30283
+ error.canRetry && /* @__PURE__ */ jsxRuntime.jsx(
30284
+ "button",
30285
+ {
30286
+ onClick: () => {
30287
+ setError(null);
30288
+ videoRetryCountRef.current = 0;
30289
+ if (videoRef.current) {
30290
+ videoRef.current.dispose();
30291
+ }
30292
+ },
30293
+ className: "px-5 py-2.5 bg-gray-600 hover:bg-gray-700 rounded-md text-sm font-medium transition-colors",
30294
+ children: "Retry"
30295
+ }
30296
+ )
30297
+ ] })
30154
30298
  ] }) }),
30155
30299
  (currentVideo.type === "cycle_completion" || currentVideo.type === "bottleneck" && currentVideo.description.toLowerCase().includes("cycle time")) && currentVideo.cycle_time_seconds || currentVideo.type === "idle_time" || currentVideo.type === "low_value" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-3 left-3 z-10 bg-black/60 backdrop-blur-sm px-3 py-1.5 rounded-lg text-white shadow-lg text-xs", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
30156
30300
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex-shrink-0 h-2.5 w-2.5 rounded-full ${currentVideo.type === "low_value" || currentVideo.type === "idle_time" ? "bg-purple-400" : isPercentileCategory(activeFilterRef.current) ? activeFilterRef.current === "fast-cycles" ? "bg-green-600" : activeFilterRef.current === "slow-cycles" ? "bg-red-700" : "bg-orange-500" : currentVideo.type === "cycle_completion" ? "bg-blue-600" : "bg-gray-500"} mr-2 animate-pulse` }),
@@ -42659,7 +42803,7 @@ var WorkspaceDetailView = ({
42659
42803
  /* @__PURE__ */ jsxRuntime.jsxs(Card2, { children: [
42660
42804
  /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
42661
42805
  /* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
42662
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold text-green-500`, children: workspace.avg_cycle_time.toFixed(1) }),
42806
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold ${workspace.avg_cycle_time > (workspace.ideal_cycle_time || 0) ? "text-red-500" : "text-green-500"}`, children: workspace.avg_cycle_time.toFixed(1) }),
42663
42807
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
42664
42808
  "Standard: ",
42665
42809
  workspace.ideal_cycle_time?.toFixed(1) || 0,
@@ -42779,7 +42923,7 @@ var WorkspaceDetailView = ({
42779
42923
  /* @__PURE__ */ jsxRuntime.jsxs(Card2, { children: [
42780
42924
  /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
42781
42925
  /* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
42782
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold text-green-500`, children: workspace.avg_cycle_time.toFixed(1) }),
42926
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-5xl font-bold ${workspace.avg_cycle_time > (workspace.ideal_cycle_time || 0) ? "text-red-500" : "text-green-500"}`, children: workspace.avg_cycle_time.toFixed(1) }),
42783
42927
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
42784
42928
  "Standard: ",
42785
42929
  workspace.ideal_cycle_time?.toFixed(1) || 0,
@@ -42854,6 +42998,7 @@ var WorkspaceDetailView = ({
42854
42998
  workspaceName: formattedWorkspaceName,
42855
42999
  date,
42856
43000
  shift,
43001
+ totalOutput: workspace?.total_actions,
42857
43002
  className: "h-[calc(100vh-10rem)]"
42858
43003
  }
42859
43004
  ) })
package/dist/index.mjs CHANGED
@@ -3735,7 +3735,7 @@ var S3ClipsSupabaseService = class {
3735
3735
  /**
3736
3736
  * Get clip counts with optional video index
3737
3737
  */
3738
- async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex = false) {
3738
+ async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex = false, totalOutput) {
3739
3739
  const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
3740
3740
  return this.deduplicate(cacheKey, async () => {
3741
3741
  console.log(`[S3ClipsSupabase] Fetching clip counts from Supabase for:`, {
@@ -3746,7 +3746,8 @@ var S3ClipsSupabaseService = class {
3746
3746
  const response = await this.fetchWithAuth("count", {
3747
3747
  workspaceId,
3748
3748
  date,
3749
- shift: shiftId.toString()
3749
+ shift: shiftId.toString(),
3750
+ totalOutput
3750
3751
  });
3751
3752
  console.log(`[S3ClipsSupabase] Count API response:`, response);
3752
3753
  const counts = response.counts || {};
@@ -3798,8 +3799,8 @@ var S3ClipsSupabaseService = class {
3798
3799
  /**
3799
3800
  * Get clip counts (simplified version)
3800
3801
  */
3801
- async getClipCounts(workspaceId, date, shiftId) {
3802
- const result = await this.getClipCountsCacheFirst(workspaceId, date, shiftId, false);
3802
+ async getClipCounts(workspaceId, date, shiftId, totalOutput) {
3803
+ const result = await this.getClipCountsCacheFirst(workspaceId, date, shiftId, false, totalOutput);
3803
3804
  if (typeof result === "object" && "counts" in result) {
3804
3805
  return result.counts;
3805
3806
  }
@@ -8990,7 +8991,7 @@ function useClipTypes() {
8990
8991
  refresh: fetchClipTypes
8991
8992
  };
8992
8993
  }
8993
- function useClipTypesWithCounts(workspaceId, date, shiftId) {
8994
+ function useClipTypesWithCounts(workspaceId, date, shiftId, totalOutput) {
8994
8995
  const { clipTypes, isLoading: typesLoading, error: typesError, refresh } = useClipTypes();
8995
8996
  const [counts, setCounts] = useState({});
8996
8997
  const [countsLoading, setCountsLoading] = useState(false);
@@ -9015,7 +9016,8 @@ function useClipTypesWithCounts(workspaceId, date, shiftId) {
9015
9016
  const clipCounts = await s3Service.getClipCounts(
9016
9017
  workspaceId,
9017
9018
  date,
9018
- shiftId.toString()
9019
+ shiftId.toString(),
9020
+ totalOutput
9019
9021
  );
9020
9022
  console.log("[useClipTypesWithCounts] Received counts:", clipCounts);
9021
9023
  setCounts(clipCounts);
@@ -9026,7 +9028,7 @@ function useClipTypesWithCounts(workspaceId, date, shiftId) {
9026
9028
  }
9027
9029
  };
9028
9030
  fetchCounts();
9029
- }, [s3Service, workspaceId, date, shiftId]);
9031
+ }, [s3Service, workspaceId, date, shiftId, totalOutput]);
9030
9032
  return {
9031
9033
  clipTypes: clipTypes.map((type) => ({
9032
9034
  ...type,
@@ -22247,7 +22249,7 @@ var WorkspaceMetricCardsImpl = ({
22247
22249
  /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
22248
22250
  /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
22249
22251
  /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6 sm:py-3", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
22250
- /* @__PURE__ */ jsx("p", { className: `text-5xl font-bold text-green-500`, children: workspace.avg_cycle_time.toFixed(1) }),
22252
+ /* @__PURE__ */ jsx("p", { className: `text-5xl font-bold ${workspace.avg_cycle_time > (workspace.ideal_cycle_time || 0) ? "text-red-500" : "text-green-500"}`, children: workspace.avg_cycle_time.toFixed(1) }),
22251
22253
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
22252
22254
  "Standard: ",
22253
22255
  workspace.ideal_cycle_time?.toFixed(1) || 0,
@@ -25927,8 +25929,19 @@ var WorkspaceMonthlyHistory = ({
25927
25929
  const daysInMonth = new Date(year, month + 1, 0).getDate();
25928
25930
  const dailyData = [];
25929
25931
  let maxOutput = 0;
25930
- let totalIdealOutput = 0;
25931
- let validDaysCount = 0;
25932
+ let lastSetTarget = 0;
25933
+ for (let day = daysInMonth; day >= 1; day--) {
25934
+ const dayData = data.find((d) => {
25935
+ const date = new Date(d.date);
25936
+ return date.getDate() === day;
25937
+ });
25938
+ const shiftData = dayData ? selectedShift === "day" ? dayData.dayShift : dayData.nightShift : null;
25939
+ const idealOutput = shiftData ? shiftData.idealOutput : 0;
25940
+ if (idealOutput > 0) {
25941
+ lastSetTarget = idealOutput;
25942
+ break;
25943
+ }
25944
+ }
25932
25945
  for (let day = 1; day <= daysInMonth; day++) {
25933
25946
  const dayData = data.find((d) => {
25934
25947
  const date = new Date(d.date);
@@ -25938,11 +25951,7 @@ var WorkspaceMonthlyHistory = ({
25938
25951
  const output = shiftData && hasRealData(shiftData) ? shiftData.output : 0;
25939
25952
  const idealOutput = shiftData ? shiftData.idealOutput : 0;
25940
25953
  if (output > maxOutput) maxOutput = output;
25941
- if (idealOutput > 0) {
25942
- totalIdealOutput += idealOutput;
25943
- validDaysCount++;
25944
- }
25945
- const color2 = output >= idealOutput ? "#00AB45" : "#E34329";
25954
+ const color2 = output >= lastSetTarget ? "#00AB45" : "#E34329";
25946
25955
  dailyData.push({
25947
25956
  hour: getOrdinal(day),
25948
25957
  // Using ordinal format (1st, 2nd, 3rd, etc.)
@@ -25957,14 +25966,13 @@ var WorkspaceMonthlyHistory = ({
25957
25966
  // Not used but keeps structure consistent
25958
25967
  });
25959
25968
  }
25960
- const avgIdealOutput = validDaysCount > 0 ? totalIdealOutput / validDaysCount : 0;
25961
- const calculatedMax = Math.max(maxOutput, avgIdealOutput);
25969
+ const calculatedMax = Math.max(maxOutput, lastSetTarget);
25962
25970
  const yAxisMax = calculatedMax > 0 ? calculatedMax * 1.1 : 100;
25963
- return { data: dailyData, maxOutput, avgIdealOutput, yAxisMax };
25971
+ return { data: dailyData, maxOutput, lastSetTarget, yAxisMax };
25964
25972
  }, [data, month, year, selectedShift]);
25965
25973
  const yAxisTicks = useMemo(() => {
25966
25974
  const max = chartData.yAxisMax;
25967
- const target = chartData.avgIdealOutput;
25975
+ const target = chartData.lastSetTarget;
25968
25976
  if (!max || max <= 0) return void 0;
25969
25977
  const desiredIntervals = 4;
25970
25978
  const roughStep = max / desiredIntervals;
@@ -25982,7 +25990,7 @@ var WorkspaceMonthlyHistory = ({
25982
25990
  ticks.push(Math.round(target));
25983
25991
  }
25984
25992
  return Array.from(new Set(ticks)).filter((v) => v >= 0 && v <= max).sort((a, b) => a - b);
25985
- }, [chartData.yAxisMax, chartData.avgIdealOutput]);
25993
+ }, [chartData.yAxisMax, chartData.lastSetTarget]);
25986
25994
  const pieChartData = useMemo(() => {
25987
25995
  const validShifts = data.map((d) => selectedShift === "day" ? d.dayShift : d.nightShift).filter(hasRealData);
25988
25996
  if (validShifts.length === 0) return [];
@@ -26294,7 +26302,7 @@ var WorkspaceMonthlyHistory = ({
26294
26302
  tick: (props) => {
26295
26303
  const { x, y, payload } = props;
26296
26304
  const value = Math.round(payload.value);
26297
- const targetValue = Math.round(chartData.avgIdealOutput);
26305
+ const targetValue = Math.round(chartData.lastSetTarget);
26298
26306
  const isTarget = Math.abs(value - targetValue) < 1 && targetValue > 0;
26299
26307
  return /* @__PURE__ */ jsx(
26300
26308
  "text",
@@ -26318,10 +26326,10 @@ var WorkspaceMonthlyHistory = ({
26318
26326
  content: CustomTooltip
26319
26327
  }
26320
26328
  ),
26321
- chartData.avgIdealOutput > 0 && /* @__PURE__ */ jsx(
26329
+ chartData.lastSetTarget > 0 && /* @__PURE__ */ jsx(
26322
26330
  ReferenceLine,
26323
26331
  {
26324
- y: chartData.avgIdealOutput,
26332
+ y: chartData.lastSetTarget,
26325
26333
  stroke: "#E34329",
26326
26334
  strokeDasharray: "5 5",
26327
26335
  strokeWidth: 2
@@ -26379,11 +26387,11 @@ var WorkspaceMonthlyHistory = ({
26379
26387
  ]
26380
26388
  }
26381
26389
  ) }) }),
26382
- /* @__PURE__ */ jsx("div", { className: "flex justify-center items-center gap-6 mt-3", children: chartData.avgIdealOutput > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
26390
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center items-center gap-6 mt-3", children: chartData.lastSetTarget > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
26383
26391
  /* @__PURE__ */ jsx("div", { className: "w-12 h-0.5 border-t-2 border-dashed", style: { borderColor: "#E34329" } }),
26384
26392
  /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-600", children: [
26385
26393
  "Target: ",
26386
- Math.round(chartData.avgIdealOutput),
26394
+ Math.round(chartData.lastSetTarget),
26387
26395
  " units/day"
26388
26396
  ] })
26389
26397
  ] }) })
@@ -27229,6 +27237,36 @@ var TimePickerDropdown = ({
27229
27237
  ] })
27230
27238
  ] });
27231
27239
  };
27240
+ var ERROR_MAPPING = {
27241
+ 1: {
27242
+ // MEDIA_ERR_ABORTED
27243
+ code: 1,
27244
+ type: "recoverable" /* RECOVERABLE */,
27245
+ message: "Video loading was interrupted",
27246
+ canRetry: true
27247
+ },
27248
+ 2: {
27249
+ // MEDIA_ERR_NETWORK
27250
+ code: 2,
27251
+ type: "recoverable" /* RECOVERABLE */,
27252
+ message: "Network error - please check your internet connection",
27253
+ canRetry: true
27254
+ },
27255
+ 3: {
27256
+ // MEDIA_ERR_DECODE
27257
+ code: 3,
27258
+ type: "non_recoverable" /* NON_RECOVERABLE */,
27259
+ message: "Stream corrupted due to internet connection",
27260
+ canRetry: false
27261
+ },
27262
+ 4: {
27263
+ // MEDIA_ERR_SRC_NOT_SUPPORTED
27264
+ code: 4,
27265
+ type: "non_recoverable" /* NON_RECOVERABLE */,
27266
+ message: "Video format not supported by your browser. Please use Google Chrome.",
27267
+ canRetry: false
27268
+ }
27269
+ };
27232
27270
  var videoPlayerStyles = `
27233
27271
  .video-player-container {
27234
27272
  width: 100%;
@@ -27459,8 +27497,21 @@ var VideoPlayer = React21__default.forwardRef(({
27459
27497
  player.on("seeked", () => onSeeked?.(player));
27460
27498
  player.on("error", () => {
27461
27499
  const error = player.error();
27462
- console.error("Video.js error:", error);
27463
- onError?.(player, error);
27500
+ const errorCode = error?.code ?? 0;
27501
+ const errorInfo = ERROR_MAPPING[errorCode] || {
27502
+ code: errorCode || 0,
27503
+ type: "non_recoverable" /* NON_RECOVERABLE */,
27504
+ message: "Unknown playback error occurred",
27505
+ canRetry: false
27506
+ };
27507
+ console.error("[VideoPlayer] Video.js error:", {
27508
+ code: errorCode,
27509
+ type: errorInfo.type,
27510
+ message: errorInfo.message,
27511
+ canRetry: errorInfo.canRetry,
27512
+ originalError: error
27513
+ });
27514
+ onError?.(player, errorInfo);
27464
27515
  });
27465
27516
  if (src) {
27466
27517
  const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
@@ -28638,7 +28689,7 @@ var FileManagerFilters = ({
28638
28689
  ];
28639
28690
  percentileCategories.forEach((category) => {
28640
28691
  if (category.count > 0 && shouldShowCategory(category.id)) {
28641
- tree.unshift(category);
28692
+ tree.push(category);
28642
28693
  }
28643
28694
  });
28644
28695
  return tree;
@@ -29193,7 +29244,8 @@ var BottlenecksContent = ({
29193
29244
  workspaceName,
29194
29245
  date,
29195
29246
  shift,
29196
- className
29247
+ className,
29248
+ totalOutput
29197
29249
  }) => {
29198
29250
  const dashboardConfig = useDashboardConfig();
29199
29251
  const timezone = useAppTimezone();
@@ -29292,8 +29344,9 @@ var BottlenecksContent = ({
29292
29344
  } = useClipTypesWithCounts(
29293
29345
  workspaceId,
29294
29346
  date || getOperationalDate(timezone),
29295
- effectiveShift
29347
+ effectiveShift,
29296
29348
  // Use same shift as video loading for consistency
29349
+ totalOutput
29297
29350
  );
29298
29351
  console.log("[BottlenecksContent] Clip types data:", {
29299
29352
  clipTypes,
@@ -29338,8 +29391,9 @@ var BottlenecksContent = ({
29338
29391
  const fullResult = await s3ClipsService.getClipCounts(
29339
29392
  workspaceId,
29340
29393
  operationalDate,
29341
- shiftStr
29342
- // Don't build index - use pagination for cost efficiency
29394
+ shiftStr,
29395
+ totalOutput
29396
+ // Pass totalOutput for cycle completion adjustment
29343
29397
  );
29344
29398
  console.log(`[BottlenecksContent] Direct fetch result:`, fullResult);
29345
29399
  if (fullResult) {
@@ -29352,7 +29406,12 @@ var BottlenecksContent = ({
29352
29406
  } catch (err) {
29353
29407
  console.error("[BottlenecksContent] Error fetching clip counts:", err);
29354
29408
  if (isMountedRef.current) {
29355
- setError("Failed to load clip counts. Please try again.");
29409
+ setError({
29410
+ type: "fatal",
29411
+ message: "Failed to load clip counts. Please try again.",
29412
+ canSkip: false,
29413
+ canRetry: true
29414
+ });
29356
29415
  setIsLoading(false);
29357
29416
  }
29358
29417
  } finally {
@@ -29436,7 +29495,12 @@ var BottlenecksContent = ({
29436
29495
  } catch (err) {
29437
29496
  console.error("Error loading first video for category:", err);
29438
29497
  if (isMountedRef.current) {
29439
- setError("Failed to load clips. Please try again.");
29498
+ setError({
29499
+ type: "fatal",
29500
+ message: "Failed to load clips. Please try again.",
29501
+ canSkip: false,
29502
+ canRetry: true
29503
+ });
29440
29504
  setIsCategoryLoading(false);
29441
29505
  }
29442
29506
  } finally {
@@ -29718,7 +29782,12 @@ var BottlenecksContent = ({
29718
29782
  } catch (error2) {
29719
29783
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
29720
29784
  if (isMountedRef.current) {
29721
- setError("Failed to load selected clip. Please try again.");
29785
+ setError({
29786
+ type: "fatal",
29787
+ message: "Failed to load selected clip. Please try again.",
29788
+ canSkip: true,
29789
+ canRetry: true
29790
+ });
29722
29791
  clearLoadingState();
29723
29792
  }
29724
29793
  }
@@ -29741,7 +29810,12 @@ var BottlenecksContent = ({
29741
29810
  }
29742
29811
  } catch (error2) {
29743
29812
  console.error(`[BottlenecksContent] Error in legacy loadAndPlayClip:`, error2);
29744
- setError("Failed to load selected clip. Please try again.");
29813
+ setError({
29814
+ type: "fatal",
29815
+ message: "Failed to load selected clip. Please try again.",
29816
+ canSkip: true,
29817
+ canRetry: true
29818
+ });
29745
29819
  setIsNavigating(false);
29746
29820
  }
29747
29821
  }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById]);
@@ -29781,7 +29855,12 @@ var BottlenecksContent = ({
29781
29855
  }
29782
29856
  } catch (error2) {
29783
29857
  console.error(`[handleNext] Error navigating:`, error2);
29784
- setError("Failed to navigate to next clip");
29858
+ setError({
29859
+ type: "fatal",
29860
+ message: "Failed to navigate to next clip",
29861
+ canSkip: true,
29862
+ canRetry: true
29863
+ });
29785
29864
  clearLoadingState();
29786
29865
  }
29787
29866
  }, [clearLoadingState, s3ClipsService]);
@@ -29817,7 +29896,12 @@ var BottlenecksContent = ({
29817
29896
  }
29818
29897
  } catch (error2) {
29819
29898
  console.error(`[handlePrevious] Error navigating:`, error2);
29820
- setError("Failed to navigate to previous clip");
29899
+ setError({
29900
+ type: "fatal",
29901
+ message: "Failed to navigate to previous clip",
29902
+ canSkip: true,
29903
+ canRetry: true
29904
+ });
29821
29905
  clearLoadingState();
29822
29906
  }
29823
29907
  }, [clearLoadingState, s3ClipsService]);
@@ -29830,7 +29914,7 @@ var BottlenecksContent = ({
29830
29914
  const handleVideoReady = useCallback((player) => {
29831
29915
  console.log("Video.js player ready - NOT clearing loading (wait for playing event)");
29832
29916
  videoRetryCountRef.current = 0;
29833
- if (error?.includes("Retrying")) {
29917
+ if (error?.isRetrying) {
29834
29918
  setError(null);
29835
29919
  }
29836
29920
  }, [error]);
@@ -29875,20 +29959,53 @@ var BottlenecksContent = ({
29875
29959
  }, [clearLoadingState]);
29876
29960
  const handleVideoLoadingChange = useCallback((isLoading2) => {
29877
29961
  console.log(`[BottlenecksContent] Video loading state changed: ${isLoading2}`);
29962
+ if (error && error.type === "fatal") {
29963
+ console.log(`[BottlenecksContent] Ignoring loading state change - fatal error is showing`);
29964
+ return;
29965
+ }
29878
29966
  setIsVideoBuffering(isLoading2);
29879
- }, []);
29967
+ }, [error]);
29880
29968
  const handleVideoEnded = useCallback((player) => {
29881
29969
  handleNext();
29882
29970
  }, [handleNext]);
29883
29971
  const videoRetryCountRef = useRef(0);
29884
- const handleVideoError = useCallback((player, error2) => {
29885
- console.error("Video.js error:", error2);
29972
+ const handleVideoError = useCallback((player, errorInfo) => {
29973
+ console.error("[BottlenecksContent] Video.js error:", errorInfo);
29886
29974
  setIsPlaying(false);
29975
+ setIsVideoBuffering(false);
29976
+ const errorCode = errorInfo?.code || 0;
29977
+ const canRetry = errorInfo?.canRetry ?? false;
29978
+ const errorMessage = errorInfo?.message || "Unknown error";
29979
+ console.log(`[Video Error] Code: ${errorCode}, Can Retry: ${canRetry}, Message: ${errorMessage}`);
29980
+ if (!canRetry) {
29981
+ console.log("[Video Error] Non-recoverable error - showing error overlay immediately");
29982
+ setError({
29983
+ type: "fatal",
29984
+ code: errorCode,
29985
+ message: errorMessage,
29986
+ canSkip: true,
29987
+ canRetry: false
29988
+ });
29989
+ clearLoadingState();
29990
+ videoRetryCountRef.current = 0;
29991
+ trackCoreEvent("clips_video_error_non_recoverable", {
29992
+ workspaceId,
29993
+ category: activeFilterRef.current,
29994
+ videoId: currentVideo?.id,
29995
+ errorCode,
29996
+ errorMessage
29997
+ });
29998
+ return;
29999
+ }
29887
30000
  if (videoRetryCountRef.current < 3 && currentVideo) {
29888
30001
  videoRetryCountRef.current++;
29889
30002
  const retryDelay = 1e3 * videoRetryCountRef.current;
29890
- console.log(`[Video Error] Retrying... Attempt ${videoRetryCountRef.current}/3 in ${retryDelay}ms`);
29891
- setError(`Retrying... (${videoRetryCountRef.current}/3)`);
30003
+ console.log(`[Video Error] Recoverable error - Retrying... Attempt ${videoRetryCountRef.current}/3 in ${retryDelay}ms`);
30004
+ setError({
30005
+ type: "retrying",
30006
+ message: `Retrying... (${videoRetryCountRef.current}/3)`,
30007
+ isRetrying: true
30008
+ });
29892
30009
  setTimeout(() => {
29893
30010
  if (videoRef.current && currentVideo && isMountedRef.current) {
29894
30011
  setError(null);
@@ -29896,16 +30013,26 @@ var BottlenecksContent = ({
29896
30013
  }
29897
30014
  }, retryDelay);
29898
30015
  } else {
29899
- setError("Video failed to load after 3 attempts. The stream may be corrupted.");
30016
+ console.log("[Video Error] Retries exhausted - showing final error overlay");
30017
+ setError({
30018
+ type: "fatal",
30019
+ code: errorCode,
30020
+ message: errorMessage,
30021
+ canSkip: true,
30022
+ canRetry: true
30023
+ // Allow manual retry for network errors
30024
+ });
29900
30025
  videoRetryCountRef.current = 0;
29901
30026
  trackCoreEvent("clips_video_error_final", {
29902
30027
  workspaceId,
29903
30028
  category: activeFilterRef.current,
29904
30029
  videoId: currentVideo?.id,
29905
- attempts: videoRetryCountRef.current
30030
+ errorCode,
30031
+ errorMessage,
30032
+ attempts: 3
29906
30033
  });
29907
30034
  }
29908
- }, [currentVideo, workspaceId]);
30035
+ }, [currentVideo, workspaceId, clearLoadingState]);
29909
30036
  useEffect(() => {
29910
30037
  isMountedRef.current = true;
29911
30038
  return () => {
@@ -29923,9 +30050,13 @@ var BottlenecksContent = ({
29923
30050
  }, [s3ClipsService]);
29924
30051
  useEffect(() => {
29925
30052
  if (filteredVideos.length > 0 && currentIndex < filteredVideos.length) {
30053
+ if (error && error.type === "fatal") {
30054
+ console.log("[BottlenecksContent] Not clearing fatal error on video change - let user handle it");
30055
+ return;
30056
+ }
29926
30057
  setError(null);
29927
30058
  }
29928
- }, [currentIndex, filteredVideos]);
30059
+ }, [currentIndex, filteredVideos, error]);
29929
30060
  useEffect(() => {
29930
30061
  if (!isTransitioning && pendingVideo) {
29931
30062
  const timer = setTimeout(() => {
@@ -30000,11 +30131,11 @@ var BottlenecksContent = ({
30000
30131
  if ((isLoading || clipTypesLoading) && allVideos.length === 0 && Object.keys(mergedCounts).length === 0) {
30001
30132
  return /* @__PURE__ */ jsx("div", { className: "flex-grow p-4 flex items-center justify-center h-[calc(100vh-12rem)]", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading clips..." }) });
30002
30133
  }
30003
- if (error || clipTypesError) {
30134
+ if (error && error.type === "fatal" && !hasInitialLoad || clipTypesError) {
30004
30135
  return /* @__PURE__ */ jsxs("div", { className: "flex-grow p-4 flex flex-col items-center justify-center h-[calc(100vh-12rem)] text-center", children: [
30005
30136
  /* @__PURE__ */ jsx(XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
30006
30137
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-red-700 mb-1", children: "Error Loading Clips" }),
30007
- /* @__PURE__ */ jsx("p", { className: "text-gray-600 max-w-md", children: error || clipTypesError })
30138
+ /* @__PURE__ */ jsx("p", { className: "text-gray-600 max-w-md", children: error?.message || clipTypesError })
30008
30139
  ] });
30009
30140
  }
30010
30141
  const categoriesToShow = clipTypes.length > 0 ? clipTypes : [];
@@ -30051,7 +30182,7 @@ var BottlenecksContent = ({
30051
30182
  ),
30052
30183
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
30053
30184
  /* @__PURE__ */ jsx("span", { className: "text-sm px-2 py-1 bg-blue-50 text-blue-700 rounded-full font-medium tabular-nums", children: categoryMetadata.length > 0 ? `${currentMetadataIndex + 1} / ${categoryMetadata.length}` : "0 / 0" }),
30054
- error && /* @__PURE__ */ jsx("span", { className: "text-xs text-red-600 font-medium", children: error })
30185
+ error && error.type === "retrying" && /* @__PURE__ */ jsx("span", { className: "text-xs text-orange-600 font-medium", children: error.message })
30055
30186
  ] }),
30056
30187
  /* @__PURE__ */ jsx(
30057
30188
  "button",
@@ -30065,7 +30196,6 @@ var BottlenecksContent = ({
30065
30196
  )
30066
30197
  ] })
30067
30198
  ] }) }),
30068
- /* Show video if we have filtered videos and current video, or show loading */
30069
30199
  filteredVideos.length > 0 && currentVideo ? /* @__PURE__ */ jsx("div", { className: "p-4 h-[calc(100%-4rem)]", children: /* @__PURE__ */ jsx("div", { className: "relative h-full group", children: /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full overflow-hidden rounded-md shadow-inner bg-gray-900", children: [
30070
30200
  /* @__PURE__ */ jsx(
30071
30201
  CroppedVideoPlayer,
@@ -30097,30 +30227,44 @@ var BottlenecksContent = ({
30097
30227
  }
30098
30228
  }
30099
30229
  ),
30100
- (isTransitioning || isVideoBuffering && isInitialLoading) && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
30101
- !isTransitioning && isVideoBuffering && !isInitialLoading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
30102
- error && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/80 text-white p-4", children: /* @__PURE__ */ jsxs("div", { className: "text-center max-w-md", children: [
30230
+ (isTransitioning || isVideoBuffering && isInitialLoading) && !error && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
30231
+ !isTransitioning && isVideoBuffering && !isInitialLoading && !error && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
30232
+ error && error.type === "retrying" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-40 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
30233
+ /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md" }),
30234
+ /* @__PURE__ */ jsx("p", { className: "text-white text-sm mt-4 font-medium", children: error.message })
30235
+ ] }) }),
30236
+ error && error.type === "fatal" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-50 flex items-center justify-center bg-black/90 text-white p-4", children: /* @__PURE__ */ jsxs("div", { className: "text-center max-w-md", children: [
30103
30237
  /* @__PURE__ */ jsx("svg", { className: "w-16 h-16 mx-auto mb-4 text-red-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 16.5c-.77.833.192 2.5 1.732 2.5z" }) }),
30104
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: "Video Stream Error" }),
30105
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-300 mb-4", children: "The video stream appears to be corrupted or unavailable. This may be due to:" }),
30106
- /* @__PURE__ */ jsxs("ul", { className: "text-sm text-gray-300 text-left space-y-1 mb-4", children: [
30107
- /* @__PURE__ */ jsx("li", { children: "\u2022 Incomplete video encoding" }),
30108
- /* @__PURE__ */ jsx("li", { children: "\u2022 Network connectivity issues" }),
30109
- /* @__PURE__ */ jsx("li", { children: "\u2022 Server processing errors" })
30110
- ] }),
30111
- /* @__PURE__ */ jsx(
30112
- "button",
30113
- {
30114
- onClick: () => {
30115
- setError(null);
30116
- if (videoRef.current) {
30117
- videoRef.current.dispose();
30118
- }
30119
- },
30120
- className: "px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm font-medium",
30121
- children: "Retry"
30122
- }
30123
- )
30238
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: error.code === 3 ? "Stream Corrupted" : error.code === 4 ? "Format Not Supported" : error.code === 2 ? "Network Error" : error.code === 1 ? "Loading Interrupted" : "Playback Error" }),
30239
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-300 mb-6", children: error.message }),
30240
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3 justify-center", children: [
30241
+ error.canSkip && /* @__PURE__ */ jsx(
30242
+ "button",
30243
+ {
30244
+ onClick: () => {
30245
+ setError(null);
30246
+ videoRetryCountRef.current = 0;
30247
+ handleNext();
30248
+ },
30249
+ className: "px-5 py-2.5 bg-blue-600 hover:bg-blue-700 rounded-md text-sm font-medium transition-colors",
30250
+ children: "Skip to Next Clip"
30251
+ }
30252
+ ),
30253
+ error.canRetry && /* @__PURE__ */ jsx(
30254
+ "button",
30255
+ {
30256
+ onClick: () => {
30257
+ setError(null);
30258
+ videoRetryCountRef.current = 0;
30259
+ if (videoRef.current) {
30260
+ videoRef.current.dispose();
30261
+ }
30262
+ },
30263
+ className: "px-5 py-2.5 bg-gray-600 hover:bg-gray-700 rounded-md text-sm font-medium transition-colors",
30264
+ children: "Retry"
30265
+ }
30266
+ )
30267
+ ] })
30124
30268
  ] }) }),
30125
30269
  (currentVideo.type === "cycle_completion" || currentVideo.type === "bottleneck" && currentVideo.description.toLowerCase().includes("cycle time")) && currentVideo.cycle_time_seconds || currentVideo.type === "idle_time" || currentVideo.type === "low_value" ? /* @__PURE__ */ jsx("div", { className: "absolute top-3 left-3 z-10 bg-black/60 backdrop-blur-sm px-3 py-1.5 rounded-lg text-white shadow-lg text-xs", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
30126
30270
  /* @__PURE__ */ jsx("div", { className: `flex-shrink-0 h-2.5 w-2.5 rounded-full ${currentVideo.type === "low_value" || currentVideo.type === "idle_time" ? "bg-purple-400" : isPercentileCategory(activeFilterRef.current) ? activeFilterRef.current === "fast-cycles" ? "bg-green-600" : activeFilterRef.current === "slow-cycles" ? "bg-red-700" : "bg-orange-500" : currentVideo.type === "cycle_completion" ? "bg-blue-600" : "bg-gray-500"} mr-2 animate-pulse` }),
@@ -42629,7 +42773,7 @@ var WorkspaceDetailView = ({
42629
42773
  /* @__PURE__ */ jsxs(Card2, { children: [
42630
42774
  /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
42631
42775
  /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
42632
- /* @__PURE__ */ jsx("p", { className: `text-5xl font-bold text-green-500`, children: workspace.avg_cycle_time.toFixed(1) }),
42776
+ /* @__PURE__ */ jsx("p", { className: `text-5xl font-bold ${workspace.avg_cycle_time > (workspace.ideal_cycle_time || 0) ? "text-red-500" : "text-green-500"}`, children: workspace.avg_cycle_time.toFixed(1) }),
42633
42777
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
42634
42778
  "Standard: ",
42635
42779
  workspace.ideal_cycle_time?.toFixed(1) || 0,
@@ -42749,7 +42893,7 @@ var WorkspaceDetailView = ({
42749
42893
  /* @__PURE__ */ jsxs(Card2, { children: [
42750
42894
  /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
42751
42895
  /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
42752
- /* @__PURE__ */ jsx("p", { className: `text-5xl font-bold text-green-500`, children: workspace.avg_cycle_time.toFixed(1) }),
42896
+ /* @__PURE__ */ jsx("p", { className: `text-5xl font-bold ${workspace.avg_cycle_time > (workspace.ideal_cycle_time || 0) ? "text-red-500" : "text-green-500"}`, children: workspace.avg_cycle_time.toFixed(1) }),
42753
42897
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
42754
42898
  "Standard: ",
42755
42899
  workspace.ideal_cycle_time?.toFixed(1) || 0,
@@ -42824,6 +42968,7 @@ var WorkspaceDetailView = ({
42824
42968
  workspaceName: formattedWorkspaceName,
42825
42969
  date,
42826
42970
  shift,
42971
+ totalOutput: workspace?.total_actions,
42827
42972
  className: "h-[calc(100vh-10rem)]"
42828
42973
  }
42829
42974
  ) })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.6.9",
3
+ "version": "6.6.11",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",