@optifye/dashboard-core 6.9.9 → 6.9.12

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.mjs CHANGED
@@ -2896,8 +2896,8 @@ var AuthService = class {
2896
2896
  "Authorization": `Bearer ${accessToken}`,
2897
2897
  "Content-Type": "application/json"
2898
2898
  },
2899
- timeout: 3e5,
2900
- // 5 minutes
2899
+ timeout: 1e4,
2900
+ // 10 seconds
2901
2901
  retries: 1,
2902
2902
  silentErrors: false
2903
2903
  // We want to know about auth errors
@@ -2934,8 +2934,8 @@ var AuthService = class {
2934
2934
  "Authorization": `Bearer ${accessToken}`,
2935
2935
  "Content-Type": "application/json"
2936
2936
  },
2937
- timeout: 3e5,
2938
- // 5 minutes
2937
+ timeout: 1e4,
2938
+ // 10 seconds
2939
2939
  retries: 2,
2940
2940
  // More retries for validation
2941
2941
  silentErrors: true,
@@ -2967,8 +2967,8 @@ var AuthService = class {
2967
2967
  "Authorization": `Bearer ${accessToken}`,
2968
2968
  "Content-Type": "application/json"
2969
2969
  },
2970
- timeout: 3e5,
2971
- // 5 minutes
2970
+ timeout: 1e4,
2971
+ // 10 seconds
2972
2972
  retries: 1,
2973
2973
  silentErrors: false
2974
2974
  }
@@ -10883,7 +10883,7 @@ function useDateFormatter() {
10883
10883
  },
10884
10884
  [defaultTimezone, defaultLocale, dateFormatOptions]
10885
10885
  );
10886
- const formatTime3 = useCallback(
10886
+ const formatTime4 = useCallback(
10887
10887
  (date, formatString) => {
10888
10888
  const dateObj = typeof date === "string" ? parseISO(date) : date;
10889
10889
  if (!isValid(dateObj)) return "Invalid Time";
@@ -10914,7 +10914,7 @@ function useDateFormatter() {
10914
10914
  }, []);
10915
10915
  return {
10916
10916
  formatDate,
10917
- formatTime: formatTime3,
10917
+ formatTime: formatTime4,
10918
10918
  formatDateTime,
10919
10919
  getNow,
10920
10920
  timezone: defaultTimezone || "UTC",
@@ -23070,7 +23070,7 @@ var OutputProgressChartComponent = ({
23070
23070
  ];
23071
23071
  const COLORS = ["#00AB45", "#f3f4f6"];
23072
23072
  const percentage = (currentOutput / targetOutput * 100).toFixed(1);
23073
- return /* @__PURE__ */ jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full aspect-square max-h-full", style: { maxWidth: "min(100%, 280px)" }, children: [
23073
+ return /* @__PURE__ */ jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full aspect-square max-h-full", style: { maxWidth: "min(100%, 280px)", containerType: "inline-size" }, children: [
23074
23074
  /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsx(PieChart, { children: /* @__PURE__ */ jsx(
23075
23075
  Pie,
23076
23076
  {
@@ -23094,16 +23094,33 @@ var OutputProgressChartComponent = ({
23094
23094
  ))
23095
23095
  }
23096
23096
  ) }) }),
23097
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
23098
- /* @__PURE__ */ jsxs("div", { className: "text-2xl sm:text-3xl lg:text-4xl font-bold text-gray-800", children: [
23099
- percentage,
23100
- "%"
23101
- ] }),
23102
- /* @__PURE__ */ jsxs("div", { className: "text-xs sm:text-sm lg:text-base text-gray-500 mt-1 sm:mt-2", children: [
23103
- currentOutput,
23104
- " / ",
23105
- Math.round(targetOutput)
23106
- ] })
23097
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
23098
+ /* @__PURE__ */ jsxs(
23099
+ "div",
23100
+ {
23101
+ className: "font-bold text-gray-800",
23102
+ style: { fontSize: "clamp(1.25rem, 8cqw, 2.5rem)" },
23103
+ children: [
23104
+ percentage,
23105
+ "%"
23106
+ ]
23107
+ }
23108
+ ),
23109
+ /* @__PURE__ */ jsxs(
23110
+ "div",
23111
+ {
23112
+ className: "text-gray-500",
23113
+ style: {
23114
+ fontSize: "clamp(0.7rem, 3.5cqw, 1rem)",
23115
+ marginTop: "clamp(0.125rem, 1cqw, 0.5rem)"
23116
+ },
23117
+ children: [
23118
+ currentOutput,
23119
+ " / ",
23120
+ Math.round(targetOutput)
23121
+ ]
23122
+ }
23123
+ )
23107
23124
  ] }) })
23108
23125
  ] }) });
23109
23126
  };
@@ -23121,7 +23138,7 @@ var LargeOutputProgressChart = ({
23121
23138
  ];
23122
23139
  const COLORS = ["#00AB45", "#f3f4f6"];
23123
23140
  const percentage = (currentOutput / targetOutput * 100).toFixed(1);
23124
- return /* @__PURE__ */ jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full", style: { minHeight: "100px", minWidth: "100px" }, children: [
23141
+ return /* @__PURE__ */ jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full", style: { minHeight: "100px", minWidth: "100px", containerType: "inline-size" }, children: [
23125
23142
  /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsx(PieChart, { children: /* @__PURE__ */ jsx(
23126
23143
  Pie,
23127
23144
  {
@@ -23145,16 +23162,40 @@ var LargeOutputProgressChart = ({
23145
23162
  ))
23146
23163
  }
23147
23164
  ) }) }),
23148
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
23149
- /* @__PURE__ */ jsx("div", { className: "text-4xl sm:text-5xl font-bold text-gray-900", children: currentOutput }),
23150
- /* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-500", children: [
23151
- "of ",
23152
- targetOutput
23153
- ] }),
23154
- /* @__PURE__ */ jsxs("div", { className: "text-base font-medium text-gray-600 mt-1", children: [
23155
- percentage,
23156
- "%"
23157
- ] })
23165
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
23166
+ /* @__PURE__ */ jsx(
23167
+ "div",
23168
+ {
23169
+ className: "font-bold text-gray-900",
23170
+ style: { fontSize: "clamp(1.5rem, 10cqw, 3rem)" },
23171
+ children: currentOutput
23172
+ }
23173
+ ),
23174
+ /* @__PURE__ */ jsxs(
23175
+ "div",
23176
+ {
23177
+ className: "text-gray-500",
23178
+ style: { fontSize: "clamp(0.75rem, 3.5cqw, 1rem)" },
23179
+ children: [
23180
+ "of ",
23181
+ targetOutput
23182
+ ]
23183
+ }
23184
+ ),
23185
+ /* @__PURE__ */ jsxs(
23186
+ "div",
23187
+ {
23188
+ className: "font-medium text-gray-600",
23189
+ style: {
23190
+ fontSize: "clamp(0.875rem, 4.5cqw, 1.25rem)",
23191
+ marginTop: "clamp(0.125rem, 1cqw, 0.5rem)"
23192
+ },
23193
+ children: [
23194
+ percentage,
23195
+ "%"
23196
+ ]
23197
+ }
23198
+ )
23158
23199
  ] }) })
23159
23200
  ] }) });
23160
23201
  };
@@ -23859,7 +23900,7 @@ var HourlyOutputChartComponent = ({
23859
23900
  endHour = Math.floor(endDecimalHour) % 24;
23860
23901
  endMinute = Math.round(endDecimalHour % 1 * 60);
23861
23902
  }
23862
- const formatTime3 = (h, m) => {
23903
+ const formatTime4 = (h, m) => {
23863
23904
  const period = h >= 12 ? "PM" : "AM";
23864
23905
  const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
23865
23906
  if (m === 0) {
@@ -23867,7 +23908,7 @@ var HourlyOutputChartComponent = ({
23867
23908
  }
23868
23909
  return `${hour12}:${m.toString().padStart(2, "0")}${period}`;
23869
23910
  };
23870
- return `${formatTime3(startHour, startMinute)}-${formatTime3(endHour, endMinute)}`;
23911
+ return `${formatTime4(startHour, startMinute)}-${formatTime4(endHour, endMinute)}`;
23871
23912
  }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
23872
23913
  const formatTimeRange = React23__default.useCallback((hourIndex) => {
23873
23914
  const isLastHour = hourIndex === SHIFT_DURATION - 1;
@@ -23883,12 +23924,12 @@ var HourlyOutputChartComponent = ({
23883
23924
  endHour = Math.floor(endDecimalHour) % 24;
23884
23925
  endMinute = Math.round(endDecimalHour % 1 * 60);
23885
23926
  }
23886
- const formatTime3 = (h, m) => {
23927
+ const formatTime4 = (h, m) => {
23887
23928
  const period = h >= 12 ? "PM" : "AM";
23888
23929
  const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
23889
23930
  return `${hour12}:${m.toString().padStart(2, "0")} ${period}`;
23890
23931
  };
23891
- return `${formatTime3(startHour, startMinute)} - ${formatTime3(endHour, endMinute)}`;
23932
+ return `${formatTime4(startHour, startMinute)} - ${formatTime4(endHour, endMinute)}`;
23892
23933
  }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
23893
23934
  const chartData = React23__default.useMemo(() => {
23894
23935
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
@@ -25059,7 +25100,7 @@ var SOPComplianceChart = ({
25059
25100
  }
25060
25101
  };
25061
25102
  }, [data, animateToNewData, mockData]);
25062
- const formatTime3 = (minuteIndex) => {
25103
+ const formatTime4 = (minuteIndex) => {
25063
25104
  const totalMinutes = shiftStartHour * 60 + minuteIndex;
25064
25105
  const hours = Math.floor(totalMinutes / 60) % 24;
25065
25106
  const minutes = totalMinutes % 60;
@@ -25071,7 +25112,7 @@ var SOPComplianceChart = ({
25071
25112
  const hasDataForMinute = index < animatedData.length - 10;
25072
25113
  return {
25073
25114
  minute: index,
25074
- time: formatTime3(index),
25115
+ time: formatTime4(index),
25075
25116
  compliance: hasDataForMinute ? animatedData[index] : null
25076
25117
  };
25077
25118
  });
@@ -25248,7 +25289,7 @@ var GaugeChart = ({
25248
25289
  };
25249
25290
  const gaugeColor = getColor();
25250
25291
  const targetAngle = target !== void 0 ? 180 - (target - min) / (max - min) * 180 : null;
25251
- return /* @__PURE__ */ jsx("div", { className: `relative w-full h-full flex flex-col items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-[280px] aspect-square", style: { minHeight: "100px", minWidth: "100px" }, children: [
25292
+ return /* @__PURE__ */ jsx("div", { className: `relative w-full h-full flex flex-col items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-[280px] aspect-square", style: { minHeight: "100px", minWidth: "100px", containerType: "inline-size" }, children: [
25252
25293
  /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsx(PieChart, { children: /* @__PURE__ */ jsxs(
25253
25294
  Pie,
25254
25295
  {
@@ -25281,17 +25322,44 @@ var GaugeChart = ({
25281
25322
  children: /* @__PURE__ */ jsx("div", { className: "absolute -top-1 -left-1.5 w-3 h-3 bg-gray-800 rounded-full" })
25282
25323
  }
25283
25324
  ),
25284
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
25285
- /* @__PURE__ */ jsxs("div", { className: "text-3xl font-bold text-gray-800", children: [
25286
- value.toFixed(unit === "%" ? 1 : 0),
25287
- unit
25288
- ] }),
25289
- /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-600 mt-1 font-medium", children: label }),
25290
- target !== void 0 && /* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-500 mt-1", children: [
25291
- "Target: ",
25292
- target,
25293
- unit
25294
- ] })
25325
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
25326
+ /* @__PURE__ */ jsxs(
25327
+ "div",
25328
+ {
25329
+ className: "font-bold text-gray-800",
25330
+ style: { fontSize: "clamp(1.25rem, 8cqw, 2rem)" },
25331
+ children: [
25332
+ value.toFixed(unit === "%" ? 1 : 0),
25333
+ unit
25334
+ ]
25335
+ }
25336
+ ),
25337
+ /* @__PURE__ */ jsx(
25338
+ "div",
25339
+ {
25340
+ className: "text-gray-600 font-medium",
25341
+ style: {
25342
+ fontSize: "clamp(0.75rem, 3.5cqw, 1rem)",
25343
+ marginTop: "clamp(0.125rem, 1cqw, 0.25rem)"
25344
+ },
25345
+ children: label
25346
+ }
25347
+ ),
25348
+ target !== void 0 && /* @__PURE__ */ jsxs(
25349
+ "div",
25350
+ {
25351
+ className: "text-gray-500",
25352
+ style: {
25353
+ fontSize: "clamp(0.7rem, 3cqw, 0.875rem)",
25354
+ marginTop: "clamp(0.125rem, 1cqw, 0.25rem)"
25355
+ },
25356
+ children: [
25357
+ "Target: ",
25358
+ target,
25359
+ unit
25360
+ ]
25361
+ }
25362
+ )
25295
25363
  ] }) }),
25296
25364
  /* @__PURE__ */ jsxs("div", { className: "absolute bottom-[15%] left-[15%] text-xs text-gray-500", children: [
25297
25365
  min,
@@ -25478,7 +25546,7 @@ var DateTimeDisplay = ({
25478
25546
  const {
25479
25547
  defaultTimezone
25480
25548
  } = useDateTimeConfig();
25481
- const { formatDate, formatTime: formatTime3 } = useDateFormatter();
25549
+ const { formatDate, formatTime: formatTime4 } = useDateFormatter();
25482
25550
  const [now2, setNow] = useState(() => getCurrentTimeInZone(defaultTimezone || "UTC"));
25483
25551
  useEffect(() => {
25484
25552
  const timerId = setInterval(() => {
@@ -25490,7 +25558,7 @@ var DateTimeDisplay = ({
25490
25558
  return null;
25491
25559
  }
25492
25560
  const formattedDate = showDate ? formatDate(now2) : "";
25493
- const formattedTime = showTime ? formatTime3(now2) : "";
25561
+ const formattedTime = showTime ? formatTime4(now2) : "";
25494
25562
  return /* @__PURE__ */ jsxs("div", { className: clsx_default("flex items-center space-x-2 text-sm text-gray-700 dark:text-gray-300", className), children: [
25495
25563
  showDate && /* @__PURE__ */ jsx("span", { className: "date-display", "aria-label": `Current date: ${formattedDate}`, children: formattedDate }),
25496
25564
  showDate && showTime && formattedDate && formattedTime && /* @__PURE__ */ jsx("span", { className: "separator", "aria-hidden": "true", children: "|" }),
@@ -25654,7 +25722,7 @@ var BreakNotificationPopup = ({
25654
25722
  const handlePrevious = () => {
25655
25723
  setCurrentIndex((prev) => (prev - 1 + visibleBreaks.length) % visibleBreaks.length);
25656
25724
  };
25657
- const formatTime3 = (minutes) => {
25725
+ const formatTime4 = (minutes) => {
25658
25726
  const hours = Math.floor(minutes / 60);
25659
25727
  const mins = minutes % 60;
25660
25728
  if (hours > 0) {
@@ -25728,9 +25796,9 @@ var BreakNotificationPopup = ({
25728
25796
  formatTo12Hour(currentBreak.endTime)
25729
25797
  ] }),
25730
25798
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 mb-2", children: [
25731
- formatTime3(currentBreak.elapsedMinutes),
25799
+ formatTime4(currentBreak.elapsedMinutes),
25732
25800
  " elapsed of ",
25733
- formatTime3(currentBreak.duration),
25801
+ formatTime4(currentBreak.duration),
25734
25802
  " total"
25735
25803
  ] }),
25736
25804
  /* @__PURE__ */ jsx("div", { className: "w-full bg-gray-200 rounded-full h-1.5", children: /* @__PURE__ */ jsx(
@@ -26034,64 +26102,208 @@ var getSeverityColor = (severity) => {
26034
26102
  return "bg-gray-500";
26035
26103
  }
26036
26104
  };
26037
- var PlayPauseIndicator = ({
26038
- show,
26105
+ var formatTime2 = (seconds) => {
26106
+ if (!seconds || isNaN(seconds)) return "0:00";
26107
+ const h = Math.floor(seconds / 3600);
26108
+ const m = Math.floor(seconds % 3600 / 60);
26109
+ const s = Math.floor(seconds % 60);
26110
+ if (h > 0) {
26111
+ return `${h}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
26112
+ }
26113
+ return `${m}:${s.toString().padStart(2, "0")}`;
26114
+ };
26115
+ var VideoControls = ({
26039
26116
  isPlaying,
26040
- duration = 600
26117
+ currentTime,
26118
+ duration,
26119
+ buffered,
26120
+ showControls,
26121
+ controlsPinned = false,
26122
+ onTogglePinControls,
26123
+ playbackRate = 1,
26124
+ onPlayPause,
26125
+ onSeek,
26126
+ onSeekStart,
26127
+ onSeekEnd,
26128
+ onToggleFullscreen,
26129
+ onPlaybackRateChange,
26130
+ className = ""
26041
26131
  }) => {
26042
- const [isVisible, setIsVisible] = useState(false);
26043
- const [isFading, setIsFading] = useState(false);
26132
+ const [isDragging, setIsDragging] = useState(false);
26133
+ const [dragTime, setDragTime] = useState(0);
26134
+ const [isHoveringProgressBar, setIsHoveringProgressBar] = useState(false);
26135
+ const [showSpeedMenu, setShowSpeedMenu] = useState(false);
26136
+ const speedMenuRef = useRef(null);
26137
+ const progressColor = "#4b5563";
26138
+ const controlsVisible = showControls || controlsPinned;
26139
+ const getPercentage = (current, total) => {
26140
+ if (!total || total === 0) return 0;
26141
+ return Math.min(Math.max(current / total * 100, 0), 100);
26142
+ };
26143
+ const handleSeekChange = (e) => {
26144
+ const newTime = parseFloat(e.target.value);
26145
+ setDragTime(newTime);
26146
+ onSeek(newTime);
26147
+ };
26148
+ const handleSeekStart = () => {
26149
+ setIsDragging(true);
26150
+ setDragTime(currentTime);
26151
+ onSeekStart?.();
26152
+ };
26153
+ const handleSeekEnd = () => {
26154
+ setIsDragging(false);
26155
+ onSeekEnd?.();
26156
+ };
26044
26157
  useEffect(() => {
26045
- if (show) {
26046
- setIsVisible(true);
26047
- setIsFading(false);
26048
- const fadeTimer = setTimeout(() => {
26049
- setIsFading(true);
26050
- }, 100);
26051
- const hideTimer = setTimeout(() => {
26052
- setIsVisible(false);
26053
- setIsFading(false);
26054
- }, duration);
26055
- return () => {
26056
- clearTimeout(fadeTimer);
26057
- clearTimeout(hideTimer);
26058
- };
26059
- }
26060
- }, [show, duration]);
26061
- if (!isVisible) return null;
26062
- return /* @__PURE__ */ jsx(
26158
+ const handleClickOutside = (event) => {
26159
+ if (speedMenuRef.current && !speedMenuRef.current.contains(event.target)) {
26160
+ setShowSpeedMenu(false);
26161
+ }
26162
+ };
26163
+ document.addEventListener("mousedown", handleClickOutside);
26164
+ return () => {
26165
+ document.removeEventListener("mousedown", handleClickOutside);
26166
+ };
26167
+ }, []);
26168
+ const displayTime = isDragging ? dragTime : currentTime;
26169
+ const progressPercent = getPercentage(displayTime, duration);
26170
+ const bufferedPercent = getPercentage(buffered, duration);
26171
+ return /* @__PURE__ */ jsxs(
26063
26172
  "div",
26064
26173
  {
26065
- className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
26066
- style: {
26067
- opacity: isFading ? 0 : 1,
26068
- transition: `opacity ${duration - 100}ms ease-out`
26069
- },
26070
- children: /* @__PURE__ */ jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
26071
- // Play icon (triangle)
26072
- /* @__PURE__ */ jsx(
26073
- "svg",
26074
- {
26075
- xmlns: "http://www.w3.org/2000/svg",
26076
- viewBox: "0 0 24 24",
26077
- fill: "white",
26078
- className: "w-16 h-16",
26079
- children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" })
26080
- }
26081
- )
26082
- ) : (
26083
- // Pause icon (two bars)
26084
- /* @__PURE__ */ jsx(
26085
- "svg",
26174
+ className: `absolute bottom-0 left-0 right-0 px-3 pb-3 pt-12 bg-gradient-to-t from-black/80 via-black/40 to-transparent transition-opacity duration-300 ${controlsVisible ? "opacity-100" : "opacity-0 pointer-events-none"} ${className}`,
26175
+ style: { touchAction: "none" },
26176
+ children: [
26177
+ /* @__PURE__ */ jsxs(
26178
+ "div",
26086
26179
  {
26087
- xmlns: "http://www.w3.org/2000/svg",
26088
- viewBox: "0 0 24 24",
26089
- fill: "white",
26090
- className: "w-16 h-16",
26091
- children: /* @__PURE__ */ jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
26180
+ className: "relative h-1 mb-4 group cursor-pointer",
26181
+ onMouseEnter: () => setIsHoveringProgressBar(true),
26182
+ onMouseLeave: () => setIsHoveringProgressBar(false),
26183
+ children: [
26184
+ /* @__PURE__ */ jsx("div", { className: "absolute -top-2 -bottom-2 left-0 right-0 z-20" }),
26185
+ /* @__PURE__ */ jsx("div", { className: "absolute top-0 left-0 right-0 bottom-0 bg-white/20 rounded-full overflow-hidden z-0", children: /* @__PURE__ */ jsx(
26186
+ "div",
26187
+ {
26188
+ className: "absolute top-0 left-0 bottom-0 bg-white/40 transition-all duration-200",
26189
+ style: { width: `${bufferedPercent}%` }
26190
+ }
26191
+ ) }),
26192
+ /* @__PURE__ */ jsx(
26193
+ "div",
26194
+ {
26195
+ className: "absolute top-0 left-0 bottom-0 bg-[#007bff] transition-all duration-75 z-10",
26196
+ style: { width: `${progressPercent}%`, backgroundColor: progressColor },
26197
+ children: /* @__PURE__ */ jsx(
26198
+ "div",
26199
+ {
26200
+ className: `absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 w-3 h-3 rounded-full shadow transform transition-transform duration-200 ${isHoveringProgressBar || isDragging ? "scale-100" : "scale-0"}`,
26201
+ style: { backgroundColor: progressColor }
26202
+ }
26203
+ )
26204
+ }
26205
+ ),
26206
+ /* @__PURE__ */ jsx(
26207
+ "input",
26208
+ {
26209
+ type: "range",
26210
+ min: "0",
26211
+ max: duration || 100,
26212
+ step: "0.1",
26213
+ value: displayTime,
26214
+ onChange: handleSeekChange,
26215
+ onMouseDown: handleSeekStart,
26216
+ onMouseUp: handleSeekEnd,
26217
+ onTouchStart: handleSeekStart,
26218
+ onTouchEnd: handleSeekEnd,
26219
+ className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer z-30 margin-0 padding-0"
26220
+ }
26221
+ )
26222
+ ]
26092
26223
  }
26093
- )
26094
- ) })
26224
+ ),
26225
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-white", children: [
26226
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
26227
+ /* @__PURE__ */ jsx(
26228
+ "button",
26229
+ {
26230
+ onClick: (e) => {
26231
+ e.stopPropagation();
26232
+ onPlayPause();
26233
+ },
26234
+ className: "hover:text-[#007bff] transition-colors focus:outline-none",
26235
+ "aria-label": isPlaying ? "Pause" : "Play",
26236
+ children: isPlaying ? /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" }) }) : /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" }) })
26237
+ }
26238
+ ),
26239
+ /* @__PURE__ */ jsxs("div", { className: "text-xs font-medium font-sans", children: [
26240
+ /* @__PURE__ */ jsx("span", { children: formatTime2(displayTime) }),
26241
+ /* @__PURE__ */ jsx("span", { className: "mx-1 text-white/70", children: "/" }),
26242
+ /* @__PURE__ */ jsx("span", { className: "text-white/70", children: formatTime2(duration) })
26243
+ ] })
26244
+ ] }),
26245
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
26246
+ onTogglePinControls && /* @__PURE__ */ jsx(
26247
+ "button",
26248
+ {
26249
+ onClick: (e) => {
26250
+ e.stopPropagation();
26251
+ onTogglePinControls();
26252
+ },
26253
+ className: `transition-colors focus:outline-none ${controlsPinned ? "text-[#007bff]" : "hover:text-[#007bff]"}`,
26254
+ "aria-label": controlsPinned ? "Unpin controls" : "Pin controls",
26255
+ title: controlsPinned ? "Unpin controls" : "Pin controls",
26256
+ children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: controlsPinned ? /* @__PURE__ */ jsx("path", { d: "M9 3h6l-1 7h3v2h-4.5l-.5 4.5-2 1L10 12H6v-2h3z" }) : /* @__PURE__ */ jsx("path", { d: "M9 3h6l-1 7h3v2h-4v5l-2 1-1-6H6v-2h3z" }) })
26257
+ }
26258
+ ),
26259
+ onPlaybackRateChange && /* @__PURE__ */ jsxs("div", { className: "relative", ref: speedMenuRef, children: [
26260
+ /* @__PURE__ */ jsxs(
26261
+ "button",
26262
+ {
26263
+ onClick: (e) => {
26264
+ e.stopPropagation();
26265
+ setShowSpeedMenu(!showSpeedMenu);
26266
+ },
26267
+ className: "text-xs font-medium hover:text-[#007bff] transition-colors focus:outline-none min-w-[32px]",
26268
+ "aria-label": "Playback Speed",
26269
+ children: [
26270
+ playbackRate,
26271
+ "x"
26272
+ ]
26273
+ }
26274
+ ),
26275
+ showSpeedMenu && /* @__PURE__ */ jsx("div", { className: "absolute bottom-full right-0 mb-2 bg-black/90 text-white rounded shadow-lg overflow-hidden z-50 min-w-[80px]", children: [0.5, 1, 1.5, 2, 2.5, 3, 4, 5].map((rate) => /* @__PURE__ */ jsxs(
26276
+ "button",
26277
+ {
26278
+ onClick: (e) => {
26279
+ e.stopPropagation();
26280
+ onPlaybackRateChange(rate);
26281
+ setShowSpeedMenu(false);
26282
+ },
26283
+ className: `block w-full text-left px-4 py-2 text-xs hover:bg-white/20 transition-colors ${playbackRate === rate ? "text-[#007bff] font-bold" : ""}`,
26284
+ children: [
26285
+ rate,
26286
+ "x"
26287
+ ]
26288
+ },
26289
+ rate
26290
+ )) })
26291
+ ] }),
26292
+ onToggleFullscreen && /* @__PURE__ */ jsx(
26293
+ "button",
26294
+ {
26295
+ onClick: (e) => {
26296
+ e.stopPropagation();
26297
+ onToggleFullscreen();
26298
+ },
26299
+ className: "hover:text-[#007bff] transition-colors focus:outline-none",
26300
+ "aria-label": "Toggle Fullscreen",
26301
+ children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
26302
+ }
26303
+ )
26304
+ ] })
26305
+ ] })
26306
+ ]
26095
26307
  }
26096
26308
  );
26097
26309
  };
@@ -26219,9 +26431,15 @@ var HlsVideoPlayer = forwardRef(({
26219
26431
  const blobUrlRef = useRef(null);
26220
26432
  const [isReady, setIsReady] = useState(false);
26221
26433
  const [isLoading, setIsLoading] = useState(true);
26222
- const [showIndicator, setShowIndicator] = useState(false);
26223
- const [indicatorIsPlaying, setIndicatorIsPlaying] = useState(false);
26224
- const indicatorKeyRef = useRef(0);
26434
+ const [showControls, setShowControls] = useState(true);
26435
+ const [controlsPinned, setControlsPinned] = useState(false);
26436
+ const [isPlaying, setIsPlaying] = useState(false);
26437
+ const [currentTime, setCurrentTime] = useState(0);
26438
+ const [duration, setDuration] = useState(0);
26439
+ const [buffered, setBuffered] = useState(0);
26440
+ const [playbackRate, setPlaybackRate] = useState(1);
26441
+ const userSeekingRef = useRef(false);
26442
+ const controlsTimeoutRef = useRef(null);
26225
26443
  const eventCallbacksRef = useRef({
26226
26444
  onReady,
26227
26445
  onPlay,
@@ -26400,8 +26618,6 @@ var HlsVideoPlayer = forwardRef(({
26400
26618
  }
26401
26619
  });
26402
26620
  hls.on(Events.FRAG_LOADING, () => {
26403
- setIsLoading(true);
26404
- eventCallbacksRef.current.onLoadingChange?.(true);
26405
26621
  });
26406
26622
  hls.on(Events.FRAG_LOADED, () => {
26407
26623
  setIsLoading(false);
@@ -26455,25 +26671,52 @@ var HlsVideoPlayer = forwardRef(({
26455
26671
  const handleCanPlay = () => {
26456
26672
  if (!hlsRef.current) {
26457
26673
  setIsReady(true);
26458
- onReady?.(player);
26459
26674
  }
26675
+ setIsLoading(false);
26676
+ eventCallbacksRef.current.onLoadingChange?.(false);
26677
+ onReady?.(player);
26678
+ };
26679
+ const handlePlay = () => {
26680
+ setIsPlaying(true);
26681
+ eventCallbacksRef.current.onPlay?.(player);
26682
+ };
26683
+ const handlePause = () => {
26684
+ if (userSeekingRef.current && videoRef.current) {
26685
+ videoRef.current.play().catch((err) => console.warn("Auto-resume after seek pause failed:", err));
26686
+ return;
26687
+ }
26688
+ setIsPlaying(false);
26689
+ eventCallbacksRef.current.onPause?.(player);
26460
26690
  };
26461
- const handlePlay = () => eventCallbacksRef.current.onPlay?.(player);
26462
- const handlePause = () => eventCallbacksRef.current.onPause?.(player);
26463
26691
  const handlePlaying = () => {
26464
26692
  setIsLoading(false);
26693
+ setIsPlaying(true);
26465
26694
  eventCallbacksRef.current.onLoadingChange?.(false);
26466
26695
  eventCallbacksRef.current.onPlaying?.(player);
26467
26696
  };
26468
26697
  const handleTimeUpdate = () => {
26469
26698
  const currentTime2 = video.currentTime || 0;
26699
+ setCurrentTime(currentTime2);
26700
+ if (video.buffered.length > 0) {
26701
+ for (let i = 0; i < video.buffered.length; i++) {
26702
+ if (video.buffered.start(i) <= currentTime2 && video.buffered.end(i) >= currentTime2) {
26703
+ setBuffered(video.buffered.end(i));
26704
+ break;
26705
+ }
26706
+ }
26707
+ }
26470
26708
  eventCallbacksRef.current.onTimeUpdate?.(player, currentTime2);
26471
26709
  };
26472
26710
  const handleDurationChange = () => {
26473
26711
  const duration2 = video.duration || 0;
26712
+ setDuration(duration2);
26474
26713
  eventCallbacksRef.current.onDurationChange?.(player, duration2);
26475
26714
  };
26476
- const handleEnded = () => eventCallbacksRef.current.onEnded?.(player);
26715
+ const handleEnded = () => {
26716
+ setIsPlaying(false);
26717
+ userSeekingRef.current = false;
26718
+ eventCallbacksRef.current.onEnded?.(player);
26719
+ };
26477
26720
  const handleLoadStart = () => {
26478
26721
  setIsLoading(true);
26479
26722
  eventCallbacksRef.current.onLoadingChange?.(true);
@@ -26489,8 +26732,19 @@ var HlsVideoPlayer = forwardRef(({
26489
26732
  setIsLoading(true);
26490
26733
  eventCallbacksRef.current.onLoadingChange?.(true);
26491
26734
  };
26492
- const handleSeeking = () => eventCallbacksRef.current.onSeeking?.(player);
26493
- const handleSeeked = () => eventCallbacksRef.current.onSeeked?.(player);
26735
+ const handleSeeking = () => {
26736
+ userSeekingRef.current = true;
26737
+ eventCallbacksRef.current.onSeeking?.(player);
26738
+ };
26739
+ const handleSeeked = () => {
26740
+ setIsLoading(false);
26741
+ eventCallbacksRef.current.onLoadingChange?.(false);
26742
+ if (videoRef.current) {
26743
+ videoRef.current.play().catch((err) => console.warn("Resume playback after seek failed:", err));
26744
+ }
26745
+ userSeekingRef.current = false;
26746
+ eventCallbacksRef.current.onSeeked?.(player);
26747
+ };
26494
26748
  const handleError = () => {
26495
26749
  const error = video.error;
26496
26750
  if (error) {
@@ -26503,6 +26757,10 @@ var HlsVideoPlayer = forwardRef(({
26503
26757
  eventCallbacksRef.current.onError?.(player, errorInfo);
26504
26758
  }
26505
26759
  };
26760
+ const handlePlaybackRateChange2 = (e) => {
26761
+ const target = e.target;
26762
+ setPlaybackRate(target.playbackRate);
26763
+ };
26506
26764
  video.addEventListener("canplay", handleCanPlay);
26507
26765
  video.addEventListener("play", handlePlay);
26508
26766
  video.addEventListener("pause", handlePause);
@@ -26517,6 +26775,7 @@ var HlsVideoPlayer = forwardRef(({
26517
26775
  video.addEventListener("seeking", handleSeeking);
26518
26776
  video.addEventListener("seeked", handleSeeked);
26519
26777
  video.addEventListener("error", handleError);
26778
+ video.addEventListener("ratechange", handlePlaybackRateChange2);
26520
26779
  return () => {
26521
26780
  video.removeEventListener("canplay", handleCanPlay);
26522
26781
  video.removeEventListener("play", handlePlay);
@@ -26532,6 +26791,7 @@ var HlsVideoPlayer = forwardRef(({
26532
26791
  video.removeEventListener("seeking", handleSeeking);
26533
26792
  video.removeEventListener("seeked", handleSeeked);
26534
26793
  video.removeEventListener("error", handleError);
26794
+ video.removeEventListener("ratechange", handlePlaybackRateChange2);
26535
26795
  };
26536
26796
  }, [
26537
26797
  src,
@@ -26560,20 +26820,46 @@ var HlsVideoPlayer = forwardRef(({
26560
26820
  }
26561
26821
  }
26562
26822
  }, [autoplay]);
26823
+ const resetControlsTimeout = useCallback(() => {
26824
+ if (controlsPinned) {
26825
+ setShowControls(true);
26826
+ return;
26827
+ }
26828
+ setShowControls(true);
26829
+ if (controlsTimeoutRef.current) {
26830
+ clearTimeout(controlsTimeoutRef.current);
26831
+ }
26832
+ if (isPlaying) {
26833
+ controlsTimeoutRef.current = setTimeout(() => {
26834
+ setShowControls(false);
26835
+ }, 3e3);
26836
+ }
26837
+ }, [isPlaying, controlsPinned]);
26838
+ const handleMouseMove = useCallback(() => {
26839
+ resetControlsTimeout();
26840
+ }, [resetControlsTimeout]);
26841
+ useEffect(() => {
26842
+ resetControlsTimeout();
26843
+ return () => {
26844
+ if (controlsTimeoutRef.current) {
26845
+ clearTimeout(controlsTimeoutRef.current);
26846
+ }
26847
+ };
26848
+ }, [isPlaying, resetControlsTimeout]);
26563
26849
  const play = useCallback(() => {
26564
26850
  return videoRef.current?.play();
26565
26851
  }, []);
26566
26852
  const pause = useCallback(() => {
26567
26853
  videoRef.current?.pause();
26568
26854
  }, []);
26569
- const currentTime = useCallback((time2) => {
26855
+ const currentTimeProp = useCallback((time2) => {
26570
26856
  if (time2 !== void 0 && videoRef.current) {
26571
26857
  videoRef.current.currentTime = time2;
26572
26858
  return time2;
26573
26859
  }
26574
26860
  return videoRef.current?.currentTime || 0;
26575
26861
  }, []);
26576
- const duration = useCallback(() => {
26862
+ const durationProp = useCallback(() => {
26577
26863
  return videoRef.current?.duration || 0;
26578
26864
  }, []);
26579
26865
  const paused = useCallback(() => {
@@ -26586,95 +26872,144 @@ var HlsVideoPlayer = forwardRef(({
26586
26872
  }
26587
26873
  return videoRef.current?.muted ?? false;
26588
26874
  }, []);
26589
- const volume = useCallback((level) => {
26875
+ const volumeProp = useCallback((level) => {
26590
26876
  if (level !== void 0 && videoRef.current) {
26591
26877
  videoRef.current.volume = level;
26592
26878
  return level;
26593
26879
  }
26594
26880
  return videoRef.current?.volume ?? 1;
26595
26881
  }, []);
26596
- const playbackRate = useCallback((rate) => {
26882
+ const playbackRateProp = useCallback((rate) => {
26597
26883
  if (rate !== void 0 && videoRef.current) {
26598
26884
  videoRef.current.playbackRate = rate;
26599
26885
  return rate;
26600
26886
  }
26601
26887
  return videoRef.current?.playbackRate ?? 1;
26602
26888
  }, []);
26889
+ const handleTogglePlay = useCallback(() => {
26890
+ if (videoRef.current) {
26891
+ if (videoRef.current.paused) {
26892
+ videoRef.current.play();
26893
+ } else {
26894
+ videoRef.current.pause();
26895
+ }
26896
+ }
26897
+ }, []);
26898
+ const handleSeek = useCallback((time2) => {
26899
+ if (videoRef.current) {
26900
+ videoRef.current.currentTime = time2;
26901
+ videoRef.current.play().catch((err) => console.warn("Resume playback failed during seek:", err));
26902
+ }
26903
+ }, []);
26904
+ const handleSeekStart = useCallback(() => {
26905
+ userSeekingRef.current = true;
26906
+ }, []);
26907
+ const handleSeekEnd = useCallback(() => {
26908
+ if (videoRef.current) {
26909
+ videoRef.current.play().catch((err) => console.warn("Resume playback failed after seek:", err));
26910
+ }
26911
+ }, []);
26912
+ const handlePlaybackRateChange = useCallback((rate) => {
26913
+ if (videoRef.current) {
26914
+ videoRef.current.playbackRate = rate;
26915
+ }
26916
+ }, []);
26917
+ const handleToggleFullscreen = useCallback(() => {
26918
+ if (videoContainerRef.current) {
26919
+ if (!document.fullscreenElement) {
26920
+ videoContainerRef.current.requestFullscreen();
26921
+ } else {
26922
+ document.exitFullscreen();
26923
+ }
26924
+ }
26925
+ }, []);
26603
26926
  useImperativeHandle(ref, () => ({
26604
26927
  hls: hlsRef.current,
26605
26928
  video: videoRef.current,
26606
26929
  play,
26607
26930
  pause,
26608
- currentTime,
26609
- duration,
26931
+ currentTime: currentTimeProp,
26932
+ duration: durationProp,
26610
26933
  paused,
26611
26934
  mute,
26612
- volume,
26613
- playbackRate,
26935
+ volume: volumeProp,
26936
+ playbackRate: playbackRateProp,
26614
26937
  dispose,
26615
26938
  isReady,
26616
26939
  // For backward compatibility with Video.js API
26617
26940
  player: playerLikeObject()
26618
- }), [play, pause, currentTime, duration, paused, mute, volume, playbackRate, dispose, isReady, playerLikeObject]);
26619
- const handleClickWithIndicator = useCallback(() => {
26620
- if (!onClick || !videoRef.current) return;
26621
- const willBePlaying = videoRef.current.paused;
26622
- setIndicatorIsPlaying(willBePlaying);
26623
- setShowIndicator(false);
26624
- setTimeout(() => {
26625
- indicatorKeyRef.current += 1;
26626
- setShowIndicator(true);
26627
- }, 0);
26628
- onClick();
26629
- }, [onClick]);
26630
- return /* @__PURE__ */ jsxs("div", { className: `hls-video-player-wrapper ${className}`, style: { position: "relative", width: "100%", height: "100%" }, children: [
26631
- /* @__PURE__ */ jsx(
26632
- "div",
26633
- {
26634
- className: "hls-video-player-container",
26635
- ref: videoContainerRef,
26636
- children: /* @__PURE__ */ jsx(
26637
- "video",
26941
+ }), [play, pause, currentTimeProp, durationProp, paused, mute, volumeProp, playbackRateProp, dispose, isReady, playerLikeObject]);
26942
+ const handleContainerClick = useCallback(() => {
26943
+ if (!onClick && !controls) {
26944
+ handleTogglePlay();
26945
+ }
26946
+ if (onClick) {
26947
+ onClick();
26948
+ }
26949
+ }, [onClick, controls, handleTogglePlay]);
26950
+ return /* @__PURE__ */ jsxs(
26951
+ "div",
26952
+ {
26953
+ className: `hls-video-player-wrapper ${className} group`,
26954
+ style: { position: "relative", width: "100%", height: "100%" },
26955
+ onMouseMove: handleMouseMove,
26956
+ onMouseLeave: () => isPlaying && !controlsPinned && setShowControls(false),
26957
+ children: [
26958
+ /* @__PURE__ */ jsxs(
26959
+ "div",
26638
26960
  {
26639
- ref: videoRef,
26640
- className: "hls-video-element",
26641
- poster,
26642
- controls,
26643
- loop,
26644
- muted,
26645
- playsInline,
26646
- autoPlay: autoplay,
26647
- preload: "metadata"
26961
+ className: "hls-video-player-container",
26962
+ ref: videoContainerRef,
26963
+ onClick: handleContainerClick,
26964
+ children: [
26965
+ /* @__PURE__ */ jsx(
26966
+ "video",
26967
+ {
26968
+ ref: videoRef,
26969
+ className: "hls-video-element",
26970
+ poster,
26971
+ controls: false,
26972
+ loop,
26973
+ muted,
26974
+ playsInline,
26975
+ autoPlay: autoplay,
26976
+ preload: "metadata"
26977
+ }
26978
+ ),
26979
+ controls && /* @__PURE__ */ jsx(
26980
+ VideoControls,
26981
+ {
26982
+ isPlaying,
26983
+ currentTime,
26984
+ duration,
26985
+ buffered,
26986
+ showControls: controlsPinned || showControls || !isPlaying,
26987
+ controlsPinned,
26988
+ playbackRate,
26989
+ onPlayPause: handleTogglePlay,
26990
+ onSeek: handleSeek,
26991
+ onSeekStart: handleSeekStart,
26992
+ onSeekEnd: handleSeekEnd,
26993
+ onPlaybackRateChange: handlePlaybackRateChange,
26994
+ onTogglePinControls: () => setControlsPinned((prev) => {
26995
+ const next = !prev;
26996
+ if (next) {
26997
+ setShowControls(true);
26998
+ } else {
26999
+ resetControlsTimeout();
27000
+ }
27001
+ return next;
27002
+ }),
27003
+ onToggleFullscreen: handleToggleFullscreen
27004
+ }
27005
+ )
27006
+ ]
26648
27007
  }
26649
- )
26650
- }
26651
- ),
26652
- isLoading && !externalLoadingControl && /* @__PURE__ */ jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
26653
- onClick && !controls && /* @__PURE__ */ jsx(
26654
- "div",
26655
- {
26656
- onClick: handleClickWithIndicator,
26657
- style: {
26658
- position: "absolute",
26659
- top: 0,
26660
- left: 0,
26661
- right: 0,
26662
- bottom: 0,
26663
- zIndex: 1,
26664
- cursor: "pointer"
26665
- },
26666
- "aria-label": "Click to play/pause"
26667
- }
26668
- ),
26669
- onClick && !controls && /* @__PURE__ */ jsx(
26670
- PlayPauseIndicator,
26671
- {
26672
- show: showIndicator,
26673
- isPlaying: indicatorIsPlaying
26674
- },
26675
- indicatorKeyRef.current
26676
- )
26677
- ] });
27008
+ ),
27009
+ isLoading && !externalLoadingControl && /* @__PURE__ */ jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) })
27010
+ ]
27011
+ }
27012
+ );
26678
27013
  });
26679
27014
  HlsVideoPlayer.displayName = "HlsVideoPlayer";
26680
27015
  var VideoPlayer = HlsVideoPlayer;
@@ -26682,6 +27017,7 @@ var CroppedHlsVideoPlayer = forwardRef(({
26682
27017
  crop,
26683
27018
  debug = false,
26684
27019
  onClick,
27020
+ controls = true,
26685
27021
  ...videoProps
26686
27022
  }, ref) => {
26687
27023
  const {
@@ -26703,9 +27039,15 @@ var CroppedHlsVideoPlayer = forwardRef(({
26703
27039
  const [isVideoReady, setIsVideoReady] = useState(false);
26704
27040
  const [canvasDimensions, setCanvasDimensions] = useState({ width: 0, height: 0 });
26705
27041
  const [isProcessing, setIsProcessing] = useState(false);
26706
- const [showIndicator, setShowIndicator] = useState(false);
26707
- const [indicatorIsPlaying, setIndicatorIsPlaying] = useState(false);
26708
- const indicatorKeyRef = useRef(0);
27042
+ const [showControls, setShowControls] = useState(true);
27043
+ const [isPlaying, setIsPlaying] = useState(false);
27044
+ const [currentTime, setCurrentTime] = useState(0);
27045
+ const [duration, setDuration] = useState(0);
27046
+ const [buffered, setBuffered] = useState(0);
27047
+ const [playbackRate, setPlaybackRate] = useState(1);
27048
+ const controlsTimeoutRef = useRef(null);
27049
+ const userSeekingRef = useRef(false);
27050
+ const [controlsPinned, setControlsPinned] = useState(false);
26709
27051
  const stopCanvasRendering = useCallback(() => {
26710
27052
  if (animationFrameRef.current) {
26711
27053
  cancelAnimationFrame(animationFrameRef.current);
@@ -26790,7 +27132,7 @@ var CroppedHlsVideoPlayer = forwardRef(({
26790
27132
  const canvas = canvasRef.current;
26791
27133
  const video = videoElementRef.current;
26792
27134
  const ctx = canvas.getContext("2d");
26793
- if (!ctx || video.paused || video.ended) {
27135
+ if (!ctx || video.readyState < 2) {
26794
27136
  return;
26795
27137
  }
26796
27138
  const videoWidth = video.videoWidth;
@@ -26817,7 +27159,9 @@ var CroppedHlsVideoPlayer = forwardRef(({
26817
27159
  canvas.height
26818
27160
  // Destination (full canvas)
26819
27161
  );
26820
- animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
27162
+ if (!video.paused && !video.ended) {
27163
+ animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
27164
+ }
26821
27165
  }, [crop]);
26822
27166
  const handleVideoReady = useCallback((player) => {
26823
27167
  console.log("[CroppedHlsVideoPlayer] Video player ready");
@@ -26825,11 +27169,15 @@ var CroppedHlsVideoPlayer = forwardRef(({
26825
27169
  if (videoEl) {
26826
27170
  videoElementRef.current = videoEl;
26827
27171
  setIsVideoReady(true);
27172
+ if (videoEl.readyState >= 2) {
27173
+ renderFrameToCanvas();
27174
+ }
26828
27175
  }
26829
27176
  onReadyProp?.(player);
26830
- }, [onReadyProp]);
27177
+ }, [onReadyProp, renderFrameToCanvas]);
26831
27178
  const handleVideoPlay = useCallback((player) => {
26832
27179
  console.log("[CroppedHlsVideoPlayer] Video playing, starting canvas rendering");
27180
+ setIsPlaying(true);
26833
27181
  if (crop && canvasRef.current) {
26834
27182
  setIsProcessing(true);
26835
27183
  renderFrameToCanvas();
@@ -26837,43 +27185,40 @@ var CroppedHlsVideoPlayer = forwardRef(({
26837
27185
  onPlayProp?.(player);
26838
27186
  }, [crop, renderFrameToCanvas, onPlayProp]);
26839
27187
  const handleVideoPause = useCallback((player) => {
26840
- console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering and CLEARING canvas");
27188
+ console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering (keeping last frame)");
27189
+ if (userSeekingRef.current && hiddenVideoRef.current) {
27190
+ hiddenVideoRef.current.play()?.catch(() => {
27191
+ });
27192
+ return;
27193
+ }
26841
27194
  stopCanvasRendering();
26842
27195
  setIsProcessing(false);
26843
- if (canvasRef.current) {
26844
- const ctx = canvasRef.current.getContext("2d");
26845
- if (ctx) {
26846
- ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26847
- ctx.fillStyle = "black";
26848
- ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26849
- }
26850
- }
27196
+ setIsPlaying(false);
27197
+ renderFrameToCanvas();
26851
27198
  onPauseProp?.(player);
26852
- }, [stopCanvasRendering, onPauseProp]);
27199
+ }, [stopCanvasRendering, onPauseProp, renderFrameToCanvas]);
26853
27200
  const handleVideoEnded = useCallback((player) => {
26854
- console.log("[CroppedHlsVideoPlayer] Video ended, CLEARING canvas");
27201
+ console.log("[CroppedHlsVideoPlayer] Video ended, stopping canvas rendering (keeping last frame)");
26855
27202
  stopCanvasRendering();
26856
27203
  setIsProcessing(false);
26857
- if (canvasRef.current) {
26858
- const ctx = canvasRef.current.getContext("2d");
26859
- if (ctx) {
26860
- ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26861
- ctx.fillStyle = "black";
26862
- ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26863
- }
26864
- }
27204
+ setIsPlaying(false);
27205
+ userSeekingRef.current = false;
26865
27206
  onEndedProp?.(player);
26866
27207
  }, [stopCanvasRendering, onEndedProp]);
26867
27208
  const handleSeeking = useCallback((player) => {
26868
27209
  console.log("[CroppedHlsVideoPlayer] Video seeking");
26869
- if (crop && !videoElementRef.current?.paused) {
27210
+ userSeekingRef.current = true;
27211
+ if (crop) {
26870
27212
  renderFrameToCanvas();
26871
27213
  }
26872
27214
  onSeekingProp?.(player);
26873
27215
  }, [crop, renderFrameToCanvas, onSeekingProp]);
26874
27216
  const handleSeeked = useCallback((player) => {
26875
27217
  console.log("[CroppedHlsVideoPlayer] Video seeked");
26876
- if (crop && !videoElementRef.current?.paused) {
27218
+ hiddenVideoRef.current?.play()?.catch(() => {
27219
+ });
27220
+ userSeekingRef.current = false;
27221
+ if (crop) {
26877
27222
  renderFrameToCanvas();
26878
27223
  }
26879
27224
  onSeekedProp?.(player);
@@ -26881,8 +27226,29 @@ var CroppedHlsVideoPlayer = forwardRef(({
26881
27226
  const handleLoadedMetadata = useCallback((player) => {
26882
27227
  console.log("[CroppedHlsVideoPlayer] Video metadata loaded");
26883
27228
  calculateCanvasDimensions();
27229
+ if (hiddenVideoRef.current?.video) {
27230
+ setDuration(hiddenVideoRef.current.video.duration || 0);
27231
+ }
27232
+ requestAnimationFrame(() => renderFrameToCanvas());
26884
27233
  onLoadedMetadataProp?.(player);
26885
- }, [calculateCanvasDimensions, onLoadedMetadataProp]);
27234
+ }, [calculateCanvasDimensions, onLoadedMetadataProp, renderFrameToCanvas]);
27235
+ const handleTimeUpdate = useCallback((player, time2) => {
27236
+ setCurrentTime(time2);
27237
+ if (hiddenVideoRef.current?.video && hiddenVideoRef.current.video.buffered.length > 0) {
27238
+ const video = hiddenVideoRef.current.video;
27239
+ for (let i = 0; i < video.buffered.length; i++) {
27240
+ if (video.buffered.start(i) <= time2 && video.buffered.end(i) >= time2) {
27241
+ setBuffered(video.buffered.end(i));
27242
+ break;
27243
+ }
27244
+ }
27245
+ }
27246
+ videoProps.onTimeUpdate?.(player, time2);
27247
+ }, [videoProps.onTimeUpdate]);
27248
+ const handleDurationChange = useCallback((player, dur) => {
27249
+ setDuration(dur);
27250
+ videoProps.onDurationChange?.(player, dur);
27251
+ }, [videoProps.onDurationChange]);
26886
27252
  useEffect(() => {
26887
27253
  calculateCanvasDimensions();
26888
27254
  const handleResize = () => {
@@ -26910,33 +27276,97 @@ var CroppedHlsVideoPlayer = forwardRef(({
26910
27276
  stopCanvasRendering();
26911
27277
  };
26912
27278
  }, [stopCanvasRendering]);
27279
+ const resetControlsTimeout = useCallback(() => {
27280
+ if (controlsPinned) {
27281
+ setShowControls(true);
27282
+ return;
27283
+ }
27284
+ setShowControls(true);
27285
+ if (controlsTimeoutRef.current) {
27286
+ clearTimeout(controlsTimeoutRef.current);
27287
+ }
27288
+ if (isPlaying) {
27289
+ controlsTimeoutRef.current = setTimeout(() => {
27290
+ setShowControls(false);
27291
+ }, 3e3);
27292
+ }
27293
+ }, [isPlaying, controlsPinned]);
27294
+ const handleMouseMove = useCallback(() => {
27295
+ resetControlsTimeout();
27296
+ }, [resetControlsTimeout]);
27297
+ useEffect(() => {
27298
+ resetControlsTimeout();
27299
+ return () => {
27300
+ if (controlsTimeoutRef.current) {
27301
+ clearTimeout(controlsTimeoutRef.current);
27302
+ }
27303
+ };
27304
+ }, [isPlaying, resetControlsTimeout]);
27305
+ const handleTogglePlay = useCallback(() => {
27306
+ if (hiddenVideoRef.current?.video) {
27307
+ if (hiddenVideoRef.current.video.paused) {
27308
+ hiddenVideoRef.current.play();
27309
+ } else {
27310
+ hiddenVideoRef.current.pause();
27311
+ }
27312
+ }
27313
+ }, []);
27314
+ const handleSeek = useCallback((time2) => {
27315
+ if (hiddenVideoRef.current) {
27316
+ hiddenVideoRef.current.currentTime(time2);
27317
+ hiddenVideoRef.current.play()?.catch(() => {
27318
+ });
27319
+ setTimeout(() => renderFrameToCanvas(), 50);
27320
+ }
27321
+ }, [renderFrameToCanvas]);
27322
+ const handleSeekStart = useCallback(() => {
27323
+ userSeekingRef.current = true;
27324
+ }, []);
27325
+ const handleSeekEnd = useCallback(() => {
27326
+ if (hiddenVideoRef.current) {
27327
+ hiddenVideoRef.current.play()?.catch(() => {
27328
+ });
27329
+ }
27330
+ }, []);
27331
+ const handlePlaybackRateChange = useCallback((rate) => {
27332
+ if (hiddenVideoRef.current) {
27333
+ hiddenVideoRef.current.playbackRate(rate);
27334
+ setPlaybackRate(rate);
27335
+ }
27336
+ }, []);
27337
+ const handleToggleFullscreen = useCallback(() => {
27338
+ if (videoContainerRef.current) {
27339
+ if (!document.fullscreenElement) {
27340
+ videoContainerRef.current.requestFullscreen();
27341
+ } else {
27342
+ document.exitFullscreen();
27343
+ }
27344
+ }
27345
+ }, []);
26913
27346
  if (!crop) {
26914
- return /* @__PURE__ */ jsx(HlsVideoPlayer, { ref, ...videoProps, onClick });
26915
- }
26916
- const handleClickWithIndicator = () => {
26917
- if (!onClick || !hiddenVideoRef.current?.video) return;
26918
- const video = hiddenVideoRef.current.video;
26919
- const willBePlaying = video.paused;
26920
- setIndicatorIsPlaying(willBePlaying);
26921
- setShowIndicator(false);
26922
- setTimeout(() => {
26923
- indicatorKeyRef.current += 1;
26924
- setShowIndicator(true);
26925
- }, 0);
26926
- onClick();
27347
+ return /* @__PURE__ */ jsx(HlsVideoPlayer, { ref, ...videoProps, onClick, controls });
27348
+ }
27349
+ const handleClick = () => {
27350
+ if (!onClick && !controls) {
27351
+ handleTogglePlay();
27352
+ }
27353
+ if (onClick) onClick();
26927
27354
  };
26928
27355
  return /* @__PURE__ */ jsxs(
26929
27356
  "div",
26930
27357
  {
26931
27358
  ref: videoContainerRef,
26932
- className: `relative w-full h-full flex items-center justify-center bg-black ${onClick ? "cursor-pointer" : ""} ${inheritedClassName}`,
26933
- onClick: handleClickWithIndicator,
27359
+ className: `relative w-full h-full flex items-center justify-center bg-black group ${inheritedClassName} ${onClick || controls ? "cursor-pointer" : ""}`,
27360
+ onClick: handleClick,
27361
+ onMouseMove: handleMouseMove,
27362
+ onMouseLeave: () => isPlaying && !controlsPinned && setShowControls(false),
26934
27363
  children: [
26935
27364
  /* @__PURE__ */ jsx("div", { className: "hidden", children: /* @__PURE__ */ jsx(
26936
27365
  HlsVideoPlayer,
26937
27366
  {
26938
27367
  ref: hiddenVideoRef,
26939
27368
  ...videoProps,
27369
+ controls: false,
26940
27370
  onReady: handleVideoReady,
26941
27371
  onPlay: handleVideoPlay,
26942
27372
  onPause: handleVideoPause,
@@ -26946,7 +27376,9 @@ var CroppedHlsVideoPlayer = forwardRef(({
26946
27376
  onLoadedMetadata: handleLoadedMetadata,
26947
27377
  onLoadedData: videoProps.onLoadedData,
26948
27378
  onPlaying: videoProps.onPlaying,
26949
- onLoadingChange: videoProps.onLoadingChange
27379
+ onLoadingChange: videoProps.onLoadingChange,
27380
+ onTimeUpdate: handleTimeUpdate,
27381
+ onDurationChange: handleDurationChange
26950
27382
  }
26951
27383
  ) }),
26952
27384
  /* @__PURE__ */ jsx(
@@ -26963,8 +27395,8 @@ var CroppedHlsVideoPlayer = forwardRef(({
26963
27395
  }
26964
27396
  }
26965
27397
  ),
26966
- !isVideoReady && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
26967
- debug && isVideoReady && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono", children: [
27398
+ !isVideoReady && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27399
+ debug && isVideoReady && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono pointer-events-none z-20", children: [
26968
27400
  /* @__PURE__ */ jsxs("div", { children: [
26969
27401
  "Crop: ",
26970
27402
  crop.x,
@@ -26988,13 +27420,32 @@ var CroppedHlsVideoPlayer = forwardRef(({
26988
27420
  isProcessing ? "Yes" : "No"
26989
27421
  ] })
26990
27422
  ] }),
26991
- onClick && /* @__PURE__ */ jsx(
26992
- PlayPauseIndicator,
27423
+ controls && isVideoReady && /* @__PURE__ */ jsx(
27424
+ VideoControls,
26993
27425
  {
26994
- show: showIndicator,
26995
- isPlaying: indicatorIsPlaying
26996
- },
26997
- indicatorKeyRef.current
27426
+ isPlaying,
27427
+ currentTime,
27428
+ duration,
27429
+ buffered,
27430
+ showControls: controlsPinned || showControls || !isPlaying,
27431
+ controlsPinned,
27432
+ playbackRate,
27433
+ onPlayPause: handleTogglePlay,
27434
+ onSeek: handleSeek,
27435
+ onSeekStart: handleSeekStart,
27436
+ onSeekEnd: handleSeekEnd,
27437
+ onPlaybackRateChange: handlePlaybackRateChange,
27438
+ onTogglePinControls: () => setControlsPinned((prev) => {
27439
+ const next = !prev;
27440
+ if (next) {
27441
+ setShowControls(true);
27442
+ } else {
27443
+ resetControlsTimeout();
27444
+ }
27445
+ return next;
27446
+ }),
27447
+ onToggleFullscreen: handleToggleFullscreen
27448
+ }
26998
27449
  )
26999
27450
  ]
27000
27451
  }
@@ -27533,6 +27984,67 @@ var SilentErrorBoundary = class extends React23__default.Component {
27533
27984
  ] }) });
27534
27985
  }
27535
27986
  };
27987
+ var PlayPauseIndicator = ({
27988
+ show,
27989
+ isPlaying,
27990
+ duration = 600
27991
+ }) => {
27992
+ const [isVisible, setIsVisible] = useState(false);
27993
+ const [isFading, setIsFading] = useState(false);
27994
+ useEffect(() => {
27995
+ if (show) {
27996
+ setIsVisible(true);
27997
+ setIsFading(false);
27998
+ const fadeTimer = setTimeout(() => {
27999
+ setIsFading(true);
28000
+ }, 100);
28001
+ const hideTimer = setTimeout(() => {
28002
+ setIsVisible(false);
28003
+ setIsFading(false);
28004
+ }, duration);
28005
+ return () => {
28006
+ clearTimeout(fadeTimer);
28007
+ clearTimeout(hideTimer);
28008
+ };
28009
+ }
28010
+ }, [show, duration]);
28011
+ if (!isVisible) return null;
28012
+ return /* @__PURE__ */ jsx(
28013
+ "div",
28014
+ {
28015
+ className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
28016
+ style: {
28017
+ opacity: isFading ? 0 : 1,
28018
+ transition: `opacity ${duration - 100}ms ease-out`
28019
+ },
28020
+ children: /* @__PURE__ */ jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
28021
+ // Play icon (triangle)
28022
+ /* @__PURE__ */ jsx(
28023
+ "svg",
28024
+ {
28025
+ xmlns: "http://www.w3.org/2000/svg",
28026
+ viewBox: "0 0 24 24",
28027
+ fill: "white",
28028
+ className: "w-16 h-16",
28029
+ children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" })
28030
+ }
28031
+ )
28032
+ ) : (
28033
+ // Pause icon (two bars)
28034
+ /* @__PURE__ */ jsx(
28035
+ "svg",
28036
+ {
28037
+ xmlns: "http://www.w3.org/2000/svg",
28038
+ viewBox: "0 0 24 24",
28039
+ fill: "white",
28040
+ className: "w-16 h-16",
28041
+ children: /* @__PURE__ */ jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
28042
+ }
28043
+ )
28044
+ ) })
28045
+ }
28046
+ );
28047
+ };
27536
28048
  var BackButton = ({
27537
28049
  onClick,
27538
28050
  text = "Back",
@@ -29185,6 +29697,31 @@ var BottlenecksContent = ({
29185
29697
  const [categoryMetadata, setCategoryMetadata] = useState([]);
29186
29698
  const [currentMetadataIndex, setCurrentMetadataIndex] = useState(0);
29187
29699
  const [metadataCache, setMetadataCache] = useState({});
29700
+ const invalidateMetadataCache = useCallback((categories) => {
29701
+ setMetadataCache((prevCache) => {
29702
+ if (!prevCache || Object.keys(prevCache).length === 0) {
29703
+ return prevCache;
29704
+ }
29705
+ const targetCategories = categories ? (Array.isArray(categories) ? categories : [categories]).filter(Boolean) : null;
29706
+ let updatedCache = null;
29707
+ const shouldInvalidate = (key) => {
29708
+ if (!targetCategories || targetCategories.length === 0) {
29709
+ return true;
29710
+ }
29711
+ const [categoryId] = key.split("-");
29712
+ return targetCategories.includes(categoryId);
29713
+ };
29714
+ Object.keys(prevCache).forEach((cacheKey) => {
29715
+ if (shouldInvalidate(cacheKey)) {
29716
+ if (!updatedCache) {
29717
+ updatedCache = { ...prevCache };
29718
+ }
29719
+ delete updatedCache[cacheKey];
29720
+ }
29721
+ });
29722
+ return updatedCache || prevCache;
29723
+ });
29724
+ }, []);
29188
29725
  const [triageClips, setTriageClips] = useState([]);
29189
29726
  const [isLoadingTriageClips, setIsLoadingTriageClips] = useState(false);
29190
29727
  const [isFullscreen, setIsFullscreen] = useState(false);
@@ -29204,6 +29741,12 @@ var BottlenecksContent = ({
29204
29741
  onNewClips: (notification) => {
29205
29742
  console.log(`[BottlenecksContent] New clips detected:`, notification);
29206
29743
  if (notification.clips.length > 0) {
29744
+ const categoryIds = notification.clips.map((clip) => clip.clip_type).filter(Boolean).map((value) => String(value));
29745
+ if (categoryIds.length > 0) {
29746
+ invalidateMetadataCache(categoryIds);
29747
+ } else {
29748
+ invalidateMetadataCache();
29749
+ }
29207
29750
  fetchClipCounts();
29208
29751
  }
29209
29752
  }
@@ -29250,24 +29793,37 @@ var BottlenecksContent = ({
29250
29793
  shift: shift || "0"
29251
29794
  });
29252
29795
  useEffect(() => {
29253
- if (clipTypes.length > 0 && !initialFilter) {
29254
- const priorityOrder = ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
29255
- let selectedType = null;
29256
- for (const priorityType of priorityOrder) {
29257
- const type = clipTypes.find((t) => t.type === priorityType && (dynamicCounts[t.type] || 0) > 0);
29258
- if (type) {
29259
- selectedType = type;
29260
- break;
29796
+ if (clipTypes.length > 0) {
29797
+ const currentFilterCount = initialFilter ? dynamicCounts[initialFilter] || 0 : 0;
29798
+ const hasAnyCounts = Object.values(dynamicCounts).some((c) => c > 0);
29799
+ const userHasNotNavigated = !initialFilter || activeFilterRef.current === initialFilter;
29800
+ const shouldRunSelection = !initialFilter || userHasNotNavigated && currentFilterCount === 0 && hasAnyCounts;
29801
+ if (shouldRunSelection) {
29802
+ let selectedType = null;
29803
+ if (clipTypes.length === 1) {
29804
+ selectedType = clipTypes[0];
29805
+ } else {
29806
+ const priorityOrder = ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
29807
+ for (const priorityType of priorityOrder) {
29808
+ const type = clipTypes.find((t) => t.type === priorityType && (dynamicCounts[t.type] || 0) > 0);
29809
+ if (type) {
29810
+ selectedType = type;
29811
+ break;
29812
+ }
29813
+ }
29814
+ if (!selectedType) {
29815
+ selectedType = clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0);
29816
+ }
29817
+ if (!selectedType) {
29818
+ selectedType = clipTypes[0];
29819
+ }
29820
+ }
29821
+ if (selectedType && selectedType.type !== initialFilter) {
29822
+ console.log(`[BottlenecksContent] Auto-selecting filter: ${selectedType.type} (count: ${dynamicCounts[selectedType.type] || 0})`);
29823
+ setInitialFilter(selectedType.type);
29824
+ setActiveFilter(selectedType.type);
29825
+ activeFilterRef.current = selectedType.type;
29261
29826
  }
29262
- }
29263
- if (!selectedType) {
29264
- selectedType = clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0);
29265
- }
29266
- const firstType = selectedType || clipTypes[0];
29267
- if (firstType) {
29268
- setInitialFilter(firstType.type);
29269
- setActiveFilter(firstType.type);
29270
- activeFilterRef.current = firstType.type;
29271
29827
  }
29272
29828
  }
29273
29829
  }, [clipTypes, dynamicCounts, initialFilter]);
@@ -29319,7 +29875,7 @@ var BottlenecksContent = ({
29319
29875
  } finally {
29320
29876
  fetchInProgressRef.current.delete(operationKey);
29321
29877
  }
29322
- }, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts]);
29878
+ }, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts, timezone, totalOutput]);
29323
29879
  const loadingCategoryRef = useRef(null);
29324
29880
  const loadFirstVideoForCategory = useCallback(async (category) => {
29325
29881
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
@@ -29409,15 +29965,16 @@ var BottlenecksContent = ({
29409
29965
  loadingCategoryRef.current = null;
29410
29966
  fetchInProgressRef.current.delete(operationKey);
29411
29967
  }
29412
- }, [workspaceId, date, s3ClipsService, mergedCounts, effectiveShift]);
29968
+ }, [workspaceId, date, s3ClipsService, mergedCounts, effectiveShift, timezone]);
29413
29969
  const handleRefreshClips = useCallback(async () => {
29414
29970
  console.log("[BottlenecksContent] Refreshing clips after new additions");
29415
29971
  acknowledgeNewClips();
29972
+ invalidateMetadataCache();
29416
29973
  await fetchClipCounts();
29417
29974
  if (activeFilter && mergedCounts[activeFilter] > 0) {
29418
29975
  await loadFirstVideoForCategory(activeFilter);
29419
29976
  }
29420
- }, [acknowledgeNewClips, fetchClipCounts, activeFilter, mergedCounts, loadFirstVideoForCategory]);
29977
+ }, [acknowledgeNewClips, fetchClipCounts, activeFilter, mergedCounts, loadFirstVideoForCategory, invalidateMetadataCache]);
29421
29978
  useEffect(() => {
29422
29979
  if (s3ClipsService) {
29423
29980
  fetchClipCounts();
@@ -29587,38 +30144,38 @@ var BottlenecksContent = ({
29587
30144
  loadingTimeoutRef.current = null;
29588
30145
  }
29589
30146
  }, []);
29590
- const loadCategoryMetadata = useCallback(async (categoryId, autoLoadFirstVideo = false) => {
30147
+ const loadCategoryMetadata = useCallback(async (categoryId, autoLoadFirstVideo = false, forceRefresh = false) => {
29591
30148
  if (!workspaceId) {
29592
30149
  return;
29593
30150
  }
29594
- const cacheKey = `${categoryId}-${date || getOperationalDate(timezone)}-${effectiveShift}`;
29595
- if (metadataCache[cacheKey]) {
29596
- console.log(`[BottlenecksContent] Using cached metadata for ${categoryId}`);
29597
- setCategoryMetadata(metadataCache[cacheKey]);
29598
- categoryMetadataRef.current = metadataCache[cacheKey];
29599
- if (autoLoadFirstVideo && metadataCache[cacheKey].length > 0 && s3ClipsService) {
29600
- const firstClipMeta = metadataCache[cacheKey][0];
29601
- try {
29602
- const video = await s3ClipsService.getClipById(firstClipMeta.clipId);
29603
- if (video && isMountedRef.current) {
29604
- setCurrentClipId(firstClipMeta.clipId);
29605
- setAllVideos([video]);
29606
- setCurrentIndex(0);
29607
- setCurrentMetadataIndex(0);
29608
- currentMetadataIndexRef.current = 0;
29609
- setIsCategoryLoading(false);
29610
- console.log(`[BottlenecksContent] Auto-loaded first video from cache: ${video.id} (1/${metadataCache[cacheKey].length})`);
30151
+ const resolvedDate = date || getOperationalDate(timezone);
30152
+ const cacheKey = `${categoryId}-${resolvedDate}-${effectiveShift}`;
30153
+ const cachedMetadata = !forceRefresh ? metadataCache[cacheKey] : void 0;
30154
+ try {
30155
+ if (cachedMetadata) {
30156
+ console.log(`[BottlenecksContent] Using cached metadata for ${categoryId}`);
30157
+ setCategoryMetadata(cachedMetadata);
30158
+ categoryMetadataRef.current = cachedMetadata;
30159
+ if (autoLoadFirstVideo && cachedMetadata.length > 0 && s3ClipsService) {
30160
+ const firstClipMeta = cachedMetadata[0];
30161
+ try {
30162
+ const video = await s3ClipsService.getClipById(firstClipMeta.clipId);
30163
+ if (video && isMountedRef.current) {
30164
+ setCurrentClipId(firstClipMeta.clipId);
30165
+ setAllVideos([video]);
30166
+ setCurrentIndex(0);
30167
+ setCurrentMetadataIndex(0);
30168
+ currentMetadataIndexRef.current = 0;
30169
+ console.log(`[BottlenecksContent] Auto-loaded first video from cache: ${video.id} (1/${cachedMetadata.length})`);
30170
+ }
30171
+ } catch (error2) {
30172
+ console.error(`[BottlenecksContent] Error loading first video from cache:`, error2);
30173
+ clearLoadingState();
29611
30174
  }
29612
- } catch (error2) {
29613
- console.error(`[BottlenecksContent] Error loading first video from cache:`, error2);
29614
- setIsCategoryLoading(false);
29615
- clearLoadingState();
29616
30175
  }
30176
+ return;
29617
30177
  }
29618
- return;
29619
- }
29620
- try {
29621
- console.log(`[BottlenecksContent] Loading metadata for category: ${categoryId}`);
30178
+ console.log(`[BottlenecksContent] Loading metadata for category: ${categoryId}${forceRefresh ? " (force refresh)" : ""}`);
29622
30179
  const { createClient: createClient5 } = await import('@supabase/supabase-js');
29623
30180
  const supabase = createClient5(
29624
30181
  process.env.NEXT_PUBLIC_SUPABASE_URL || "",
@@ -29643,8 +30200,8 @@ var BottlenecksContent = ({
29643
30200
  action: "percentile-clips",
29644
30201
  percentileAction: percentileType,
29645
30202
  workspaceId,
29646
- startDate: `${date || getOperationalDate(timezone)}T00:00:00Z`,
29647
- endDate: `${date || getOperationalDate(timezone)}T23:59:59Z`,
30203
+ startDate: `${resolvedDate}T00:00:00Z`,
30204
+ endDate: `${resolvedDate}T23:59:59Z`,
29648
30205
  percentile: 10,
29649
30206
  shiftId: effectiveShift,
29650
30207
  limit: 100
@@ -29660,7 +30217,7 @@ var BottlenecksContent = ({
29660
30217
  body: JSON.stringify({
29661
30218
  action: "clip-metadata",
29662
30219
  workspaceId,
29663
- date: date || getOperationalDate(timezone),
30220
+ date: resolvedDate,
29664
30221
  shift: effectiveShift,
29665
30222
  category: categoryId,
29666
30223
  page: 1,
@@ -29705,19 +30262,22 @@ var BottlenecksContent = ({
29705
30262
  setCurrentIndex(0);
29706
30263
  setCurrentMetadataIndex(0);
29707
30264
  currentMetadataIndexRef.current = 0;
29708
- setIsCategoryLoading(false);
29709
30265
  console.log(`[BottlenecksContent] Auto-loaded first video: ${video.id} (1/${metadataClips.length})`);
29710
30266
  }
29711
30267
  } catch (error2) {
29712
30268
  console.error(`[BottlenecksContent] Error loading first video:`, error2);
29713
- setIsCategoryLoading(false);
29714
30269
  }
29715
30270
  }
30271
+ } else {
30272
+ setCategoryMetadata([]);
30273
+ categoryMetadataRef.current = [];
29716
30274
  }
29717
30275
  } catch (error2) {
29718
30276
  console.error(`[BottlenecksContent] Error loading category metadata:`, error2);
30277
+ } finally {
30278
+ setIsCategoryLoading(false);
29719
30279
  }
29720
- }, [workspaceId, date, effectiveShift, isPercentileCategory, metadataCache, s3ClipsService]);
30280
+ }, [workspaceId, date, effectiveShift, isPercentileCategory, metadataCache, s3ClipsService, timezone, clearLoadingState]);
29721
30281
  const loadAndPlayClipById = useCallback(async (clipId, categoryId, position) => {
29722
30282
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
29723
30283
  console.log(`[BottlenecksContent] Loading clip by ID: ${clipId}, category=${categoryId}, position=${position}`);
@@ -29741,21 +30301,31 @@ var BottlenecksContent = ({
29741
30301
  }
29742
30302
  try {
29743
30303
  await loadCategoryMetadata(categoryId, false);
29744
- const metadataArray = categoryMetadataRef.current;
29745
- if (metadataArray.length > 0) {
29746
- const clickedClipIndex = metadataArray.findIndex((clip) => clip.clipId === clipId);
29747
- if (clickedClipIndex !== -1) {
29748
- setCurrentMetadataIndex(clickedClipIndex);
29749
- currentMetadataIndexRef.current = clickedClipIndex;
29750
- const video = await s3ClipsService.getClipById(clipId);
29751
- if (video) {
29752
- setPendingVideo(video);
29753
- setCurrentClipId(clipId);
29754
- setAllVideos([video]);
29755
- setCurrentIndex(0);
29756
- console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
29757
- }
29758
- }
30304
+ let metadataArray = categoryMetadataRef.current;
30305
+ const clipExistsInMetadata = metadataArray.some((clip) => clip.clipId === clipId);
30306
+ if (metadataArray.length === 0 || !clipExistsInMetadata) {
30307
+ console.warn(`[BottlenecksContent] Clip ${clipId} not found in metadata for ${categoryId} (cache hit: ${metadataArray.length > 0}) - forcing refresh`);
30308
+ await loadCategoryMetadata(categoryId, false, true);
30309
+ metadataArray = categoryMetadataRef.current;
30310
+ }
30311
+ if (metadataArray.length === 0) {
30312
+ throw new Error(`No metadata available for category ${categoryId}`);
30313
+ }
30314
+ const clickedClipIndex = metadataArray.findIndex((clip) => clip.clipId === clipId);
30315
+ if (clickedClipIndex === -1) {
30316
+ throw new Error(`Clip ${clipId} not found after metadata refresh`);
30317
+ }
30318
+ setCurrentMetadataIndex(clickedClipIndex);
30319
+ currentMetadataIndexRef.current = clickedClipIndex;
30320
+ const video = await s3ClipsService.getClipById(clipId);
30321
+ if (video) {
30322
+ setPendingVideo(video);
30323
+ setCurrentClipId(clipId);
30324
+ setAllVideos([video]);
30325
+ setCurrentIndex(0);
30326
+ console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
30327
+ } else {
30328
+ throw new Error(`Failed to load video data for clip ${clipId}`);
29759
30329
  }
29760
30330
  } catch (error2) {
29761
30331
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
@@ -29769,7 +30339,7 @@ var BottlenecksContent = ({
29769
30339
  clearLoadingState();
29770
30340
  }
29771
30341
  }
29772
- }, [workspaceId, s3ClipsService, date, effectiveShift, updateActiveFilter, clearLoadingState]);
30342
+ }, [workspaceId, s3ClipsService, updateActiveFilter, clearLoadingState, loadCategoryMetadata]);
29773
30343
  useCallback(async (categoryId, clipIndex) => {
29774
30344
  console.warn("[BottlenecksContent] loadAndPlayClip is deprecated, use loadAndPlayClipById instead");
29775
30345
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
@@ -29796,7 +30366,7 @@ var BottlenecksContent = ({
29796
30366
  });
29797
30367
  setIsNavigating(false);
29798
30368
  }
29799
- }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById]);
30369
+ }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById, timezone]);
29800
30370
  const handleNext = useCallback(async () => {
29801
30371
  if (!isMountedRef.current) return;
29802
30372
  const currentFilter = activeFilterRef.current;
@@ -29813,8 +30383,18 @@ var BottlenecksContent = ({
29813
30383
  }
29814
30384
  try {
29815
30385
  const currentMetaIndex = currentMetadataIndexRef.current;
29816
- const metadataArray = categoryMetadataRef.current;
30386
+ let metadataArray = categoryMetadataRef.current;
30387
+ if (metadataArray.length === 0) {
30388
+ console.log(`[handleNext] Metadata empty for ${currentFilter}, loading before navigation`);
30389
+ await loadCategoryMetadata(currentFilter, false);
30390
+ metadataArray = categoryMetadataRef.current;
30391
+ }
29817
30392
  console.log(`[handleNext] Unified navigation: ${currentFilter}, metadata index: ${currentMetaIndex}/${metadataArray.length}`);
30393
+ if (metadataArray.length === 0) {
30394
+ console.warn("[handleNext] No metadata available after refresh - stopping navigation");
30395
+ clearLoadingState();
30396
+ return;
30397
+ }
29818
30398
  if (currentMetaIndex < metadataArray.length - 1) {
29819
30399
  const nextMetadataIndex = currentMetaIndex + 1;
29820
30400
  const nextClipMeta = metadataArray[nextMetadataIndex];
@@ -29849,7 +30429,7 @@ var BottlenecksContent = ({
29849
30429
  });
29850
30430
  clearLoadingState();
29851
30431
  }
29852
- }, [clearLoadingState, s3ClipsService]);
30432
+ }, [clearLoadingState, s3ClipsService, loadCategoryMetadata]);
29853
30433
  const handlePrevious = useCallback(async () => {
29854
30434
  if (!isMountedRef.current) return;
29855
30435
  const currentFilter = activeFilterRef.current;
@@ -29866,8 +30446,18 @@ var BottlenecksContent = ({
29866
30446
  }
29867
30447
  try {
29868
30448
  const currentMetaIndex = currentMetadataIndexRef.current;
29869
- const metadataArray = categoryMetadataRef.current;
30449
+ let metadataArray = categoryMetadataRef.current;
30450
+ if (metadataArray.length === 0) {
30451
+ console.log(`[handlePrevious] Metadata empty for ${currentFilter}, loading before navigation`);
30452
+ await loadCategoryMetadata(currentFilter, false);
30453
+ metadataArray = categoryMetadataRef.current;
30454
+ }
29870
30455
  console.log(`[handlePrevious] Unified navigation: ${currentFilter}, metadata index: ${currentMetaIndex}/${metadataArray.length}`);
30456
+ if (metadataArray.length === 0) {
30457
+ console.warn("[handlePrevious] No metadata available after refresh - stopping navigation");
30458
+ clearLoadingState();
30459
+ return;
30460
+ }
29871
30461
  if (currentMetaIndex > 0) {
29872
30462
  const prevMetadataIndex = currentMetaIndex - 1;
29873
30463
  const prevClipMeta = metadataArray[prevMetadataIndex];
@@ -29898,7 +30488,7 @@ var BottlenecksContent = ({
29898
30488
  });
29899
30489
  clearLoadingState();
29900
30490
  }
29901
- }, [clearLoadingState, s3ClipsService]);
30491
+ }, [clearLoadingState, s3ClipsService, loadCategoryMetadata]);
29902
30492
  const currentVideo = useMemo(() => {
29903
30493
  if (!filteredVideos || filteredVideos.length === 0 || currentIndex >= filteredVideos.length) {
29904
30494
  return null;
@@ -30874,7 +31464,7 @@ function DiagnosisVideoModal({
30874
31464
  }
30875
31465
  loadClip();
30876
31466
  }, [clipId, supabase, transformPlaylistUrls]);
30877
- const formatTime3 = (seconds) => {
31467
+ const formatTime4 = (seconds) => {
30878
31468
  const mins = Math.floor(seconds / 60);
30879
31469
  const secs = Math.floor(seconds % 60);
30880
31470
  return `${mins}:${secs.toString().padStart(2, "0")}`;
@@ -31066,9 +31656,9 @@ function DiagnosisVideoModal({
31066
31656
  }
31067
31657
  ),
31068
31658
  /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
31069
- formatTime3(currentTime),
31659
+ formatTime4(currentTime),
31070
31660
  " / ",
31071
- formatTime3(duration)
31661
+ formatTime4(duration)
31072
31662
  ] }),
31073
31663
  /* @__PURE__ */ jsx(
31074
31664
  "input",
@@ -33039,7 +33629,7 @@ var LinePdfGenerator = ({
33039
33629
  }
33040
33630
  hourEndTime.setSeconds(0);
33041
33631
  hourEndTime.setMilliseconds(0);
33042
- const formatTime3 = (date2) => {
33632
+ const formatTime4 = (date2) => {
33043
33633
  return date2.toLocaleTimeString("en-IN", {
33044
33634
  hour: "2-digit",
33045
33635
  minute: "2-digit",
@@ -33047,7 +33637,7 @@ var LinePdfGenerator = ({
33047
33637
  timeZone: "Asia/Kolkata"
33048
33638
  });
33049
33639
  };
33050
- return `${formatTime3(hourStartTime)} - ${formatTime3(hourEndTime)}`;
33640
+ return `${formatTime4(hourStartTime)} - ${formatTime4(hourEndTime)}`;
33051
33641
  });
33052
33642
  };
33053
33643
  const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
@@ -39633,7 +40223,7 @@ var AIAgentView = () => {
39633
40223
  }
39634
40224
  return formattedLines.join("");
39635
40225
  };
39636
- const formatTime3 = (timestamp) => {
40226
+ const formatTime4 = (timestamp) => {
39637
40227
  const date = new Date(timestamp);
39638
40228
  return date.toLocaleTimeString([], {
39639
40229
  hour: "2-digit",
@@ -40897,7 +41487,7 @@ var AIAgentView = () => {
40897
41487
  }
40898
41488
  ),
40899
41489
  /* @__PURE__ */ jsxs("div", { className: `mt-1.5 sm:mt-2 flex items-center gap-2 text-xs text-gray-400 ${message.role === "user" ? "justify-end" : "justify-start"}`, children: [
40900
- /* @__PURE__ */ jsx("span", { children: formatTime3(message.created_at) }),
41490
+ /* @__PURE__ */ jsx("span", { children: formatTime4(message.created_at) }),
40901
41491
  message.role === "assistant" && message.id !== -1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
40902
41492
  /* @__PURE__ */ jsx("div", { className: "w-1 h-1 bg-gray-300 rounded-full" }),
40903
41493
  /* @__PURE__ */ jsx("span", { children: "Axel" })