@optifye/dashboard-core 6.6.10 → 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.js CHANGED
@@ -22279,7 +22279,7 @@ var WorkspaceMetricCardsImpl = ({
22279
22279
  /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
22280
22280
  /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
22281
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: [
22282
- /* @__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) }),
22283
22283
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
22284
22284
  "Standard: ",
22285
22285
  workspace.ideal_cycle_time?.toFixed(1) || 0,
@@ -25959,8 +25959,19 @@ var WorkspaceMonthlyHistory = ({
25959
25959
  const daysInMonth = new Date(year, month + 1, 0).getDate();
25960
25960
  const dailyData = [];
25961
25961
  let maxOutput = 0;
25962
- let totalIdealOutput = 0;
25963
- 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
+ }
25964
25975
  for (let day = 1; day <= daysInMonth; day++) {
25965
25976
  const dayData = data.find((d) => {
25966
25977
  const date = new Date(d.date);
@@ -25970,11 +25981,7 @@ var WorkspaceMonthlyHistory = ({
25970
25981
  const output = shiftData && hasRealData(shiftData) ? shiftData.output : 0;
25971
25982
  const idealOutput = shiftData ? shiftData.idealOutput : 0;
25972
25983
  if (output > maxOutput) maxOutput = output;
25973
- if (idealOutput > 0) {
25974
- totalIdealOutput += idealOutput;
25975
- validDaysCount++;
25976
- }
25977
- const color2 = output >= idealOutput ? "#00AB45" : "#E34329";
25984
+ const color2 = output >= lastSetTarget ? "#00AB45" : "#E34329";
25978
25985
  dailyData.push({
25979
25986
  hour: getOrdinal(day),
25980
25987
  // Using ordinal format (1st, 2nd, 3rd, etc.)
@@ -25989,14 +25996,13 @@ var WorkspaceMonthlyHistory = ({
25989
25996
  // Not used but keeps structure consistent
25990
25997
  });
25991
25998
  }
25992
- const avgIdealOutput = validDaysCount > 0 ? totalIdealOutput / validDaysCount : 0;
25993
- const calculatedMax = Math.max(maxOutput, avgIdealOutput);
25999
+ const calculatedMax = Math.max(maxOutput, lastSetTarget);
25994
26000
  const yAxisMax = calculatedMax > 0 ? calculatedMax * 1.1 : 100;
25995
- return { data: dailyData, maxOutput, avgIdealOutput, yAxisMax };
26001
+ return { data: dailyData, maxOutput, lastSetTarget, yAxisMax };
25996
26002
  }, [data, month, year, selectedShift]);
25997
26003
  const yAxisTicks = React21.useMemo(() => {
25998
26004
  const max = chartData.yAxisMax;
25999
- const target = chartData.avgIdealOutput;
26005
+ const target = chartData.lastSetTarget;
26000
26006
  if (!max || max <= 0) return void 0;
26001
26007
  const desiredIntervals = 4;
26002
26008
  const roughStep = max / desiredIntervals;
@@ -26014,7 +26020,7 @@ var WorkspaceMonthlyHistory = ({
26014
26020
  ticks.push(Math.round(target));
26015
26021
  }
26016
26022
  return Array.from(new Set(ticks)).filter((v) => v >= 0 && v <= max).sort((a, b) => a - b);
26017
- }, [chartData.yAxisMax, chartData.avgIdealOutput]);
26023
+ }, [chartData.yAxisMax, chartData.lastSetTarget]);
26018
26024
  const pieChartData = React21.useMemo(() => {
26019
26025
  const validShifts = data.map((d) => selectedShift === "day" ? d.dayShift : d.nightShift).filter(hasRealData);
26020
26026
  if (validShifts.length === 0) return [];
@@ -26326,7 +26332,7 @@ var WorkspaceMonthlyHistory = ({
26326
26332
  tick: (props) => {
26327
26333
  const { x, y, payload } = props;
26328
26334
  const value = Math.round(payload.value);
26329
- const targetValue = Math.round(chartData.avgIdealOutput);
26335
+ const targetValue = Math.round(chartData.lastSetTarget);
26330
26336
  const isTarget = Math.abs(value - targetValue) < 1 && targetValue > 0;
26331
26337
  return /* @__PURE__ */ jsxRuntime.jsx(
26332
26338
  "text",
@@ -26350,10 +26356,10 @@ var WorkspaceMonthlyHistory = ({
26350
26356
  content: CustomTooltip
26351
26357
  }
26352
26358
  ),
26353
- chartData.avgIdealOutput > 0 && /* @__PURE__ */ jsxRuntime.jsx(
26359
+ chartData.lastSetTarget > 0 && /* @__PURE__ */ jsxRuntime.jsx(
26354
26360
  recharts.ReferenceLine,
26355
26361
  {
26356
- y: chartData.avgIdealOutput,
26362
+ y: chartData.lastSetTarget,
26357
26363
  stroke: "#E34329",
26358
26364
  strokeDasharray: "5 5",
26359
26365
  strokeWidth: 2
@@ -26411,11 +26417,11 @@ var WorkspaceMonthlyHistory = ({
26411
26417
  ]
26412
26418
  }
26413
26419
  ) }) }),
26414
- /* @__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: [
26415
26421
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-0.5 border-t-2 border-dashed", style: { borderColor: "#E34329" } }),
26416
26422
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-gray-600", children: [
26417
26423
  "Target: ",
26418
- Math.round(chartData.avgIdealOutput),
26424
+ Math.round(chartData.lastSetTarget),
26419
26425
  " units/day"
26420
26426
  ] })
26421
26427
  ] }) })
@@ -27261,6 +27267,36 @@ var TimePickerDropdown = ({
27261
27267
  ] })
27262
27268
  ] });
27263
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
+ };
27264
27300
  var videoPlayerStyles = `
27265
27301
  .video-player-container {
27266
27302
  width: 100%;
@@ -27491,8 +27527,21 @@ var VideoPlayer = React21__namespace.default.forwardRef(({
27491
27527
  player.on("seeked", () => onSeeked?.(player));
27492
27528
  player.on("error", () => {
27493
27529
  const error = player.error();
27494
- console.error("Video.js error:", error);
27495
- 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);
27496
27545
  });
27497
27546
  if (src) {
27498
27547
  const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
@@ -28670,7 +28719,7 @@ var FileManagerFilters = ({
28670
28719
  ];
28671
28720
  percentileCategories.forEach((category) => {
28672
28721
  if (category.count > 0 && shouldShowCategory(category.id)) {
28673
- tree.unshift(category);
28722
+ tree.push(category);
28674
28723
  }
28675
28724
  });
28676
28725
  return tree;
@@ -29387,7 +29436,12 @@ var BottlenecksContent = ({
29387
29436
  } catch (err) {
29388
29437
  console.error("[BottlenecksContent] Error fetching clip counts:", err);
29389
29438
  if (isMountedRef.current) {
29390
- 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
+ });
29391
29445
  setIsLoading(false);
29392
29446
  }
29393
29447
  } finally {
@@ -29471,7 +29525,12 @@ var BottlenecksContent = ({
29471
29525
  } catch (err) {
29472
29526
  console.error("Error loading first video for category:", err);
29473
29527
  if (isMountedRef.current) {
29474
- 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
+ });
29475
29534
  setIsCategoryLoading(false);
29476
29535
  }
29477
29536
  } finally {
@@ -29753,7 +29812,12 @@ var BottlenecksContent = ({
29753
29812
  } catch (error2) {
29754
29813
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
29755
29814
  if (isMountedRef.current) {
29756
- 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
+ });
29757
29821
  clearLoadingState();
29758
29822
  }
29759
29823
  }
@@ -29776,7 +29840,12 @@ var BottlenecksContent = ({
29776
29840
  }
29777
29841
  } catch (error2) {
29778
29842
  console.error(`[BottlenecksContent] Error in legacy loadAndPlayClip:`, error2);
29779
- 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
+ });
29780
29849
  setIsNavigating(false);
29781
29850
  }
29782
29851
  }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById]);
@@ -29816,7 +29885,12 @@ var BottlenecksContent = ({
29816
29885
  }
29817
29886
  } catch (error2) {
29818
29887
  console.error(`[handleNext] Error navigating:`, error2);
29819
- 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
+ });
29820
29894
  clearLoadingState();
29821
29895
  }
29822
29896
  }, [clearLoadingState, s3ClipsService]);
@@ -29852,7 +29926,12 @@ var BottlenecksContent = ({
29852
29926
  }
29853
29927
  } catch (error2) {
29854
29928
  console.error(`[handlePrevious] Error navigating:`, error2);
29855
- 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
+ });
29856
29935
  clearLoadingState();
29857
29936
  }
29858
29937
  }, [clearLoadingState, s3ClipsService]);
@@ -29865,7 +29944,7 @@ var BottlenecksContent = ({
29865
29944
  const handleVideoReady = React21.useCallback((player) => {
29866
29945
  console.log("Video.js player ready - NOT clearing loading (wait for playing event)");
29867
29946
  videoRetryCountRef.current = 0;
29868
- if (error?.includes("Retrying")) {
29947
+ if (error?.isRetrying) {
29869
29948
  setError(null);
29870
29949
  }
29871
29950
  }, [error]);
@@ -29910,20 +29989,53 @@ var BottlenecksContent = ({
29910
29989
  }, [clearLoadingState]);
29911
29990
  const handleVideoLoadingChange = React21.useCallback((isLoading2) => {
29912
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
+ }
29913
29996
  setIsVideoBuffering(isLoading2);
29914
- }, []);
29997
+ }, [error]);
29915
29998
  const handleVideoEnded = React21.useCallback((player) => {
29916
29999
  handleNext();
29917
30000
  }, [handleNext]);
29918
30001
  const videoRetryCountRef = React21.useRef(0);
29919
- const handleVideoError = React21.useCallback((player, error2) => {
29920
- console.error("Video.js error:", error2);
30002
+ const handleVideoError = React21.useCallback((player, errorInfo) => {
30003
+ console.error("[BottlenecksContent] Video.js error:", errorInfo);
29921
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
+ }
29922
30030
  if (videoRetryCountRef.current < 3 && currentVideo) {
29923
30031
  videoRetryCountRef.current++;
29924
30032
  const retryDelay = 1e3 * videoRetryCountRef.current;
29925
- console.log(`[Video Error] Retrying... Attempt ${videoRetryCountRef.current}/3 in ${retryDelay}ms`);
29926
- 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
+ });
29927
30039
  setTimeout(() => {
29928
30040
  if (videoRef.current && currentVideo && isMountedRef.current) {
29929
30041
  setError(null);
@@ -29931,16 +30043,26 @@ var BottlenecksContent = ({
29931
30043
  }
29932
30044
  }, retryDelay);
29933
30045
  } else {
29934
- 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
+ });
29935
30055
  videoRetryCountRef.current = 0;
29936
30056
  trackCoreEvent("clips_video_error_final", {
29937
30057
  workspaceId,
29938
30058
  category: activeFilterRef.current,
29939
30059
  videoId: currentVideo?.id,
29940
- attempts: videoRetryCountRef.current
30060
+ errorCode,
30061
+ errorMessage,
30062
+ attempts: 3
29941
30063
  });
29942
30064
  }
29943
- }, [currentVideo, workspaceId]);
30065
+ }, [currentVideo, workspaceId, clearLoadingState]);
29944
30066
  React21.useEffect(() => {
29945
30067
  isMountedRef.current = true;
29946
30068
  return () => {
@@ -29958,9 +30080,13 @@ var BottlenecksContent = ({
29958
30080
  }, [s3ClipsService]);
29959
30081
  React21.useEffect(() => {
29960
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
+ }
29961
30087
  setError(null);
29962
30088
  }
29963
- }, [currentIndex, filteredVideos]);
30089
+ }, [currentIndex, filteredVideos, error]);
29964
30090
  React21.useEffect(() => {
29965
30091
  if (!isTransitioning && pendingVideo) {
29966
30092
  const timer = setTimeout(() => {
@@ -30035,11 +30161,11 @@ var BottlenecksContent = ({
30035
30161
  if ((isLoading || clipTypesLoading) && allVideos.length === 0 && Object.keys(mergedCounts).length === 0) {
30036
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..." }) });
30037
30163
  }
30038
- if (error || clipTypesError) {
30164
+ if (error && error.type === "fatal" && !hasInitialLoad || clipTypesError) {
30039
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: [
30040
30166
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
30041
30167
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-red-700 mb-1", children: "Error Loading Clips" }),
30042
- /* @__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 })
30043
30169
  ] });
30044
30170
  }
30045
30171
  const categoriesToShow = clipTypes.length > 0 ? clipTypes : [];
@@ -30086,7 +30212,7 @@ var BottlenecksContent = ({
30086
30212
  ),
30087
30213
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1", children: [
30088
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" }),
30089
- 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 })
30090
30216
  ] }),
30091
30217
  /* @__PURE__ */ jsxRuntime.jsx(
30092
30218
  "button",
@@ -30100,7 +30226,6 @@ var BottlenecksContent = ({
30100
30226
  )
30101
30227
  ] })
30102
30228
  ] }) }),
30103
- /* Show video if we have filtered videos and current video, or show loading */
30104
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: [
30105
30230
  /* @__PURE__ */ jsxRuntime.jsx(
30106
30231
  CroppedVideoPlayer,
@@ -30132,30 +30257,44 @@ var BottlenecksContent = ({
30132
30257
  }
30133
30258
  }
30134
30259
  ),
30135
- (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..." }) }),
30136
- !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..." }) }),
30137
- 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: [
30138
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" }) }),
30139
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-2", children: "Video Stream Error" }),
30140
- /* @__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:" }),
30141
- /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "text-sm text-gray-300 text-left space-y-1 mb-4", children: [
30142
- /* @__PURE__ */ jsxRuntime.jsx("li", { children: "\u2022 Incomplete video encoding" }),
30143
- /* @__PURE__ */ jsxRuntime.jsx("li", { children: "\u2022 Network connectivity issues" }),
30144
- /* @__PURE__ */ jsxRuntime.jsx("li", { children: "\u2022 Server processing errors" })
30145
- ] }),
30146
- /* @__PURE__ */ jsxRuntime.jsx(
30147
- "button",
30148
- {
30149
- onClick: () => {
30150
- setError(null);
30151
- if (videoRef.current) {
30152
- videoRef.current.dispose();
30153
- }
30154
- },
30155
- className: "px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm font-medium",
30156
- children: "Retry"
30157
- }
30158
- )
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
+ ] })
30159
30298
  ] }) }),
30160
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: [
30161
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` }),
@@ -42664,7 +42803,7 @@ var WorkspaceDetailView = ({
42664
42803
  /* @__PURE__ */ jsxRuntime.jsxs(Card2, { children: [
42665
42804
  /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
42666
42805
  /* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
42667
- /* @__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) }),
42668
42807
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
42669
42808
  "Standard: ",
42670
42809
  workspace.ideal_cycle_time?.toFixed(1) || 0,
@@ -42784,7 +42923,7 @@ var WorkspaceDetailView = ({
42784
42923
  /* @__PURE__ */ jsxRuntime.jsxs(Card2, { children: [
42785
42924
  /* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
42786
42925
  /* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
42787
- /* @__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) }),
42788
42927
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
42789
42928
  "Standard: ",
42790
42929
  workspace.ideal_cycle_time?.toFixed(1) || 0,
package/dist/index.mjs CHANGED
@@ -22249,7 +22249,7 @@ var WorkspaceMetricCardsImpl = ({
22249
22249
  /* @__PURE__ */ jsxs(Card2, { className: "flex flex-col bg-white shadow-sm h-full min-h-[150px] sm:min-h-0", children: [
22250
22250
  /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
22251
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: [
22252
- /* @__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) }),
22253
22253
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
22254
22254
  "Standard: ",
22255
22255
  workspace.ideal_cycle_time?.toFixed(1) || 0,
@@ -25929,8 +25929,19 @@ var WorkspaceMonthlyHistory = ({
25929
25929
  const daysInMonth = new Date(year, month + 1, 0).getDate();
25930
25930
  const dailyData = [];
25931
25931
  let maxOutput = 0;
25932
- let totalIdealOutput = 0;
25933
- 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
+ }
25934
25945
  for (let day = 1; day <= daysInMonth; day++) {
25935
25946
  const dayData = data.find((d) => {
25936
25947
  const date = new Date(d.date);
@@ -25940,11 +25951,7 @@ var WorkspaceMonthlyHistory = ({
25940
25951
  const output = shiftData && hasRealData(shiftData) ? shiftData.output : 0;
25941
25952
  const idealOutput = shiftData ? shiftData.idealOutput : 0;
25942
25953
  if (output > maxOutput) maxOutput = output;
25943
- if (idealOutput > 0) {
25944
- totalIdealOutput += idealOutput;
25945
- validDaysCount++;
25946
- }
25947
- const color2 = output >= idealOutput ? "#00AB45" : "#E34329";
25954
+ const color2 = output >= lastSetTarget ? "#00AB45" : "#E34329";
25948
25955
  dailyData.push({
25949
25956
  hour: getOrdinal(day),
25950
25957
  // Using ordinal format (1st, 2nd, 3rd, etc.)
@@ -25959,14 +25966,13 @@ var WorkspaceMonthlyHistory = ({
25959
25966
  // Not used but keeps structure consistent
25960
25967
  });
25961
25968
  }
25962
- const avgIdealOutput = validDaysCount > 0 ? totalIdealOutput / validDaysCount : 0;
25963
- const calculatedMax = Math.max(maxOutput, avgIdealOutput);
25969
+ const calculatedMax = Math.max(maxOutput, lastSetTarget);
25964
25970
  const yAxisMax = calculatedMax > 0 ? calculatedMax * 1.1 : 100;
25965
- return { data: dailyData, maxOutput, avgIdealOutput, yAxisMax };
25971
+ return { data: dailyData, maxOutput, lastSetTarget, yAxisMax };
25966
25972
  }, [data, month, year, selectedShift]);
25967
25973
  const yAxisTicks = useMemo(() => {
25968
25974
  const max = chartData.yAxisMax;
25969
- const target = chartData.avgIdealOutput;
25975
+ const target = chartData.lastSetTarget;
25970
25976
  if (!max || max <= 0) return void 0;
25971
25977
  const desiredIntervals = 4;
25972
25978
  const roughStep = max / desiredIntervals;
@@ -25984,7 +25990,7 @@ var WorkspaceMonthlyHistory = ({
25984
25990
  ticks.push(Math.round(target));
25985
25991
  }
25986
25992
  return Array.from(new Set(ticks)).filter((v) => v >= 0 && v <= max).sort((a, b) => a - b);
25987
- }, [chartData.yAxisMax, chartData.avgIdealOutput]);
25993
+ }, [chartData.yAxisMax, chartData.lastSetTarget]);
25988
25994
  const pieChartData = useMemo(() => {
25989
25995
  const validShifts = data.map((d) => selectedShift === "day" ? d.dayShift : d.nightShift).filter(hasRealData);
25990
25996
  if (validShifts.length === 0) return [];
@@ -26296,7 +26302,7 @@ var WorkspaceMonthlyHistory = ({
26296
26302
  tick: (props) => {
26297
26303
  const { x, y, payload } = props;
26298
26304
  const value = Math.round(payload.value);
26299
- const targetValue = Math.round(chartData.avgIdealOutput);
26305
+ const targetValue = Math.round(chartData.lastSetTarget);
26300
26306
  const isTarget = Math.abs(value - targetValue) < 1 && targetValue > 0;
26301
26307
  return /* @__PURE__ */ jsx(
26302
26308
  "text",
@@ -26320,10 +26326,10 @@ var WorkspaceMonthlyHistory = ({
26320
26326
  content: CustomTooltip
26321
26327
  }
26322
26328
  ),
26323
- chartData.avgIdealOutput > 0 && /* @__PURE__ */ jsx(
26329
+ chartData.lastSetTarget > 0 && /* @__PURE__ */ jsx(
26324
26330
  ReferenceLine,
26325
26331
  {
26326
- y: chartData.avgIdealOutput,
26332
+ y: chartData.lastSetTarget,
26327
26333
  stroke: "#E34329",
26328
26334
  strokeDasharray: "5 5",
26329
26335
  strokeWidth: 2
@@ -26381,11 +26387,11 @@ var WorkspaceMonthlyHistory = ({
26381
26387
  ]
26382
26388
  }
26383
26389
  ) }) }),
26384
- /* @__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: [
26385
26391
  /* @__PURE__ */ jsx("div", { className: "w-12 h-0.5 border-t-2 border-dashed", style: { borderColor: "#E34329" } }),
26386
26392
  /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-600", children: [
26387
26393
  "Target: ",
26388
- Math.round(chartData.avgIdealOutput),
26394
+ Math.round(chartData.lastSetTarget),
26389
26395
  " units/day"
26390
26396
  ] })
26391
26397
  ] }) })
@@ -27231,6 +27237,36 @@ var TimePickerDropdown = ({
27231
27237
  ] })
27232
27238
  ] });
27233
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
+ };
27234
27270
  var videoPlayerStyles = `
27235
27271
  .video-player-container {
27236
27272
  width: 100%;
@@ -27461,8 +27497,21 @@ var VideoPlayer = React21__default.forwardRef(({
27461
27497
  player.on("seeked", () => onSeeked?.(player));
27462
27498
  player.on("error", () => {
27463
27499
  const error = player.error();
27464
- console.error("Video.js error:", error);
27465
- 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);
27466
27515
  });
27467
27516
  if (src) {
27468
27517
  const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
@@ -28640,7 +28689,7 @@ var FileManagerFilters = ({
28640
28689
  ];
28641
28690
  percentileCategories.forEach((category) => {
28642
28691
  if (category.count > 0 && shouldShowCategory(category.id)) {
28643
- tree.unshift(category);
28692
+ tree.push(category);
28644
28693
  }
28645
28694
  });
28646
28695
  return tree;
@@ -29357,7 +29406,12 @@ var BottlenecksContent = ({
29357
29406
  } catch (err) {
29358
29407
  console.error("[BottlenecksContent] Error fetching clip counts:", err);
29359
29408
  if (isMountedRef.current) {
29360
- 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
+ });
29361
29415
  setIsLoading(false);
29362
29416
  }
29363
29417
  } finally {
@@ -29441,7 +29495,12 @@ var BottlenecksContent = ({
29441
29495
  } catch (err) {
29442
29496
  console.error("Error loading first video for category:", err);
29443
29497
  if (isMountedRef.current) {
29444
- 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
+ });
29445
29504
  setIsCategoryLoading(false);
29446
29505
  }
29447
29506
  } finally {
@@ -29723,7 +29782,12 @@ var BottlenecksContent = ({
29723
29782
  } catch (error2) {
29724
29783
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
29725
29784
  if (isMountedRef.current) {
29726
- 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
+ });
29727
29791
  clearLoadingState();
29728
29792
  }
29729
29793
  }
@@ -29746,7 +29810,12 @@ var BottlenecksContent = ({
29746
29810
  }
29747
29811
  } catch (error2) {
29748
29812
  console.error(`[BottlenecksContent] Error in legacy loadAndPlayClip:`, error2);
29749
- 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
+ });
29750
29819
  setIsNavigating(false);
29751
29820
  }
29752
29821
  }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById]);
@@ -29786,7 +29855,12 @@ var BottlenecksContent = ({
29786
29855
  }
29787
29856
  } catch (error2) {
29788
29857
  console.error(`[handleNext] Error navigating:`, error2);
29789
- 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
+ });
29790
29864
  clearLoadingState();
29791
29865
  }
29792
29866
  }, [clearLoadingState, s3ClipsService]);
@@ -29822,7 +29896,12 @@ var BottlenecksContent = ({
29822
29896
  }
29823
29897
  } catch (error2) {
29824
29898
  console.error(`[handlePrevious] Error navigating:`, error2);
29825
- 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
+ });
29826
29905
  clearLoadingState();
29827
29906
  }
29828
29907
  }, [clearLoadingState, s3ClipsService]);
@@ -29835,7 +29914,7 @@ var BottlenecksContent = ({
29835
29914
  const handleVideoReady = useCallback((player) => {
29836
29915
  console.log("Video.js player ready - NOT clearing loading (wait for playing event)");
29837
29916
  videoRetryCountRef.current = 0;
29838
- if (error?.includes("Retrying")) {
29917
+ if (error?.isRetrying) {
29839
29918
  setError(null);
29840
29919
  }
29841
29920
  }, [error]);
@@ -29880,20 +29959,53 @@ var BottlenecksContent = ({
29880
29959
  }, [clearLoadingState]);
29881
29960
  const handleVideoLoadingChange = useCallback((isLoading2) => {
29882
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
+ }
29883
29966
  setIsVideoBuffering(isLoading2);
29884
- }, []);
29967
+ }, [error]);
29885
29968
  const handleVideoEnded = useCallback((player) => {
29886
29969
  handleNext();
29887
29970
  }, [handleNext]);
29888
29971
  const videoRetryCountRef = useRef(0);
29889
- const handleVideoError = useCallback((player, error2) => {
29890
- console.error("Video.js error:", error2);
29972
+ const handleVideoError = useCallback((player, errorInfo) => {
29973
+ console.error("[BottlenecksContent] Video.js error:", errorInfo);
29891
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
+ }
29892
30000
  if (videoRetryCountRef.current < 3 && currentVideo) {
29893
30001
  videoRetryCountRef.current++;
29894
30002
  const retryDelay = 1e3 * videoRetryCountRef.current;
29895
- console.log(`[Video Error] Retrying... Attempt ${videoRetryCountRef.current}/3 in ${retryDelay}ms`);
29896
- 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
+ });
29897
30009
  setTimeout(() => {
29898
30010
  if (videoRef.current && currentVideo && isMountedRef.current) {
29899
30011
  setError(null);
@@ -29901,16 +30013,26 @@ var BottlenecksContent = ({
29901
30013
  }
29902
30014
  }, retryDelay);
29903
30015
  } else {
29904
- 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
+ });
29905
30025
  videoRetryCountRef.current = 0;
29906
30026
  trackCoreEvent("clips_video_error_final", {
29907
30027
  workspaceId,
29908
30028
  category: activeFilterRef.current,
29909
30029
  videoId: currentVideo?.id,
29910
- attempts: videoRetryCountRef.current
30030
+ errorCode,
30031
+ errorMessage,
30032
+ attempts: 3
29911
30033
  });
29912
30034
  }
29913
- }, [currentVideo, workspaceId]);
30035
+ }, [currentVideo, workspaceId, clearLoadingState]);
29914
30036
  useEffect(() => {
29915
30037
  isMountedRef.current = true;
29916
30038
  return () => {
@@ -29928,9 +30050,13 @@ var BottlenecksContent = ({
29928
30050
  }, [s3ClipsService]);
29929
30051
  useEffect(() => {
29930
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
+ }
29931
30057
  setError(null);
29932
30058
  }
29933
- }, [currentIndex, filteredVideos]);
30059
+ }, [currentIndex, filteredVideos, error]);
29934
30060
  useEffect(() => {
29935
30061
  if (!isTransitioning && pendingVideo) {
29936
30062
  const timer = setTimeout(() => {
@@ -30005,11 +30131,11 @@ var BottlenecksContent = ({
30005
30131
  if ((isLoading || clipTypesLoading) && allVideos.length === 0 && Object.keys(mergedCounts).length === 0) {
30006
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..." }) });
30007
30133
  }
30008
- if (error || clipTypesError) {
30134
+ if (error && error.type === "fatal" && !hasInitialLoad || clipTypesError) {
30009
30135
  return /* @__PURE__ */ jsxs("div", { className: "flex-grow p-4 flex flex-col items-center justify-center h-[calc(100vh-12rem)] text-center", children: [
30010
30136
  /* @__PURE__ */ jsx(XCircle, { className: "w-12 h-12 text-red-400 mb-3" }),
30011
30137
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-red-700 mb-1", children: "Error Loading Clips" }),
30012
- /* @__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 })
30013
30139
  ] });
30014
30140
  }
30015
30141
  const categoriesToShow = clipTypes.length > 0 ? clipTypes : [];
@@ -30056,7 +30182,7 @@ var BottlenecksContent = ({
30056
30182
  ),
30057
30183
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
30058
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" }),
30059
- 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 })
30060
30186
  ] }),
30061
30187
  /* @__PURE__ */ jsx(
30062
30188
  "button",
@@ -30070,7 +30196,6 @@ var BottlenecksContent = ({
30070
30196
  )
30071
30197
  ] })
30072
30198
  ] }) }),
30073
- /* Show video if we have filtered videos and current video, or show loading */
30074
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: [
30075
30200
  /* @__PURE__ */ jsx(
30076
30201
  CroppedVideoPlayer,
@@ -30102,30 +30227,44 @@ var BottlenecksContent = ({
30102
30227
  }
30103
30228
  }
30104
30229
  ),
30105
- (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..." }) }),
30106
- !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..." }) }),
30107
- 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: [
30108
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" }) }),
30109
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: "Video Stream Error" }),
30110
- /* @__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:" }),
30111
- /* @__PURE__ */ jsxs("ul", { className: "text-sm text-gray-300 text-left space-y-1 mb-4", children: [
30112
- /* @__PURE__ */ jsx("li", { children: "\u2022 Incomplete video encoding" }),
30113
- /* @__PURE__ */ jsx("li", { children: "\u2022 Network connectivity issues" }),
30114
- /* @__PURE__ */ jsx("li", { children: "\u2022 Server processing errors" })
30115
- ] }),
30116
- /* @__PURE__ */ jsx(
30117
- "button",
30118
- {
30119
- onClick: () => {
30120
- setError(null);
30121
- if (videoRef.current) {
30122
- videoRef.current.dispose();
30123
- }
30124
- },
30125
- className: "px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm font-medium",
30126
- children: "Retry"
30127
- }
30128
- )
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
+ ] })
30129
30268
  ] }) }),
30130
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: [
30131
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` }),
@@ -42634,7 +42773,7 @@ var WorkspaceDetailView = ({
42634
42773
  /* @__PURE__ */ jsxs(Card2, { children: [
42635
42774
  /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
42636
42775
  /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center py-6", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
42637
- /* @__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) }),
42638
42777
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
42639
42778
  "Standard: ",
42640
42779
  workspace.ideal_cycle_time?.toFixed(1) || 0,
@@ -42754,7 +42893,7 @@ var WorkspaceDetailView = ({
42754
42893
  /* @__PURE__ */ jsxs(Card2, { children: [
42755
42894
  /* @__PURE__ */ jsx(CardHeader2, { className: "pb-2 flex-none", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg text-center", children: "Cycle Time (s)" }) }),
42756
42895
  /* @__PURE__ */ jsx(CardContent2, { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
42757
- /* @__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) }),
42758
42897
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500 mt-2", children: [
42759
42898
  "Standard: ",
42760
42899
  workspace.ideal_cycle_time?.toFixed(1) || 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "6.6.10",
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",