@optifye/dashboard-core 6.9.8 → 6.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ var dateFns = require('date-fns');
8
8
  var mixpanel = require('mixpanel-browser');
9
9
  var events = require('events');
10
10
  var supabaseJs = require('@supabase/supabase-js');
11
- var Hls2 = require('hls.js');
11
+ var Hls3 = require('hls.js');
12
12
  var useSWR = require('swr');
13
13
  var motionUtils = require('motion-utils');
14
14
  var motionDom = require('motion-dom');
@@ -17,8 +17,6 @@ var sonner = require('sonner');
17
17
  var recharts = require('recharts');
18
18
  var reactSlot = require('@radix-ui/react-slot');
19
19
  var SelectPrimitive = require('@radix-ui/react-select');
20
- var videojs = require('video.js');
21
- require('video.js/dist/video-js.css');
22
20
  var reactDayPicker = require('react-day-picker');
23
21
  var outline = require('@heroicons/react/24/outline');
24
22
  var solid = require('@heroicons/react/24/solid');
@@ -51,10 +49,9 @@ function _interopNamespace(e) {
51
49
 
52
50
  var React23__namespace = /*#__PURE__*/_interopNamespace(React23);
53
51
  var mixpanel__default = /*#__PURE__*/_interopDefault(mixpanel);
54
- var Hls2__default = /*#__PURE__*/_interopDefault(Hls2);
52
+ var Hls3__default = /*#__PURE__*/_interopDefault(Hls3);
55
53
  var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
56
54
  var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive);
57
- var videojs__default = /*#__PURE__*/_interopDefault(videojs);
58
55
  var html2canvas__default = /*#__PURE__*/_interopDefault(html2canvas);
59
56
  var jsPDF__default = /*#__PURE__*/_interopDefault(jsPDF);
60
57
 
@@ -9019,12 +9016,12 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
9019
9016
  const video = videoRef.current;
9020
9017
  if (!video) return;
9021
9018
  isNativeHlsRef.current = video.canPlayType("application/vnd.apple.mpegurl") === "probably";
9022
- if (Hls2__default.default.isSupported() && !isNativeHlsRef.current) {
9023
- const hls = new Hls2__default.default(HLS_CONFIG);
9019
+ if (Hls3__default.default.isSupported() && !isNativeHlsRef.current) {
9020
+ const hls = new Hls3__default.default(HLS_CONFIG);
9024
9021
  hlsRef.current = hls;
9025
9022
  hls.attachMedia(video);
9026
9023
  hls.loadSource(src);
9027
- hls.on(Hls2__default.default.Events.ERROR, (_, data) => {
9024
+ hls.on(Hls3__default.default.Events.ERROR, (_, data) => {
9028
9025
  if (!data.fatal) return;
9029
9026
  console.error("[HLS] Fatal error:", data.type, data.details);
9030
9027
  if (data.response?.code === 404) {
@@ -9032,8 +9029,8 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
9032
9029
  return;
9033
9030
  }
9034
9031
  switch (data.type) {
9035
- case Hls2__default.default.ErrorTypes.NETWORK_ERROR:
9036
- case Hls2__default.default.ErrorTypes.MEDIA_ERROR:
9032
+ case Hls3__default.default.ErrorTypes.NETWORK_ERROR:
9033
+ case Hls3__default.default.ErrorTypes.MEDIA_ERROR:
9037
9034
  softRestart(`${data.type}: ${data.details}`);
9038
9035
  break;
9039
9036
  default:
@@ -9041,7 +9038,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
9041
9038
  break;
9042
9039
  }
9043
9040
  });
9044
- hls.on(Hls2__default.default.Events.MANIFEST_PARSED, () => {
9041
+ hls.on(Hls3__default.default.Events.MANIFEST_PARSED, () => {
9045
9042
  if (failedUrls.has(src)) {
9046
9043
  console.log(`[HLS] Stream loaded successfully, resetting failure count for: ${src}`);
9047
9044
  failedUrls.delete(src);
@@ -14084,9 +14081,9 @@ var S3VideoPreloader = class {
14084
14081
  this.processQueue();
14085
14082
  };
14086
14083
  if (url.endsWith(".m3u8")) {
14087
- import('hls.js').then(({ default: Hls3 }) => {
14088
- if (Hls3.isSupported()) {
14089
- const hls = new Hls3({
14084
+ import('hls.js').then(({ default: Hls4 }) => {
14085
+ if (Hls4.isSupported()) {
14086
+ const hls = new Hls4({
14090
14087
  maxBufferLength: 10,
14091
14088
  startFragPrefetch: true,
14092
14089
  lowLatencyMode: false,
@@ -14098,7 +14095,7 @@ var S3VideoPreloader = class {
14098
14095
  hls.destroy();
14099
14096
  cleanup();
14100
14097
  }, 4e3);
14101
- hls.on(Hls3.Events.BUFFER_APPENDED, () => {
14098
+ hls.on(Hls4.Events.BUFFER_APPENDED, () => {
14102
14099
  window.clearTimeout(timeout);
14103
14100
  hls.destroy();
14104
14101
  cleanup();
@@ -23102,7 +23099,7 @@ var OutputProgressChartComponent = ({
23102
23099
  ];
23103
23100
  const COLORS = ["#00AB45", "#f3f4f6"];
23104
23101
  const percentage = (currentOutput / targetOutput * 100).toFixed(1);
23105
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full max-w-[200px] sm:max-w-[240px] lg:max-w-[280px] aspect-square", style: { minHeight: "120px", minWidth: "120px" }, children: [
23102
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-square max-h-full", style: { maxWidth: "min(100%, 280px)", containerType: "inline-size" }, children: [
23106
23103
  /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.PieChart, { children: /* @__PURE__ */ jsxRuntime.jsx(
23107
23104
  recharts.Pie,
23108
23105
  {
@@ -23126,16 +23123,33 @@ var OutputProgressChartComponent = ({
23126
23123
  ))
23127
23124
  }
23128
23125
  ) }) }),
23129
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
23130
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl sm:text-3xl lg:text-4xl font-bold text-gray-800", children: [
23131
- percentage,
23132
- "%"
23133
- ] }),
23134
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs sm:text-sm lg:text-base text-gray-500 mt-1 sm:mt-2", children: [
23135
- currentOutput,
23136
- " / ",
23137
- Math.round(targetOutput)
23138
- ] })
23126
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
23127
+ /* @__PURE__ */ jsxRuntime.jsxs(
23128
+ "div",
23129
+ {
23130
+ className: "font-bold text-gray-800",
23131
+ style: { fontSize: "clamp(1.25rem, 8cqw, 2.5rem)" },
23132
+ children: [
23133
+ percentage,
23134
+ "%"
23135
+ ]
23136
+ }
23137
+ ),
23138
+ /* @__PURE__ */ jsxRuntime.jsxs(
23139
+ "div",
23140
+ {
23141
+ className: "text-gray-500",
23142
+ style: {
23143
+ fontSize: "clamp(0.7rem, 3.5cqw, 1rem)",
23144
+ marginTop: "clamp(0.125rem, 1cqw, 0.5rem)"
23145
+ },
23146
+ children: [
23147
+ currentOutput,
23148
+ " / ",
23149
+ Math.round(targetOutput)
23150
+ ]
23151
+ }
23152
+ )
23139
23153
  ] }) })
23140
23154
  ] }) });
23141
23155
  };
@@ -23153,7 +23167,7 @@ var LargeOutputProgressChart = ({
23153
23167
  ];
23154
23168
  const COLORS = ["#00AB45", "#f3f4f6"];
23155
23169
  const percentage = (currentOutput / targetOutput * 100).toFixed(1);
23156
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full", style: { minHeight: "100px", minWidth: "100px" }, children: [
23170
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full h-full", style: { minHeight: "100px", minWidth: "100px", containerType: "inline-size" }, children: [
23157
23171
  /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.PieChart, { children: /* @__PURE__ */ jsxRuntime.jsx(
23158
23172
  recharts.Pie,
23159
23173
  {
@@ -23177,16 +23191,40 @@ var LargeOutputProgressChart = ({
23177
23191
  ))
23178
23192
  }
23179
23193
  ) }) }),
23180
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
23181
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-4xl sm:text-5xl font-bold text-gray-900", children: currentOutput }),
23182
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-gray-500", children: [
23183
- "of ",
23184
- targetOutput
23185
- ] }),
23186
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-base font-medium text-gray-600 mt-1", children: [
23187
- percentage,
23188
- "%"
23189
- ] })
23194
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
23195
+ /* @__PURE__ */ jsxRuntime.jsx(
23196
+ "div",
23197
+ {
23198
+ className: "font-bold text-gray-900",
23199
+ style: { fontSize: "clamp(1.5rem, 10cqw, 3rem)" },
23200
+ children: currentOutput
23201
+ }
23202
+ ),
23203
+ /* @__PURE__ */ jsxRuntime.jsxs(
23204
+ "div",
23205
+ {
23206
+ className: "text-gray-500",
23207
+ style: { fontSize: "clamp(0.75rem, 3.5cqw, 1rem)" },
23208
+ children: [
23209
+ "of ",
23210
+ targetOutput
23211
+ ]
23212
+ }
23213
+ ),
23214
+ /* @__PURE__ */ jsxRuntime.jsxs(
23215
+ "div",
23216
+ {
23217
+ className: "font-medium text-gray-600",
23218
+ style: {
23219
+ fontSize: "clamp(0.875rem, 4.5cqw, 1.25rem)",
23220
+ marginTop: "clamp(0.125rem, 1cqw, 0.5rem)"
23221
+ },
23222
+ children: [
23223
+ percentage,
23224
+ "%"
23225
+ ]
23226
+ }
23227
+ )
23190
23228
  ] }) })
23191
23229
  ] }) });
23192
23230
  };
@@ -25280,7 +25318,7 @@ var GaugeChart = ({
25280
25318
  };
25281
25319
  const gaugeColor = getColor();
25282
25320
  const targetAngle = target !== void 0 ? 180 - (target - min) / (max - min) * 180 : null;
25283
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `relative w-full h-full flex flex-col items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full max-w-[280px] aspect-square", style: { minHeight: "100px", minWidth: "100px" }, children: [
25321
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `relative w-full h-full flex flex-col items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full max-w-[280px] aspect-square", style: { minHeight: "100px", minWidth: "100px", containerType: "inline-size" }, children: [
25284
25322
  /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.PieChart, { children: /* @__PURE__ */ jsxRuntime.jsxs(
25285
25323
  recharts.Pie,
25286
25324
  {
@@ -25313,17 +25351,44 @@ var GaugeChart = ({
25313
25351
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute -top-1 -left-1.5 w-3 h-3 bg-gray-800 rounded-full" })
25314
25352
  }
25315
25353
  ),
25316
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
25317
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-3xl font-bold text-gray-800", children: [
25318
- value.toFixed(unit === "%" ? 1 : 0),
25319
- unit
25320
- ] }),
25321
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-600 mt-1 font-medium", children: label }),
25322
- target !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mt-1", children: [
25323
- "Target: ",
25324
- target,
25325
- unit
25326
- ] })
25354
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", style: { width: "100%", padding: "0 10%" }, children: [
25355
+ /* @__PURE__ */ jsxRuntime.jsxs(
25356
+ "div",
25357
+ {
25358
+ className: "font-bold text-gray-800",
25359
+ style: { fontSize: "clamp(1.25rem, 8cqw, 2rem)" },
25360
+ children: [
25361
+ value.toFixed(unit === "%" ? 1 : 0),
25362
+ unit
25363
+ ]
25364
+ }
25365
+ ),
25366
+ /* @__PURE__ */ jsxRuntime.jsx(
25367
+ "div",
25368
+ {
25369
+ className: "text-gray-600 font-medium",
25370
+ style: {
25371
+ fontSize: "clamp(0.75rem, 3.5cqw, 1rem)",
25372
+ marginTop: "clamp(0.125rem, 1cqw, 0.25rem)"
25373
+ },
25374
+ children: label
25375
+ }
25376
+ ),
25377
+ target !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(
25378
+ "div",
25379
+ {
25380
+ className: "text-gray-500",
25381
+ style: {
25382
+ fontSize: "clamp(0.7rem, 3cqw, 0.875rem)",
25383
+ marginTop: "clamp(0.125rem, 1cqw, 0.25rem)"
25384
+ },
25385
+ children: [
25386
+ "Target: ",
25387
+ target,
25388
+ unit
25389
+ ]
25390
+ }
25391
+ )
25327
25392
  ] }) }),
25328
25393
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-[15%] left-[15%] text-xs text-gray-500", children: [
25329
25394
  min,
@@ -26054,12 +26119,6 @@ var AxelNotificationPopup = ({
26054
26119
  };
26055
26120
 
26056
26121
  // src/views/components/workspace/BottlenecksContent.utils.ts
26057
- var formatTime2 = (seconds) => {
26058
- if (isNaN(seconds)) return "0:00";
26059
- const minutes = Math.floor(seconds / 60);
26060
- const remainingSeconds = Math.floor(seconds % 60);
26061
- return `${minutes}:${remainingSeconds < 10 ? "0" : ""}${remainingSeconds}`;
26062
- };
26063
26122
  var getSeverityColor = (severity) => {
26064
26123
  switch (severity) {
26065
26124
  case "low":
@@ -26072,957 +26131,575 @@ var getSeverityColor = (severity) => {
26072
26131
  return "bg-gray-500";
26073
26132
  }
26074
26133
  };
26075
- function Skeleton({ className, ...props }) {
26076
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("animate-pulse rounded-md bg-muted", className), ...props });
26077
- }
26078
- var Select = SelectPrimitive__namespace.Root;
26079
- var SelectGroup = SelectPrimitive__namespace.Group;
26080
- var SelectValue = SelectPrimitive__namespace.Value;
26081
- var SelectTrigger = React23__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
26082
- SelectPrimitive__namespace.Trigger,
26083
- {
26084
- ref,
26085
- className: cn(
26086
- "flex h-11 sm:h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 touch-manipulation",
26087
- className
26088
- ),
26089
- ...props,
26090
- children: [
26091
- children,
26092
- /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Icon, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" }) })
26093
- ]
26094
- }
26095
- ));
26096
- SelectTrigger.displayName = SelectPrimitive__namespace.Trigger.displayName;
26097
- var SelectScrollUpButton = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
26098
- SelectPrimitive__namespace.ScrollUpButton,
26099
- {
26100
- ref,
26101
- className: cn("flex cursor-default items-center justify-center py-1", className),
26102
- ...props,
26103
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-4 w-4" })
26104
- }
26105
- ));
26106
- SelectScrollUpButton.displayName = SelectPrimitive__namespace.ScrollUpButton.displayName;
26107
- var SelectScrollDownButton = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
26108
- SelectPrimitive__namespace.ScrollDownButton,
26109
- {
26110
- ref,
26111
- className: cn("flex cursor-default items-center justify-center py-1", className),
26112
- ...props,
26113
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" })
26114
- }
26115
- ));
26116
- SelectScrollDownButton.displayName = SelectPrimitive__namespace.ScrollDownButton.displayName;
26117
- var SelectContent = React23__namespace.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
26118
- SelectPrimitive__namespace.Content,
26119
- {
26120
- ref,
26121
- className: cn(
26122
- "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
26123
- position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
26124
- className
26125
- ),
26126
- position,
26127
- ...props,
26128
- children: [
26129
- /* @__PURE__ */ jsxRuntime.jsx(SelectScrollUpButton, {}),
26130
- /* @__PURE__ */ jsxRuntime.jsx(
26131
- SelectPrimitive__namespace.Viewport,
26132
- {
26133
- className: cn(
26134
- "p-1",
26135
- position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
26136
- ),
26137
- children
26138
- }
26139
- ),
26140
- /* @__PURE__ */ jsxRuntime.jsx(SelectScrollDownButton, {})
26141
- ]
26142
- }
26143
- ) }));
26144
- SelectContent.displayName = SelectPrimitive__namespace.Content.displayName;
26145
- var SelectLabel = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
26146
- SelectPrimitive__namespace.Label,
26147
- {
26148
- ref,
26149
- className: cn("px-2 py-1.5 text-sm font-semibold", className),
26150
- ...props
26151
- }
26152
- ));
26153
- SelectLabel.displayName = SelectPrimitive__namespace.Label.displayName;
26154
- var SelectItem = React23__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
26155
- SelectPrimitive__namespace.Item,
26156
- {
26157
- ref,
26158
- className: cn(
26159
- "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
26160
- className
26161
- ),
26162
- ...props,
26163
- children: [
26164
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemIndicator, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }) }),
26165
- /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemText, { children })
26166
- ]
26167
- }
26168
- ));
26169
- SelectItem.displayName = SelectPrimitive__namespace.Item.displayName;
26170
- var SelectSeparator = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
26171
- SelectPrimitive__namespace.Separator,
26172
- {
26173
- ref,
26174
- className: cn("-mx-1 my-1 h-px bg-muted", className),
26175
- ...props
26176
- }
26177
- ));
26178
- SelectSeparator.displayName = SelectPrimitive__namespace.Separator.displayName;
26179
- var LoadingOverlay = ({
26180
- isVisible,
26181
- message = "Loading...",
26182
- className
26134
+ var PlayPauseIndicator = ({
26135
+ show,
26136
+ isPlaying,
26137
+ duration = 600
26183
26138
  }) => {
26139
+ const [isVisible, setIsVisible] = React23.useState(false);
26140
+ const [isFading, setIsFading] = React23.useState(false);
26141
+ React23.useEffect(() => {
26142
+ if (show) {
26143
+ setIsVisible(true);
26144
+ setIsFading(false);
26145
+ const fadeTimer = setTimeout(() => {
26146
+ setIsFading(true);
26147
+ }, 100);
26148
+ const hideTimer = setTimeout(() => {
26149
+ setIsVisible(false);
26150
+ setIsFading(false);
26151
+ }, duration);
26152
+ return () => {
26153
+ clearTimeout(fadeTimer);
26154
+ clearTimeout(hideTimer);
26155
+ };
26156
+ }
26157
+ }, [show, duration]);
26184
26158
  if (!isVisible) return null;
26185
26159
  return /* @__PURE__ */ jsxRuntime.jsx(
26186
- motion.div,
26160
+ "div",
26187
26161
  {
26188
- initial: { opacity: 0 },
26189
- animate: { opacity: 1 },
26190
- exit: { opacity: 0 },
26191
- transition: { duration: 0.2 },
26192
- className: `fixed inset-0 z-[100] flex items-center justify-center bg-black/30 backdrop-blur-sm ${className || ""}`,
26193
- "aria-modal": "true",
26194
- role: "dialog",
26195
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center space-y-3 rounded-lg bg-white p-8 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message }) })
26162
+ className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
26163
+ style: {
26164
+ opacity: isFading ? 0 : 1,
26165
+ transition: `opacity ${duration - 100}ms ease-out`
26166
+ },
26167
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
26168
+ // Play icon (triangle)
26169
+ /* @__PURE__ */ jsxRuntime.jsx(
26170
+ "svg",
26171
+ {
26172
+ xmlns: "http://www.w3.org/2000/svg",
26173
+ viewBox: "0 0 24 24",
26174
+ fill: "white",
26175
+ className: "w-16 h-16",
26176
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 5v14l11-7z" })
26177
+ }
26178
+ )
26179
+ ) : (
26180
+ // Pause icon (two bars)
26181
+ /* @__PURE__ */ jsxRuntime.jsx(
26182
+ "svg",
26183
+ {
26184
+ xmlns: "http://www.w3.org/2000/svg",
26185
+ viewBox: "0 0 24 24",
26186
+ fill: "white",
26187
+ className: "w-16 h-16",
26188
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
26189
+ }
26190
+ )
26191
+ ) })
26196
26192
  }
26197
26193
  );
26198
26194
  };
26199
- var LoadingOverlay_default = LoadingOverlay;
26200
- var TimeDisplay = ({ className, variant = "default" }) => {
26201
- const { dateTimeConfig } = useDashboardConfig();
26202
- const [time2, setTime] = React23.useState("");
26203
- const dbTimezone = useAppTimezone();
26204
- const timezoneToDisplay = dbTimezone || dateTimeConfig?.defaultTimezone || "UTC";
26205
- const localeToUse = dateTimeConfig?.defaultLocale || "en-US";
26206
- const timeSuffix = "";
26207
- React23.useEffect(() => {
26208
- const updateTime = () => {
26209
- const now2 = /* @__PURE__ */ new Date();
26210
- const effectiveFormatOptions = {
26211
- hour: "2-digit",
26212
- minute: "2-digit",
26213
- second: "2-digit",
26214
- hour12: true,
26215
- timeZone: timezoneToDisplay,
26216
- ...dateTimeConfig?.timeFormatOptions || {}
26217
- // Allow override from config
26218
- };
26219
- try {
26220
- setTime(new Intl.DateTimeFormat(localeToUse, effectiveFormatOptions).format(now2));
26221
- } catch (e) {
26222
- console.error("Error formatting time:", e);
26223
- setTime("Error");
26224
- }
26225
- };
26226
- updateTime();
26227
- const interval = setInterval(updateTime, 1e3);
26228
- return () => clearInterval(interval);
26229
- }, [timezoneToDisplay, dateTimeConfig?.timeFormatOptions, localeToUse]);
26230
- if (!time2) return null;
26231
- if (variant === "minimal") {
26232
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: className || "", children: [
26233
- time2,
26234
- " ",
26235
- timeSuffix
26236
- ] });
26195
+ var ERROR_MAPPING = {
26196
+ "networkError": {
26197
+ code: 2,
26198
+ type: "recoverable",
26199
+ message: "Network error - please check your internet connection",
26200
+ canRetry: true
26201
+ },
26202
+ "mediaError": {
26203
+ code: 3,
26204
+ type: "non_recoverable",
26205
+ message: "Stream corrupted due to internet connection",
26206
+ canRetry: false
26207
+ },
26208
+ "muxError": {
26209
+ code: 3,
26210
+ type: "non_recoverable",
26211
+ message: "Error processing media stream",
26212
+ canRetry: false
26213
+ },
26214
+ "otherError": {
26215
+ code: 4,
26216
+ type: "non_recoverable",
26217
+ message: "Video format not supported by your browser. Please use Google Chrome.",
26218
+ canRetry: false
26237
26219
  }
26238
- return /* @__PURE__ */ jsxRuntime.jsxs(
26239
- motion.div,
26240
- {
26241
- initial: { opacity: 0, y: -5 },
26242
- animate: { opacity: 1, y: 0 },
26243
- transition: { duration: 0.3 },
26244
- className: `flex items-center space-x-1.5 bg-white/60 backdrop-blur-sm px-2 py-0.5 rounded-md shadow-xs ${className || ""}`,
26245
- children: [
26246
- /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-3 w-3 text-[var(--primary-DEFAULT)]", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z", clipRule: "evenodd" }) }),
26247
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs sm:text-[11px] font-medium text-gray-800 tabular-nums tracking-tight", children: [
26248
- time2,
26249
- " ",
26250
- timeSuffix
26251
- ] })
26252
- ]
26253
- }
26254
- );
26255
26220
  };
26256
- var TimeDisplay_default = TimeDisplay;
26257
- var DateDisplay = ({ className, variant = "default" }) => {
26258
- const { dateTimeConfig } = useDashboardConfig();
26259
- const [date, setDate] = React23.useState("");
26260
- const timezoneToDisplay = dateTimeConfig?.defaultTimezone || "UTC";
26261
- const localeToUse = dateTimeConfig?.defaultLocale || "en-US";
26221
+ var hlsVideoPlayerStyles = `
26222
+ .hls-video-player-container {
26223
+ width: 100%;
26224
+ height: 100%;
26225
+ background-color: #000;
26226
+ display: flex;
26227
+ align-items: center;
26228
+ justify-content: center;
26229
+ }
26230
+
26231
+ /* Center the video and maintain aspect ratio */
26232
+ .hls-video-element {
26233
+ width: 100%;
26234
+ height: 100%;
26235
+ max-width: 100%;
26236
+ max-height: 100%;
26237
+ object-fit: contain;
26238
+ }
26239
+
26240
+ /* Custom loading indicator styles */
26241
+ .hls-video-player-loading {
26242
+ position: absolute;
26243
+ top: 50%;
26244
+ left: 50%;
26245
+ transform: translate(-50%, -50%);
26246
+ z-index: 10;
26247
+ }
26248
+ `;
26249
+ if (typeof document !== "undefined") {
26250
+ const styleId = "hls-video-player-custom-styles";
26251
+ if (!document.getElementById(styleId)) {
26252
+ const style = document.createElement("style");
26253
+ style.id = styleId;
26254
+ style.textContent = hlsVideoPlayerStyles;
26255
+ document.head.appendChild(style);
26256
+ }
26257
+ }
26258
+ var BASE_HLS_CONFIG = {
26259
+ maxBufferLength: 3,
26260
+ maxMaxBufferLength: 8,
26261
+ maxBufferSize: 50 * 1e3 * 1e3,
26262
+ maxBufferHole: 0.25,
26263
+ manifestLoadingTimeOut: 15e3,
26264
+ manifestLoadingMaxRetry: 3,
26265
+ manifestLoadingRetryDelay: 500,
26266
+ levelLoadingTimeOut: 6e4,
26267
+ levelLoadingMaxRetry: 5,
26268
+ levelLoadingRetryDelay: 500,
26269
+ fragLoadingTimeOut: 6e4,
26270
+ fragLoadingMaxRetry: 5,
26271
+ fragLoadingRetryDelay: 500,
26272
+ startPosition: -1,
26273
+ debug: false,
26274
+ enableWorker: true,
26275
+ lowLatencyMode: false,
26276
+ progressive: true,
26277
+ abrEwmaSlowLive: 9,
26278
+ abrEwmaFastLive: 3,
26279
+ abrBandWidthFactor: 0.95,
26280
+ abrBandWidthUpFactor: 0.7,
26281
+ abrMaxWithRealBitrate: false,
26282
+ abrEwmaDefaultEstimate: 5e7
26283
+ };
26284
+ var HlsVideoPlayer = React23.forwardRef(({
26285
+ src,
26286
+ poster,
26287
+ autoplay = false,
26288
+ controls = true,
26289
+ loop = false,
26290
+ muted = false,
26291
+ playsInline = true,
26292
+ className = "",
26293
+ hlsConfig = {},
26294
+ options = {},
26295
+ // Backward compatibility with Video.js
26296
+ externalLoadingControl = false,
26297
+ onLoadingChange,
26298
+ onReady,
26299
+ onPlay,
26300
+ onPause,
26301
+ onPlaying,
26302
+ onTimeUpdate,
26303
+ onDurationChange,
26304
+ onEnded,
26305
+ onError,
26306
+ onLoadStart,
26307
+ onLoadedMetadata,
26308
+ onLoadedData,
26309
+ onSeeking,
26310
+ onSeeked,
26311
+ onClick
26312
+ }, ref) => {
26313
+ const videoContainerRef = React23.useRef(null);
26314
+ const videoRef = React23.useRef(null);
26315
+ const hlsRef = React23.useRef(null);
26316
+ const blobUrlRef = React23.useRef(null);
26317
+ const [isReady, setIsReady] = React23.useState(false);
26318
+ const [isLoading, setIsLoading] = React23.useState(true);
26319
+ const [showIndicator, setShowIndicator] = React23.useState(false);
26320
+ const [indicatorIsPlaying, setIndicatorIsPlaying] = React23.useState(false);
26321
+ const indicatorKeyRef = React23.useRef(0);
26322
+ const eventCallbacksRef = React23.useRef({
26323
+ onReady,
26324
+ onPlay,
26325
+ onPause,
26326
+ onPlaying,
26327
+ onTimeUpdate,
26328
+ onDurationChange,
26329
+ onEnded,
26330
+ onError,
26331
+ onLoadStart,
26332
+ onLoadedMetadata,
26333
+ onLoadedData,
26334
+ onSeeking,
26335
+ onSeeked,
26336
+ onLoadingChange
26337
+ });
26262
26338
  React23.useEffect(() => {
26263
- const getCurrentFormattedDate = () => {
26264
- const now2 = /* @__PURE__ */ new Date();
26265
- const effectiveFormatOptions = {
26266
- weekday: "short",
26267
- day: "numeric",
26268
- month: "short",
26269
- timeZone: timezoneToDisplay,
26270
- ...dateTimeConfig?.dateFormatOptions || {}
26271
- // Allow override from config
26272
- };
26273
- try {
26274
- return new Intl.DateTimeFormat(localeToUse, effectiveFormatOptions).format(now2);
26275
- } catch (e) {
26276
- console.error("Error formatting date:", e);
26277
- return "Error";
26278
- }
26339
+ eventCallbacksRef.current = {
26340
+ onReady,
26341
+ onPlay,
26342
+ onPause,
26343
+ onPlaying,
26344
+ onTimeUpdate,
26345
+ onDurationChange,
26346
+ onEnded,
26347
+ onError,
26348
+ onLoadStart,
26349
+ onLoadedMetadata,
26350
+ onLoadedData,
26351
+ onSeeking,
26352
+ onSeeked,
26353
+ onLoadingChange
26279
26354
  };
26280
- const updateDate = () => {
26281
- setDate(getCurrentFormattedDate());
26355
+ }, [
26356
+ onReady,
26357
+ onPlay,
26358
+ onPause,
26359
+ onPlaying,
26360
+ onTimeUpdate,
26361
+ onDurationChange,
26362
+ onEnded,
26363
+ onError,
26364
+ onLoadStart,
26365
+ onLoadedMetadata,
26366
+ onLoadedData,
26367
+ onSeeking,
26368
+ onSeeked,
26369
+ onLoadingChange
26370
+ ]);
26371
+ const stableHlsConfigRef = React23.useRef(hlsConfig);
26372
+ const stableOptionsRef = React23.useRef(options);
26373
+ const configSignatureRef = React23.useRef("");
26374
+ const [configVersion, setConfigVersion] = React23.useState(0);
26375
+ React23.useEffect(() => {
26376
+ const serialized = JSON.stringify({
26377
+ hlsConfig: hlsConfig || null,
26378
+ options: options || null
26379
+ });
26380
+ if (!configSignatureRef.current) {
26381
+ configSignatureRef.current = serialized;
26382
+ stableHlsConfigRef.current = hlsConfig;
26383
+ stableOptionsRef.current = options;
26384
+ return;
26385
+ }
26386
+ if (configSignatureRef.current !== serialized) {
26387
+ configSignatureRef.current = serialized;
26388
+ stableHlsConfigRef.current = hlsConfig;
26389
+ stableOptionsRef.current = options;
26390
+ setConfigVersion((prev) => prev + 1);
26391
+ }
26392
+ }, [hlsConfig, options]);
26393
+ const cleanupBlobUrl = React23.useCallback(() => {
26394
+ if (blobUrlRef.current) {
26395
+ URL.revokeObjectURL(blobUrlRef.current);
26396
+ blobUrlRef.current = null;
26397
+ }
26398
+ }, []);
26399
+ const dispose = React23.useCallback(() => {
26400
+ if (hlsRef.current) {
26401
+ hlsRef.current.destroy();
26402
+ hlsRef.current = null;
26403
+ }
26404
+ cleanupBlobUrl();
26405
+ setIsReady(false);
26406
+ }, [cleanupBlobUrl]);
26407
+ const playerLikeObject = React23.useCallback(() => {
26408
+ return {
26409
+ el: () => videoRef.current,
26410
+ currentTime: () => videoRef.current?.currentTime || 0,
26411
+ duration: () => videoRef.current?.duration || 0,
26412
+ paused: () => videoRef.current?.paused ?? true,
26413
+ play: () => videoRef.current?.play(),
26414
+ pause: () => videoRef.current?.pause(),
26415
+ muted: (val) => {
26416
+ if (videoRef.current) {
26417
+ if (val !== void 0) videoRef.current.muted = val;
26418
+ return videoRef.current.muted;
26419
+ }
26420
+ return false;
26421
+ },
26422
+ volume: (val) => {
26423
+ if (videoRef.current) {
26424
+ if (val !== void 0) videoRef.current.volume = val;
26425
+ return videoRef.current.volume;
26426
+ }
26427
+ return 1;
26428
+ },
26429
+ error: () => null,
26430
+ dispose: () => dispose()
26282
26431
  };
26283
- updateDate();
26284
- const interval = setInterval(() => {
26285
- const currentDateStr = getCurrentFormattedDate();
26286
- if (currentDateStr !== date) {
26287
- updateDate();
26432
+ }, [dispose]);
26433
+ const initializePlayer = React23.useCallback(() => {
26434
+ if (!videoRef.current || !src) return;
26435
+ const video = videoRef.current;
26436
+ const player = playerLikeObject();
26437
+ const mergedHlsConfig = {
26438
+ ...BASE_HLS_CONFIG,
26439
+ ...stableHlsConfigRef.current || {},
26440
+ ...stableOptionsRef.current || {}
26441
+ };
26442
+ cleanupBlobUrl();
26443
+ const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
26444
+ if (isHLS) {
26445
+ if (src.startsWith("#EXTM3U")) {
26446
+ const safariMode = isSafari();
26447
+ const browserName = getBrowserName();
26448
+ if (safariMode) {
26449
+ const clipIdMatch = src.match(/# Clip ID: ([a-f0-9-]+)/i);
26450
+ if (clipIdMatch) {
26451
+ const proxyUrl = `/api/clips/playlist/${clipIdMatch[1]}`;
26452
+ console.log(`[HlsVideoPlayer] Safari detected (${browserName}) - using playlist proxy URL:`, proxyUrl);
26453
+ video.src = proxyUrl;
26454
+ } else {
26455
+ console.warn("[HlsVideoPlayer] Safari detected but no clip ID found in playlist, trying blob URL fallback");
26456
+ const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
26457
+ const blobUrl = URL.createObjectURL(blob);
26458
+ blobUrlRef.current = blobUrl;
26459
+ video.src = blobUrl;
26460
+ }
26461
+ } else if (Hls3__default.default.isSupported()) {
26462
+ const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
26463
+ const blobUrl = URL.createObjectURL(blob);
26464
+ blobUrlRef.current = blobUrl;
26465
+ console.log(`[HlsVideoPlayer] Non-Safari browser (${browserName}) - using HLS.js with blob URL`);
26466
+ const hls = new Hls3__default.default(mergedHlsConfig);
26467
+ hlsRef.current = hls;
26468
+ hls.on(Hls3.Events.MANIFEST_PARSED, () => {
26469
+ console.log("[HlsVideoPlayer] Manifest parsed, ready to play");
26470
+ setIsReady(true);
26471
+ eventCallbacksRef.current.onReady?.(player);
26472
+ });
26473
+ hls.on(Hls3.Events.ERROR, (event, data) => {
26474
+ console.error("[HlsVideoPlayer] HLS.js error:", data);
26475
+ if (data.fatal) {
26476
+ let errorInfo;
26477
+ switch (data.type) {
26478
+ case Hls3.ErrorTypes.NETWORK_ERROR:
26479
+ errorInfo = ERROR_MAPPING.networkError;
26480
+ console.log("[HlsVideoPlayer] Attempting to recover from network error");
26481
+ hls.startLoad();
26482
+ break;
26483
+ case Hls3.ErrorTypes.MEDIA_ERROR:
26484
+ errorInfo = ERROR_MAPPING.mediaError;
26485
+ console.log("[HlsVideoPlayer] Attempting to recover from media error");
26486
+ hls.recoverMediaError();
26487
+ break;
26488
+ case Hls3.ErrorTypes.MUX_ERROR:
26489
+ errorInfo = ERROR_MAPPING.muxError;
26490
+ break;
26491
+ default:
26492
+ errorInfo = ERROR_MAPPING.otherError;
26493
+ break;
26494
+ }
26495
+ errorInfo.details = data.details;
26496
+ eventCallbacksRef.current.onError?.(player, errorInfo);
26497
+ }
26498
+ });
26499
+ hls.on(Hls3.Events.FRAG_LOADING, () => {
26500
+ setIsLoading(true);
26501
+ eventCallbacksRef.current.onLoadingChange?.(true);
26502
+ });
26503
+ hls.on(Hls3.Events.FRAG_LOADED, () => {
26504
+ setIsLoading(false);
26505
+ eventCallbacksRef.current.onLoadingChange?.(false);
26506
+ });
26507
+ hls.loadSource(blobUrl);
26508
+ hls.attachMedia(video);
26509
+ } else {
26510
+ console.error("[HlsVideoPlayer] HLS.js not supported and not Safari - cannot play HLS content");
26511
+ const errorInfo = ERROR_MAPPING.otherError;
26512
+ onError?.(player, errorInfo);
26513
+ }
26514
+ } else {
26515
+ if (Hls3__default.default.isSupported() && !isSafari()) {
26516
+ const hls = new Hls3__default.default(mergedHlsConfig);
26517
+ hlsRef.current = hls;
26518
+ hls.on(Hls3.Events.MANIFEST_PARSED, () => {
26519
+ setIsReady(true);
26520
+ eventCallbacksRef.current.onReady?.(player);
26521
+ });
26522
+ hls.on(Hls3.Events.ERROR, (event, data) => {
26523
+ console.error("[HlsVideoPlayer] HLS.js error:", data);
26524
+ if (data.fatal) {
26525
+ let errorInfo;
26526
+ switch (data.type) {
26527
+ case Hls3.ErrorTypes.NETWORK_ERROR:
26528
+ errorInfo = ERROR_MAPPING.networkError;
26529
+ hls.startLoad();
26530
+ break;
26531
+ case Hls3.ErrorTypes.MEDIA_ERROR:
26532
+ errorInfo = ERROR_MAPPING.mediaError;
26533
+ hls.recoverMediaError();
26534
+ break;
26535
+ default:
26536
+ errorInfo = ERROR_MAPPING.otherError;
26537
+ break;
26538
+ }
26539
+ errorInfo.details = data.details;
26540
+ eventCallbacksRef.current.onError?.(player, errorInfo);
26541
+ }
26542
+ });
26543
+ hls.loadSource(src);
26544
+ hls.attachMedia(video);
26545
+ } else {
26546
+ video.src = src;
26547
+ }
26288
26548
  }
26289
- }, 60 * 1e3);
26290
- return () => clearInterval(interval);
26291
- }, [date, timezoneToDisplay, dateTimeConfig?.dateFormatOptions, localeToUse]);
26292
- if (!date) return null;
26293
- if (variant === "minimal") {
26294
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: className || "", children: date });
26295
- }
26296
- return /* @__PURE__ */ jsxRuntime.jsxs(
26297
- motion.div,
26298
- {
26299
- initial: { opacity: 0, y: -5 },
26300
- animate: { opacity: 1, y: 0 },
26301
- transition: { duration: 0.3 },
26302
- className: `flex items-center space-x-1.5 bg-white/60 backdrop-blur-sm px-2 py-0.5 rounded-md shadow-xs ${className || ""}`,
26303
- children: [
26304
- /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-3 w-3 text-blue-600", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", clipRule: "evenodd" }) }),
26305
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-gray-800 tracking-tight", children: date })
26306
- ]
26549
+ } else {
26550
+ video.src = src;
26307
26551
  }
26308
- );
26309
- };
26310
- var DateDisplay_default = DateDisplay;
26311
- var Card3 = Card2;
26312
- var CardHeader3 = CardHeader2;
26313
- var CardTitle3 = CardTitle2;
26314
- var CardContent3 = CardContent2;
26315
- var MetricCard2 = ({ title, value, unit = "", trend = null }) => {
26316
- const getTrendColor = (trendValue) => {
26317
- if (trendValue === null || trendValue === void 0) return "";
26318
- return trendValue > 0 ? "text-green-500" : trendValue < 0 ? "text-red-500" : "text-gray-500";
26319
- };
26320
- const getTrendSymbol = (trendValue) => {
26321
- if (trendValue === null || trendValue === void 0) return "";
26322
- return trendValue > 0 ? "\u2191" : trendValue < 0 ? "\u2193" : "";
26323
- };
26324
- return /* @__PURE__ */ jsxRuntime.jsxs(Card3, { className: "bg-white", children: [
26325
- /* @__PURE__ */ jsxRuntime.jsx(CardHeader3, { className: "pb-2", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle3, { className: "text-sm font-medium text-gray-500", children: title }) }),
26326
- /* @__PURE__ */ jsxRuntime.jsx(CardContent3, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between", children: [
26327
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl font-semibold", children: [
26328
- value,
26329
- unit && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-sm text-gray-500", children: unit })
26330
- ] }),
26331
- trend !== null && trend !== void 0 && // Check trend for null/undefined before accessing
26332
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex items-center ${getTrendColor(trend)}`, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm", children: [
26333
- getTrendSymbol(trend),
26334
- " ",
26335
- Math.abs(trend),
26336
- "%"
26337
- ] }) })
26338
- ] }) })
26339
- ] });
26340
- };
26341
- var MetricCard_default = MetricCard2;
26342
- var TimePickerDropdown = ({
26343
- value,
26344
- onChange,
26345
- placeholder = "Select time",
26346
- className = "",
26347
- disabled = false
26348
- }) => {
26349
- const [isOpen, setIsOpen] = React23.useState(false);
26350
- const [searchTerm, setSearchTerm] = React23.useState("");
26351
- const dropdownRef = React23.useRef(null);
26352
- const inputRef = React23.useRef(null);
26353
- const generateTimeSlots = () => {
26354
- const slots = [];
26355
- for (let hour = 0; hour < 24; hour++) {
26356
- for (let minute = 0; minute < 60; minute += 15) {
26357
- const time24 = `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
26358
- const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
26359
- const ampm = hour < 12 ? "AM" : "PM";
26360
- const time12 = `${hour12}:${minute.toString().padStart(2, "0")} ${ampm}`;
26361
- slots.push({ value: time24, label: time12 });
26362
- }
26363
- }
26364
- return slots;
26365
- };
26366
- const timeSlots = generateTimeSlots();
26367
- const filteredSlots = timeSlots.filter(
26368
- (slot) => slot.label.toLowerCase().includes(searchTerm.toLowerCase())
26369
- );
26370
- const getDisplayValue = (value2) => {
26371
- if (!value2) return "";
26372
- const normalizedValue = value2.substring(0, 5);
26373
- const slot = timeSlots.find((s) => s.value === normalizedValue);
26374
- return slot ? slot.label : value2;
26375
- };
26376
- React23.useEffect(() => {
26377
- const handleClickOutside = (event) => {
26378
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
26379
- setIsOpen(false);
26380
- setSearchTerm("");
26552
+ const handleCanPlay = () => {
26553
+ if (!hlsRef.current) {
26554
+ setIsReady(true);
26555
+ onReady?.(player);
26381
26556
  }
26382
26557
  };
26383
- document.addEventListener("mousedown", handleClickOutside);
26384
- return () => document.removeEventListener("mousedown", handleClickOutside);
26385
- }, []);
26386
- const handleKeyDown = (e) => {
26387
- if (e.key === "Escape") {
26388
- setIsOpen(false);
26389
- setSearchTerm("");
26390
- } else if (e.key === "Enter") {
26391
- e.preventDefault();
26392
- if (filteredSlots.length > 0) {
26393
- onChange(filteredSlots[0].value);
26394
- setIsOpen(false);
26395
- setSearchTerm("");
26396
- }
26397
- }
26398
- };
26399
- const handleSelect = (timeValue) => {
26400
- onChange(timeValue);
26401
- setIsOpen(false);
26402
- setSearchTerm("");
26403
- };
26404
- const handleToggle = () => {
26405
- if (disabled) return;
26406
- setIsOpen(!isOpen);
26407
- if (!isOpen) {
26408
- setTimeout(() => inputRef.current?.focus(), 100);
26409
- }
26410
- };
26411
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className}`, ref: dropdownRef, children: [
26412
- /* @__PURE__ */ jsxRuntime.jsx(
26413
- "button",
26414
- {
26415
- type: "button",
26416
- onClick: handleToggle,
26417
- disabled,
26418
- className: `
26419
- w-full px-3 py-2 text-left bg-white border border-gray-300 rounded-md shadow-sm
26420
- focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
26421
- hover:border-gray-400 transition-colors duration-200
26422
- ${disabled ? "bg-gray-50 cursor-not-allowed" : "cursor-pointer"}
26423
- ${isOpen ? "ring-2 ring-blue-500 border-blue-500" : ""}
26424
- `,
26425
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
26426
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
26427
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4 text-gray-400" }),
26428
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-sm ${value ? "text-gray-900" : "text-gray-500"}`, children: value ? getDisplayValue(value) : placeholder })
26429
- ] }),
26430
- /* @__PURE__ */ jsxRuntime.jsx(
26431
- lucideReact.ChevronDown,
26432
- {
26433
- className: `h-4 w-4 text-gray-400 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`
26434
- }
26435
- )
26436
- ] })
26437
- }
26438
- ),
26439
- isOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-hidden", children: [
26440
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx(
26441
- "input",
26442
- {
26443
- ref: inputRef,
26444
- type: "text",
26445
- placeholder: "Search time...",
26446
- value: searchTerm,
26447
- onChange: (e) => setSearchTerm(e.target.value),
26448
- onKeyDown: handleKeyDown,
26449
- className: "w-full px-3 py-2 text-sm border border-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
26450
- }
26451
- ) }),
26452
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-y-auto max-h-48", children: filteredSlots.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-gray-500 text-center", children: "No times found" }) : filteredSlots.map((slot) => /* @__PURE__ */ jsxRuntime.jsx(
26453
- "button",
26454
- {
26455
- type: "button",
26456
- onClick: () => handleSelect(slot.value),
26457
- className: `
26458
- w-full px-3 py-2 text-left text-sm hover:bg-blue-50 hover:text-blue-600
26459
- transition-colors duration-150 border-b border-gray-100 last:border-b-0
26460
- ${slot.value === value ? "bg-blue-50 text-blue-600 font-medium" : "text-gray-700"}
26461
- `,
26462
- children: slot.label
26463
- },
26464
- slot.value
26465
- )) })
26466
- ] })
26467
- ] });
26468
- };
26469
- var SilentErrorBoundary = class extends React23__namespace.default.Component {
26470
- constructor(props) {
26471
- super(props);
26472
- this.handleClearAndReload = () => {
26473
- console.log("[ErrorBoundary] User initiated reset");
26474
- if (typeof window !== "undefined") {
26475
- try {
26476
- localStorage.clear();
26477
- sessionStorage.clear();
26478
- console.log("[ErrorBoundary] Cleared all storage");
26479
- } catch (error) {
26480
- console.error("[ErrorBoundary] Failed to clear storage:", error);
26481
- }
26482
- }
26483
- window.location.href = "/login";
26558
+ const handlePlay = () => eventCallbacksRef.current.onPlay?.(player);
26559
+ const handlePause = () => eventCallbacksRef.current.onPause?.(player);
26560
+ const handlePlaying = () => {
26561
+ setIsLoading(false);
26562
+ eventCallbacksRef.current.onLoadingChange?.(false);
26563
+ eventCallbacksRef.current.onPlaying?.(player);
26484
26564
  };
26485
- this.state = {
26486
- hasError: false,
26487
- errorCount: 0,
26488
- lastError: null,
26489
- errorInfo: null
26565
+ const handleTimeUpdate = () => {
26566
+ const currentTime2 = video.currentTime || 0;
26567
+ eventCallbacksRef.current.onTimeUpdate?.(player, currentTime2);
26490
26568
  };
26491
- }
26492
- static getDerivedStateFromError(error) {
26493
- return { hasError: true };
26494
- }
26495
- componentDidCatch(error, errorInfo) {
26496
- console.error("[ErrorBoundary] Caught render error:", {
26497
- error: error.message,
26498
- stack: error.stack,
26499
- componentStack: errorInfo.componentStack,
26500
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
26501
- });
26502
- this.setState((prev) => ({
26503
- errorCount: prev.errorCount + 1,
26504
- lastError: error,
26505
- errorInfo
26506
- }));
26507
- try {
26508
- if (typeof window !== "undefined" && window.mixpanel) {
26509
- window.mixpanel.track("React Render Error", {
26510
- error: error.message,
26511
- component: errorInfo.componentStack?.split("\n")[1] || "unknown",
26512
- errorCount: this.state.errorCount + 1
26513
- });
26514
- }
26515
- } catch (analyticsError) {
26516
- console.warn("[ErrorBoundary] Analytics tracking failed:", analyticsError);
26517
- }
26518
- }
26519
- render() {
26520
- if (!this.state.hasError) {
26521
- return this.props.children;
26522
- }
26523
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen w-screen items-center justify-center bg-slate-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center space-y-6 text-center", children: [
26524
- /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading Dashboard..." }),
26525
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "Taking longer than usual..." }),
26526
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
26527
- "a",
26528
- {
26529
- href: "#",
26530
- onClick: (e) => {
26531
- e.preventDefault();
26532
- this.handleClearAndReload();
26533
- },
26534
- className: "text-xs text-gray-400 hover:text-gray-600 underline transition-colors",
26535
- children: "Reset and try again"
26536
- }
26537
- ) }),
26538
- process.env.NODE_ENV === "development" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400 italic mt-4", children: "Check console for error details" })
26539
- ] }) });
26540
- }
26541
- };
26542
- var PlayPauseIndicator = ({
26543
- show,
26544
- isPlaying,
26545
- duration = 600
26546
- }) => {
26547
- const [isVisible, setIsVisible] = React23.useState(false);
26548
- const [isFading, setIsFading] = React23.useState(false);
26549
- React23.useEffect(() => {
26550
- if (show) {
26551
- setIsVisible(true);
26552
- setIsFading(false);
26553
- const fadeTimer = setTimeout(() => {
26554
- setIsFading(true);
26555
- }, 100);
26556
- const hideTimer = setTimeout(() => {
26557
- setIsVisible(false);
26558
- setIsFading(false);
26559
- }, duration);
26560
- return () => {
26561
- clearTimeout(fadeTimer);
26562
- clearTimeout(hideTimer);
26563
- };
26564
- }
26565
- }, [show, duration]);
26566
- if (!isVisible) return null;
26567
- return /* @__PURE__ */ jsxRuntime.jsx(
26568
- "div",
26569
- {
26570
- className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
26571
- style: {
26572
- opacity: isFading ? 0 : 1,
26573
- transition: `opacity ${duration - 100}ms ease-out`
26574
- },
26575
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
26576
- // Play icon (triangle)
26577
- /* @__PURE__ */ jsxRuntime.jsx(
26578
- "svg",
26579
- {
26580
- xmlns: "http://www.w3.org/2000/svg",
26581
- viewBox: "0 0 24 24",
26582
- fill: "white",
26583
- className: "w-16 h-16",
26584
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 5v14l11-7z" })
26585
- }
26586
- )
26587
- ) : (
26588
- // Pause icon (two bars)
26589
- /* @__PURE__ */ jsxRuntime.jsx(
26590
- "svg",
26591
- {
26592
- xmlns: "http://www.w3.org/2000/svg",
26593
- viewBox: "0 0 24 24",
26594
- fill: "white",
26595
- className: "w-16 h-16",
26596
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
26597
- }
26598
- )
26599
- ) })
26600
- }
26601
- );
26602
- };
26603
- var ERROR_MAPPING = {
26604
- 1: {
26605
- // MEDIA_ERR_ABORTED
26606
- code: 1,
26607
- type: "recoverable" /* RECOVERABLE */,
26608
- message: "Video loading was interrupted",
26609
- canRetry: true
26610
- },
26611
- 2: {
26612
- // MEDIA_ERR_NETWORK
26613
- code: 2,
26614
- type: "recoverable" /* RECOVERABLE */,
26615
- message: "Network error - please check your internet connection",
26616
- canRetry: true
26617
- },
26618
- 3: {
26619
- // MEDIA_ERR_DECODE
26620
- code: 3,
26621
- type: "non_recoverable" /* NON_RECOVERABLE */,
26622
- message: "Stream corrupted due to internet connection",
26623
- canRetry: false
26624
- },
26625
- 4: {
26626
- // MEDIA_ERR_SRC_NOT_SUPPORTED
26627
- code: 4,
26628
- type: "non_recoverable" /* NON_RECOVERABLE */,
26629
- message: "Video format not supported by your browser. Please use Google Chrome.",
26630
- canRetry: false
26631
- }
26632
- };
26633
- var videoPlayerStyles = `
26634
- .video-player-container {
26635
- width: 100%;
26636
- height: 100%;
26637
- background-color: #000;
26638
- display: flex;
26639
- align-items: center;
26640
- justify-content: center;
26641
- }
26642
-
26643
- /* Center the video player and maintain aspect ratio */
26644
- .video-js {
26645
- width: 100%;
26646
- height: 100%;
26647
- max-width: 100%;
26648
- max-height: 100%;
26649
- }
26650
-
26651
- /* Ensure video maintains aspect ratio and shows fully */
26652
- .video-js .vjs-tech {
26653
- position: absolute;
26654
- top: 0;
26655
- left: 0;
26656
- width: 100%;
26657
- height: 100%;
26658
- object-fit: contain;
26659
- }
26660
-
26661
- /* Hide default Video.js loading spinner */
26662
- .video-js .vjs-loading-spinner {
26663
- display: none !important;
26664
- }
26665
-
26666
- /* Custom loading indicator styles */
26667
- .video-player-loading {
26668
- position: absolute;
26669
- top: 50%;
26670
- left: 50%;
26671
- transform: translate(-50%, -50%);
26672
- z-index: 10;
26673
- }
26674
- `;
26675
- if (typeof document !== "undefined") {
26676
- const styleId = "video-player-custom-styles";
26677
- if (!document.getElementById(styleId)) {
26678
- const style = document.createElement("style");
26679
- style.id = styleId;
26680
- style.textContent = videoPlayerStyles;
26681
- document.head.appendChild(style);
26682
- }
26683
- }
26684
- var VideoPlayer = React23__namespace.default.forwardRef(({
26685
- src,
26686
- poster,
26687
- autoplay = false,
26688
- controls = true,
26689
- loop = false,
26690
- muted = false,
26691
- playsInline = true,
26692
- className = "",
26693
- options = {},
26694
- externalLoadingControl = false,
26695
- onLoadingChange,
26696
- onReady,
26697
- onPlay,
26698
- onPause,
26699
- onPlaying,
26700
- onTimeUpdate,
26701
- onDurationChange,
26702
- onEnded,
26703
- onError,
26704
- onLoadStart,
26705
- onLoadedMetadata,
26706
- onLoadedData,
26707
- onSeeking,
26708
- onSeeked,
26709
- onClick
26710
- }, ref) => {
26711
- const videoRef = React23.useRef(null);
26712
- const playerRef = React23.useRef(null);
26713
- const [isReady, setIsReady] = React23.useState(false);
26714
- const [isLoading, setIsLoading] = React23.useState(true);
26715
- const [showIndicator, setShowIndicator] = React23.useState(false);
26716
- const [indicatorIsPlaying, setIndicatorIsPlaying] = React23.useState(false);
26717
- const indicatorKeyRef = React23.useRef(0);
26718
- const defaultOptions = {
26719
- controls: false,
26720
- // Always disable Video.js controls - we use custom controls
26721
- // Don't use fluid or fill - let the video maintain its natural aspect ratio
26722
- fluid: false,
26723
- // Disable fluid mode to prevent stretching
26724
- responsive: false,
26725
- // Disable responsive mode
26726
- fill: false,
26727
- // Don't fill - maintain aspect ratio
26728
- playsinline: playsInline,
26729
- preload: "metadata",
26730
- autoplay: autoplay ? "any" : false,
26731
- muted,
26732
- loop,
26733
- poster,
26734
- // Optimized HLS configuration for VOD content with LARGE SEGMENTS (40MB)
26735
- // Strategy: Aggressive buffer reduction for faster startup with large segments
26736
- html5: {
26737
- vhs: {
26738
- // VHS (Video HTTP Streaming) options for HLS
26739
- withCredentials: false,
26740
- handleManifestRedirect: true,
26741
- overrideNative: !videojs__default.default.browser.IS_SAFARI,
26742
- // Use native HLS on Safari
26743
- enableLowInitialPlaylist: true,
26744
- smoothQualityChange: true,
26745
- // Optimized bandwidth and buffering for VOD
26746
- bandwidth: 5e7,
26747
- // Start with high bandwidth assumption (50 Mbps)
26748
- initialBandwidth: 5e7,
26749
- // Assume good connection initially
26750
- limitRenditionByPlayerDimensions: true,
26751
- // Buffer configuration optimized for large segments (40MB each)
26752
- maxBufferLength: 3,
26753
- // Start playing with just 3 seconds ahead
26754
- maxMaxBufferLength: 8,
26755
- // Maximum 8 seconds buffer for faster startup
26756
- maxBufferSize: 50 * 1e3 * 1e3,
26757
- // 50MB max buffer size (1.25 segments)
26758
- maxBufferHole: 0.25,
26759
- // Smaller holes for better continuity
26760
- bufferBasedABR: false,
26761
- // Disable for more predictable behavior
26762
- // Segment loading optimization for large segments (40MB)
26763
- maxPlaylistRetries: 3,
26764
- playlistRetryDelay: 500,
26765
- // 500ms between retries
26766
- playlistExclusionDuration: 60,
26767
- segmentLoadingRetryAttempts: 5,
26768
- // More retries for large segments
26769
- segmentLoadingRetryDelay: 500,
26770
- // Faster retry for responsiveness
26771
- segmentLoadingTimeOut: 6e4,
26772
- // 60s timeout for 40MB segments
26773
- manifestLoadingTimeOut: 15e3,
26774
- // 15s timeout for manifest
26775
- // Performance optimizations
26776
- experimentalBufferBasedCodecSwitching: true,
26777
- experimentalCacheEncryptionKeys: true,
26778
- handlePartialData: true,
26779
- allowSeeksWithinUnsafeLiveWindow: false,
26780
- // VOD content
26781
- experimentalLLHLS: false,
26782
- // Disable Low Latency HLS for VOD
26783
- // Connection settings
26784
- // Chrome 130+ started throwing "Cannot perform Construct on a detached ArrayBuffer"
26785
- // whenever the transmux worker tried to rehydrate transferred buffers that originated
26786
- // from Blob-based playlists. Disabling the worker keeps playback stable at the cost
26787
- // of slightly higher main-thread usage, which is acceptable for the dashboard usage.
26788
- enableWorker: false,
26789
- progressive: true,
26790
- // Progressive download
26791
- // Adaptive bitrate settings (if multi-quality available)
26792
- abrEwmaFastLive: 3,
26793
- abrEwmaSlowLive: 9,
26794
- abrBandWidthFactor: 0.95,
26795
- abrBandWidthUpFactor: 0.7,
26796
- abrMaxWithRealBitrate: false,
26797
- // Request options optimized for large segments
26798
- requestOptions: {
26799
- timeout: 9e4,
26800
- // 90s timeout for 40MB segments
26801
- maxRetry: 5
26802
- // More retries for reliability
26803
- }
26804
- },
26805
- nativeVideoTracks: false,
26806
- nativeAudioTracks: false,
26807
- nativeTextTracks: false
26808
- },
26809
- // Improved seeking and scrubbing
26810
- inactivityTimeout: 3e3,
26811
- // Better error handling
26812
- errorDisplay: false,
26813
- // We'll handle errors with callbacks
26814
- // Fullscreen options
26815
- fullscreen: {
26816
- options: {
26817
- navigationUI: "hide"
26818
- }
26819
- },
26820
- ...options
26821
- };
26822
- const initializePlayer = React23.useCallback(() => {
26823
- if (!videoRef.current || playerRef.current) return;
26824
- const videoElement = document.createElement("video-js");
26825
- videoElement.className = "vjs-default-skin";
26826
- videoRef.current.appendChild(videoElement);
26827
- const player = videojs__default.default(videoElement, defaultOptions);
26828
- playerRef.current = player;
26829
- player.ready(() => {
26830
- setIsReady(true);
26831
- onReady?.(player);
26832
- });
26833
- player.on("play", () => onPlay?.(player));
26834
- player.on("pause", () => onPause?.(player));
26835
- player.on("playing", () => onPlaying?.(player));
26836
- player.on("timeupdate", () => {
26837
- const currentTime2 = player.currentTime() || 0;
26838
- onTimeUpdate?.(player, currentTime2);
26839
- });
26840
- player.on("durationchange", () => {
26841
- const duration2 = player.duration() || 0;
26842
- onDurationChange?.(player, duration2);
26843
- });
26844
- player.on("ended", () => onEnded?.(player));
26845
- player.on("loadstart", () => {
26569
+ const handleDurationChange = () => {
26570
+ const duration2 = video.duration || 0;
26571
+ eventCallbacksRef.current.onDurationChange?.(player, duration2);
26572
+ };
26573
+ const handleEnded = () => eventCallbacksRef.current.onEnded?.(player);
26574
+ const handleLoadStart = () => {
26846
26575
  setIsLoading(true);
26847
- onLoadingChange?.(true);
26848
- onLoadStart?.(player);
26849
- });
26850
- player.on("loadeddata", () => {
26576
+ eventCallbacksRef.current.onLoadingChange?.(true);
26577
+ eventCallbacksRef.current.onLoadStart?.(player);
26578
+ };
26579
+ const handleLoadedMetadata = () => eventCallbacksRef.current.onLoadedMetadata?.(player);
26580
+ const handleLoadedData = () => {
26851
26581
  setIsLoading(false);
26852
- onLoadingChange?.(false);
26853
- onLoadedData?.(player);
26854
- });
26855
- player.on("waiting", () => {
26582
+ eventCallbacksRef.current.onLoadingChange?.(false);
26583
+ eventCallbacksRef.current.onLoadedData?.(player);
26584
+ };
26585
+ const handleWaiting = () => {
26856
26586
  setIsLoading(true);
26857
- onLoadingChange?.(true);
26858
- });
26859
- player.on("playing", () => {
26860
- setIsLoading(false);
26861
- onLoadingChange?.(false);
26862
- });
26863
- player.on("loadedmetadata", () => {
26864
- onLoadedMetadata?.(player);
26865
- });
26866
- player.on("seeking", () => onSeeking?.(player));
26867
- player.on("seeked", () => onSeeked?.(player));
26868
- player.on("error", () => {
26869
- const error = player.error();
26870
- const errorCode = error?.code ?? 0;
26871
- const errorInfo = ERROR_MAPPING[errorCode] || {
26872
- code: errorCode || 0,
26873
- type: "non_recoverable" /* NON_RECOVERABLE */,
26874
- message: "Unknown playback error occurred",
26875
- canRetry: false
26876
- };
26877
- console.error("[VideoPlayer] Video.js error:", {
26878
- code: errorCode,
26879
- type: errorInfo.type,
26880
- message: errorInfo.message,
26881
- canRetry: errorInfo.canRetry,
26882
- originalError: error
26883
- });
26884
- onError?.(player, errorInfo);
26885
- });
26886
- if (src) {
26887
- const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
26888
- let videoSrc = src;
26889
- let blobUrl = null;
26890
- if (src.startsWith("#EXTM3U")) {
26891
- const safariMode = isSafari();
26892
- const browserName = getBrowserName();
26893
- if (safariMode) {
26894
- const clipIdMatch = src.match(/# Clip ID: ([a-f0-9-]+)/i);
26895
- if (clipIdMatch) {
26896
- videoSrc = `/api/clips/playlist/${clipIdMatch[1]}`;
26897
- console.log(`[VideoPlayer] Safari detected (${browserName}) - using playlist proxy URL:`, videoSrc);
26898
- } else {
26899
- console.warn("[VideoPlayer] Safari detected but no clip ID found in playlist, trying blob URL fallback");
26900
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
26901
- blobUrl = URL.createObjectURL(blob);
26902
- videoSrc = blobUrl;
26903
- player._blobUrl = videoSrc;
26904
- }
26905
- } else {
26906
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
26907
- blobUrl = URL.createObjectURL(blob);
26908
- videoSrc = blobUrl;
26909
- console.log(`[VideoPlayer] Non-Safari browser (${browserName}) - using blob URL for optimal performance`);
26910
- player._blobUrl = videoSrc;
26911
- }
26587
+ eventCallbacksRef.current.onLoadingChange?.(true);
26588
+ };
26589
+ const handleSeeking = () => eventCallbacksRef.current.onSeeking?.(player);
26590
+ const handleSeeked = () => eventCallbacksRef.current.onSeeked?.(player);
26591
+ const handleError = () => {
26592
+ const error = video.error;
26593
+ if (error) {
26594
+ const errorInfo = {
26595
+ code: error.code,
26596
+ type: error.code <= 2 ? "recoverable" : "non_recoverable",
26597
+ message: error.message || "Unknown error occurred",
26598
+ canRetry: error.code <= 2
26599
+ };
26600
+ eventCallbacksRef.current.onError?.(player, errorInfo);
26912
26601
  }
26913
- player.src({
26914
- src: videoSrc,
26915
- type: isHLS ? "application/x-mpegURL" : "video/mp4"
26916
- });
26917
- }
26602
+ };
26603
+ video.addEventListener("canplay", handleCanPlay);
26604
+ video.addEventListener("play", handlePlay);
26605
+ video.addEventListener("pause", handlePause);
26606
+ video.addEventListener("playing", handlePlaying);
26607
+ video.addEventListener("timeupdate", handleTimeUpdate);
26608
+ video.addEventListener("durationchange", handleDurationChange);
26609
+ video.addEventListener("ended", handleEnded);
26610
+ video.addEventListener("loadstart", handleLoadStart);
26611
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
26612
+ video.addEventListener("loadeddata", handleLoadedData);
26613
+ video.addEventListener("waiting", handleWaiting);
26614
+ video.addEventListener("seeking", handleSeeking);
26615
+ video.addEventListener("seeked", handleSeeked);
26616
+ video.addEventListener("error", handleError);
26617
+ return () => {
26618
+ video.removeEventListener("canplay", handleCanPlay);
26619
+ video.removeEventListener("play", handlePlay);
26620
+ video.removeEventListener("pause", handlePause);
26621
+ video.removeEventListener("playing", handlePlaying);
26622
+ video.removeEventListener("timeupdate", handleTimeUpdate);
26623
+ video.removeEventListener("durationchange", handleDurationChange);
26624
+ video.removeEventListener("ended", handleEnded);
26625
+ video.removeEventListener("loadstart", handleLoadStart);
26626
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
26627
+ video.removeEventListener("loadeddata", handleLoadedData);
26628
+ video.removeEventListener("waiting", handleWaiting);
26629
+ video.removeEventListener("seeking", handleSeeking);
26630
+ video.removeEventListener("seeked", handleSeeked);
26631
+ video.removeEventListener("error", handleError);
26632
+ };
26918
26633
  }, [
26919
26634
  src,
26920
- defaultOptions,
26921
- onReady,
26922
- onPlay,
26923
- onPause,
26924
- onTimeUpdate,
26925
- onDurationChange,
26926
- onEnded,
26927
- onError,
26928
- onLoadStart,
26929
- onLoadedMetadata,
26930
- onSeeking,
26931
- onSeeked
26635
+ cleanupBlobUrl,
26636
+ playerLikeObject,
26637
+ configVersion
26932
26638
  ]);
26933
26639
  React23.useEffect(() => {
26934
- if (playerRef.current && src) {
26935
- const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
26936
- let videoSrc = src;
26937
- let blobUrl = null;
26938
- if (src.startsWith("#EXTM3U")) {
26939
- const safariMode = isSafari();
26940
- const browserName = getBrowserName();
26941
- if (safariMode) {
26942
- const clipIdMatch = src.match(/# Clip ID: ([a-f0-9-]+)/i);
26943
- if (clipIdMatch) {
26944
- videoSrc = `/api/clips/playlist/${clipIdMatch[1]}`;
26945
- console.log(`[VideoPlayer] Safari detected (${browserName}) - using playlist proxy URL for source update:`, videoSrc);
26946
- } else {
26947
- console.warn("[VideoPlayer] Safari detected but no clip ID found in playlist (source update), trying blob URL fallback");
26948
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
26949
- blobUrl = URL.createObjectURL(blob);
26950
- videoSrc = blobUrl;
26951
- }
26952
- } else {
26953
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
26954
- blobUrl = URL.createObjectURL(blob);
26955
- videoSrc = blobUrl;
26956
- console.log(`[VideoPlayer] Non-Safari browser (${browserName}) - using blob URL for source update`);
26957
- }
26958
- }
26959
- playerRef.current.src({
26960
- src: videoSrc,
26961
- type: isHLS ? "application/x-mpegURL" : "video/mp4"
26962
- });
26963
- return () => {
26964
- if (blobUrl) {
26965
- URL.revokeObjectURL(blobUrl);
26966
- }
26967
- };
26968
- }
26969
- }, [src]);
26970
- React23.useEffect(() => {
26971
- initializePlayer();
26640
+ const cleanup = initializePlayer();
26972
26641
  return () => {
26973
- if (playerRef.current) {
26974
- const blobUrl = playerRef.current._blobUrl;
26975
- if (blobUrl) {
26976
- URL.revokeObjectURL(blobUrl);
26977
- }
26978
- playerRef.current.dispose();
26979
- playerRef.current = null;
26980
- setIsReady(false);
26642
+ cleanup?.();
26643
+ if (hlsRef.current) {
26644
+ hlsRef.current.destroy();
26645
+ hlsRef.current = null;
26981
26646
  }
26647
+ cleanupBlobUrl();
26648
+ setIsReady(false);
26982
26649
  };
26983
- }, []);
26650
+ }, [src, initializePlayer, cleanupBlobUrl]);
26651
+ React23.useEffect(() => {
26652
+ if (videoRef.current) {
26653
+ if (autoplay) {
26654
+ videoRef.current.play().catch((err) => {
26655
+ console.warn("[HlsVideoPlayer] Autoplay failed:", err);
26656
+ });
26657
+ }
26658
+ }
26659
+ }, [autoplay]);
26984
26660
  const play = React23.useCallback(() => {
26985
- return playerRef.current?.play();
26661
+ return videoRef.current?.play();
26986
26662
  }, []);
26987
26663
  const pause = React23.useCallback(() => {
26988
- playerRef.current?.pause();
26664
+ videoRef.current?.pause();
26989
26665
  }, []);
26990
26666
  const currentTime = React23.useCallback((time2) => {
26991
- if (time2 !== void 0) {
26992
- playerRef.current?.currentTime(time2);
26667
+ if (time2 !== void 0 && videoRef.current) {
26668
+ videoRef.current.currentTime = time2;
26993
26669
  return time2;
26994
26670
  }
26995
- return playerRef.current?.currentTime() || 0;
26671
+ return videoRef.current?.currentTime || 0;
26996
26672
  }, []);
26997
26673
  const duration = React23.useCallback(() => {
26998
- return playerRef.current?.duration() || 0;
26674
+ return videoRef.current?.duration || 0;
26999
26675
  }, []);
27000
26676
  const paused = React23.useCallback(() => {
27001
- return playerRef.current?.paused() ?? true;
26677
+ return videoRef.current?.paused ?? true;
27002
26678
  }, []);
27003
26679
  const mute = React23.useCallback((isMuted) => {
27004
- if (isMuted !== void 0) {
27005
- playerRef.current?.muted(isMuted);
26680
+ if (isMuted !== void 0 && videoRef.current) {
26681
+ videoRef.current.muted = isMuted;
27006
26682
  return isMuted;
27007
26683
  }
27008
- return playerRef.current?.muted() ?? false;
26684
+ return videoRef.current?.muted ?? false;
27009
26685
  }, []);
27010
26686
  const volume = React23.useCallback((level) => {
27011
- if (level !== void 0) {
27012
- playerRef.current?.volume(level);
26687
+ if (level !== void 0 && videoRef.current) {
26688
+ videoRef.current.volume = level;
27013
26689
  return level;
27014
26690
  }
27015
- return playerRef.current?.volume() ?? 1;
26691
+ return videoRef.current?.volume ?? 1;
27016
26692
  }, []);
27017
- const dispose = React23.useCallback(() => {
27018
- if (playerRef.current) {
27019
- playerRef.current.dispose();
27020
- playerRef.current = null;
27021
- setIsReady(false);
26693
+ const playbackRate = React23.useCallback((rate) => {
26694
+ if (rate !== void 0 && videoRef.current) {
26695
+ videoRef.current.playbackRate = rate;
26696
+ return rate;
27022
26697
  }
26698
+ return videoRef.current?.playbackRate ?? 1;
27023
26699
  }, []);
27024
- React23__namespace.default.useImperativeHandle(ref, () => ({
27025
- player: playerRef.current,
26700
+ React23.useImperativeHandle(ref, () => ({
26701
+ hls: hlsRef.current,
26702
+ video: videoRef.current,
27026
26703
  play,
27027
26704
  pause,
27028
26705
  currentTime,
@@ -27030,13 +26707,15 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
27030
26707
  paused,
27031
26708
  mute,
27032
26709
  volume,
26710
+ playbackRate,
27033
26711
  dispose,
27034
- isReady
27035
- }));
26712
+ isReady,
26713
+ // For backward compatibility with Video.js API
26714
+ player: playerLikeObject()
26715
+ }), [play, pause, currentTime, duration, paused, mute, volume, playbackRate, dispose, isReady, playerLikeObject]);
27036
26716
  const handleClickWithIndicator = React23.useCallback(() => {
27037
- if (!onClick || !playerRef.current) return;
27038
- const player = playerRef.current;
27039
- const willBePlaying = player.paused();
26717
+ if (!onClick || !videoRef.current) return;
26718
+ const willBePlaying = videoRef.current.paused;
27040
26719
  setIndicatorIsPlaying(willBePlaying);
27041
26720
  setShowIndicator(false);
27042
26721
  setTimeout(() => {
@@ -27045,17 +26724,30 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
27045
26724
  }, 0);
27046
26725
  onClick();
27047
26726
  }, [onClick]);
27048
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `video-player-wrapper ${className}`, style: { position: "relative", width: "100%", height: "100%" }, children: [
26727
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `hls-video-player-wrapper ${className}`, style: { position: "relative", width: "100%", height: "100%" }, children: [
27049
26728
  /* @__PURE__ */ jsxRuntime.jsx(
27050
26729
  "div",
27051
26730
  {
27052
- className: "video-player-container",
27053
- ref: videoRef,
27054
- "data-vjs-player": true
26731
+ className: "hls-video-player-container",
26732
+ ref: videoContainerRef,
26733
+ children: /* @__PURE__ */ jsxRuntime.jsx(
26734
+ "video",
26735
+ {
26736
+ ref: videoRef,
26737
+ className: "hls-video-element",
26738
+ poster,
26739
+ controls,
26740
+ loop,
26741
+ muted,
26742
+ playsInline,
26743
+ autoPlay: autoplay,
26744
+ preload: "metadata"
26745
+ }
26746
+ )
27055
26747
  }
27056
26748
  ),
27057
- isLoading && !externalLoadingControl && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "video-player-loading", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27058
- onClick && /* @__PURE__ */ jsxRuntime.jsx(
26749
+ isLoading && !externalLoadingControl && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
26750
+ onClick && !controls && /* @__PURE__ */ jsxRuntime.jsx(
27059
26751
  "div",
27060
26752
  {
27061
26753
  onClick: handleClickWithIndicator,
@@ -27071,7 +26763,7 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
27071
26763
  "aria-label": "Click to play/pause"
27072
26764
  }
27073
26765
  ),
27074
- onClick && /* @__PURE__ */ jsxRuntime.jsx(
26766
+ onClick && !controls && /* @__PURE__ */ jsxRuntime.jsx(
27075
26767
  PlayPauseIndicator,
27076
26768
  {
27077
26769
  show: showIndicator,
@@ -27081,13 +26773,25 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
27081
26773
  )
27082
26774
  ] });
27083
26775
  });
27084
- VideoPlayer.displayName = "VideoPlayer";
27085
- var CroppedVideoPlayer = React23.forwardRef(({
26776
+ HlsVideoPlayer.displayName = "HlsVideoPlayer";
26777
+ var VideoPlayer = HlsVideoPlayer;
26778
+ var CroppedHlsVideoPlayer = React23.forwardRef(({
27086
26779
  crop,
27087
26780
  debug = false,
27088
26781
  onClick,
27089
26782
  ...videoProps
27090
26783
  }, ref) => {
26784
+ const {
26785
+ onReady: onReadyProp,
26786
+ onPlay: onPlayProp,
26787
+ onPause: onPauseProp,
26788
+ onEnded: onEndedProp,
26789
+ onSeeking: onSeekingProp,
26790
+ onSeeked: onSeekedProp,
26791
+ onLoadedMetadata: onLoadedMetadataProp,
26792
+ className: inheritedClassName = ""
26793
+ } = videoProps;
26794
+ const videoSrc = videoProps.src;
27091
26795
  const videoContainerRef = React23.useRef(null);
27092
26796
  const hiddenVideoRef = React23.useRef(null);
27093
26797
  const canvasRef = React23.useRef(null);
@@ -27106,8 +26810,11 @@ var CroppedVideoPlayer = React23.forwardRef(({
27106
26810
  }
27107
26811
  }, []);
27108
26812
  React23.useImperativeHandle(ref, () => ({
27109
- get player() {
27110
- return hiddenVideoRef.current?.player || null;
26813
+ get hls() {
26814
+ return hiddenVideoRef.current?.hls || null;
26815
+ },
26816
+ get video() {
26817
+ return hiddenVideoRef.current?.video || null;
27111
26818
  },
27112
26819
  play: () => hiddenVideoRef.current?.play() || void 0,
27113
26820
  pause: () => hiddenVideoRef.current?.pause(),
@@ -27121,12 +26828,36 @@ var CroppedVideoPlayer = React23.forwardRef(({
27121
26828
  paused: () => hiddenVideoRef.current?.paused() || true,
27122
26829
  mute: (isMuted) => hiddenVideoRef.current?.mute(isMuted) || false,
27123
26830
  volume: (level) => hiddenVideoRef.current?.volume(level) || 0,
26831
+ playbackRate: (rate) => hiddenVideoRef.current?.playbackRate(rate) || 1,
27124
26832
  dispose: () => {
27125
26833
  hiddenVideoRef.current?.dispose();
27126
26834
  stopCanvasRendering();
27127
26835
  },
27128
26836
  get isReady() {
27129
26837
  return hiddenVideoRef.current?.isReady || false;
26838
+ },
26839
+ // For backward compatibility with Video.js API
26840
+ get player() {
26841
+ const video = hiddenVideoRef.current?.video;
26842
+ if (!video) return null;
26843
+ return {
26844
+ el: () => video,
26845
+ currentTime: () => video.currentTime || 0,
26846
+ duration: () => video.duration || 0,
26847
+ paused: () => video.paused ?? true,
26848
+ play: () => video.play(),
26849
+ pause: () => video.pause(),
26850
+ muted: (val) => {
26851
+ if (val !== void 0) video.muted = val;
26852
+ return video.muted;
26853
+ },
26854
+ volume: (val) => {
26855
+ if (val !== void 0) video.volume = val;
26856
+ return video.volume;
26857
+ },
26858
+ error: () => null,
26859
+ dispose: () => hiddenVideoRef.current?.dispose()
26860
+ };
27130
26861
  }
27131
26862
  }), [stopCanvasRendering]);
27132
26863
  const calculateCanvasDimensions = React23.useCallback(() => {
@@ -27186,24 +26917,38 @@ var CroppedVideoPlayer = React23.forwardRef(({
27186
26917
  animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
27187
26918
  }, [crop]);
27188
26919
  const handleVideoReady = React23.useCallback((player) => {
27189
- console.log("[CroppedVideoPlayer] Video player ready");
27190
- const videoEl = player.el().querySelector("video");
26920
+ console.log("[CroppedHlsVideoPlayer] Video player ready");
26921
+ const videoEl = hiddenVideoRef.current?.video;
27191
26922
  if (videoEl) {
27192
26923
  videoElementRef.current = videoEl;
27193
26924
  setIsVideoReady(true);
27194
26925
  }
27195
- videoProps.onReady?.(player);
27196
- }, [videoProps]);
26926
+ onReadyProp?.(player);
26927
+ }, [onReadyProp]);
27197
26928
  const handleVideoPlay = React23.useCallback((player) => {
27198
- console.log("[CroppedVideoPlayer] Video playing, starting canvas rendering");
26929
+ console.log("[CroppedHlsVideoPlayer] Video playing, starting canvas rendering");
27199
26930
  if (crop && canvasRef.current) {
27200
26931
  setIsProcessing(true);
27201
26932
  renderFrameToCanvas();
27202
26933
  }
27203
- videoProps.onPlay?.(player);
27204
- }, [crop, renderFrameToCanvas, videoProps]);
26934
+ onPlayProp?.(player);
26935
+ }, [crop, renderFrameToCanvas, onPlayProp]);
27205
26936
  const handleVideoPause = React23.useCallback((player) => {
27206
- console.log("[CroppedVideoPlayer] Video paused, stopping canvas rendering and CLEARING canvas");
26937
+ console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering and CLEARING canvas");
26938
+ stopCanvasRendering();
26939
+ setIsProcessing(false);
26940
+ if (canvasRef.current) {
26941
+ const ctx = canvasRef.current.getContext("2d");
26942
+ if (ctx) {
26943
+ ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26944
+ ctx.fillStyle = "black";
26945
+ ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26946
+ }
26947
+ }
26948
+ onPauseProp?.(player);
26949
+ }, [stopCanvasRendering, onPauseProp]);
26950
+ const handleVideoEnded = React23.useCallback((player) => {
26951
+ console.log("[CroppedHlsVideoPlayer] Video ended, CLEARING canvas");
27207
26952
  stopCanvasRendering();
27208
26953
  setIsProcessing(false);
27209
26954
  if (canvasRef.current) {
@@ -27214,159 +26959,677 @@ var CroppedVideoPlayer = React23.forwardRef(({
27214
26959
  ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
27215
26960
  }
27216
26961
  }
27217
- videoProps.onPause?.(player);
27218
- }, [stopCanvasRendering, videoProps]);
27219
- const handleVideoEnded = React23.useCallback((player) => {
27220
- console.log("[CroppedVideoPlayer] Video ended, CLEARING canvas");
27221
- stopCanvasRendering();
27222
- setIsProcessing(false);
27223
- if (canvasRef.current) {
27224
- const ctx = canvasRef.current.getContext("2d");
27225
- if (ctx) {
27226
- ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
27227
- ctx.fillStyle = "black";
27228
- ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26962
+ onEndedProp?.(player);
26963
+ }, [stopCanvasRendering, onEndedProp]);
26964
+ const handleSeeking = React23.useCallback((player) => {
26965
+ console.log("[CroppedHlsVideoPlayer] Video seeking");
26966
+ if (crop && !videoElementRef.current?.paused) {
26967
+ renderFrameToCanvas();
26968
+ }
26969
+ onSeekingProp?.(player);
26970
+ }, [crop, renderFrameToCanvas, onSeekingProp]);
26971
+ const handleSeeked = React23.useCallback((player) => {
26972
+ console.log("[CroppedHlsVideoPlayer] Video seeked");
26973
+ if (crop && !videoElementRef.current?.paused) {
26974
+ renderFrameToCanvas();
26975
+ }
26976
+ onSeekedProp?.(player);
26977
+ }, [crop, renderFrameToCanvas, onSeekedProp]);
26978
+ const handleLoadedMetadata = React23.useCallback((player) => {
26979
+ console.log("[CroppedHlsVideoPlayer] Video metadata loaded");
26980
+ calculateCanvasDimensions();
26981
+ onLoadedMetadataProp?.(player);
26982
+ }, [calculateCanvasDimensions, onLoadedMetadataProp]);
26983
+ React23.useEffect(() => {
26984
+ calculateCanvasDimensions();
26985
+ const handleResize = () => {
26986
+ calculateCanvasDimensions();
26987
+ };
26988
+ window.addEventListener("resize", handleResize);
26989
+ return () => {
26990
+ window.removeEventListener("resize", handleResize);
26991
+ };
26992
+ }, [calculateCanvasDimensions]);
26993
+ React23.useLayoutEffect(() => {
26994
+ if (canvasRef.current && crop) {
26995
+ const canvas = canvasRef.current;
26996
+ const ctx = canvas.getContext("2d");
26997
+ if (ctx) {
26998
+ console.log("[CroppedHlsVideoPlayer] Source changing - CLEARING CANVAS IMMEDIATELY");
26999
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
27000
+ ctx.fillStyle = "black";
27001
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
27002
+ }
27003
+ }
27004
+ }, [videoSrc, crop]);
27005
+ React23.useEffect(() => {
27006
+ return () => {
27007
+ stopCanvasRendering();
27008
+ };
27009
+ }, [stopCanvasRendering]);
27010
+ if (!crop) {
27011
+ return /* @__PURE__ */ jsxRuntime.jsx(HlsVideoPlayer, { ref, ...videoProps, onClick });
27012
+ }
27013
+ const handleClickWithIndicator = () => {
27014
+ if (!onClick || !hiddenVideoRef.current?.video) return;
27015
+ const video = hiddenVideoRef.current.video;
27016
+ const willBePlaying = video.paused;
27017
+ setIndicatorIsPlaying(willBePlaying);
27018
+ setShowIndicator(false);
27019
+ setTimeout(() => {
27020
+ indicatorKeyRef.current += 1;
27021
+ setShowIndicator(true);
27022
+ }, 0);
27023
+ onClick();
27024
+ };
27025
+ return /* @__PURE__ */ jsxRuntime.jsxs(
27026
+ "div",
27027
+ {
27028
+ ref: videoContainerRef,
27029
+ className: `relative w-full h-full flex items-center justify-center bg-black ${onClick ? "cursor-pointer" : ""} ${inheritedClassName}`,
27030
+ onClick: handleClickWithIndicator,
27031
+ children: [
27032
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
27033
+ HlsVideoPlayer,
27034
+ {
27035
+ ref: hiddenVideoRef,
27036
+ ...videoProps,
27037
+ onReady: handleVideoReady,
27038
+ onPlay: handleVideoPlay,
27039
+ onPause: handleVideoPause,
27040
+ onEnded: handleVideoEnded,
27041
+ onSeeking: handleSeeking,
27042
+ onSeeked: handleSeeked,
27043
+ onLoadedMetadata: handleLoadedMetadata,
27044
+ onLoadedData: videoProps.onLoadedData,
27045
+ onPlaying: videoProps.onPlaying,
27046
+ onLoadingChange: videoProps.onLoadingChange
27047
+ }
27048
+ ) }),
27049
+ /* @__PURE__ */ jsxRuntime.jsx(
27050
+ "canvas",
27051
+ {
27052
+ ref: canvasRef,
27053
+ width: canvasDimensions.width,
27054
+ height: canvasDimensions.height,
27055
+ className: "max-w-full max-h-full",
27056
+ style: {
27057
+ display: isVideoReady ? "block" : "none",
27058
+ width: `${canvasDimensions.width}px`,
27059
+ height: `${canvasDimensions.height}px`
27060
+ }
27061
+ }
27062
+ ),
27063
+ !isVideoReady && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27064
+ debug && isVideoReady && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono", children: [
27065
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
27066
+ "Crop: ",
27067
+ crop.x,
27068
+ ",",
27069
+ crop.y,
27070
+ " ",
27071
+ crop.width,
27072
+ "x",
27073
+ crop.height,
27074
+ "%"
27075
+ ] }),
27076
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
27077
+ "Canvas: ",
27078
+ canvasDimensions.width,
27079
+ "x",
27080
+ canvasDimensions.height,
27081
+ "px"
27082
+ ] }),
27083
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
27084
+ "Processing: ",
27085
+ isProcessing ? "Yes" : "No"
27086
+ ] })
27087
+ ] }),
27088
+ onClick && /* @__PURE__ */ jsxRuntime.jsx(
27089
+ PlayPauseIndicator,
27090
+ {
27091
+ show: showIndicator,
27092
+ isPlaying: indicatorIsPlaying
27093
+ },
27094
+ indicatorKeyRef.current
27095
+ )
27096
+ ]
27097
+ }
27098
+ );
27099
+ });
27100
+ CroppedHlsVideoPlayer.displayName = "CroppedHlsVideoPlayer";
27101
+ var CroppedVideoPlayer = CroppedHlsVideoPlayer;
27102
+ var getSupabaseClient2 = () => {
27103
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
27104
+ const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
27105
+ if (!url || !key) {
27106
+ throw new Error("Supabase configuration missing");
27107
+ }
27108
+ return supabaseJs.createClient(url, key);
27109
+ };
27110
+ var getAuthToken3 = async () => {
27111
+ try {
27112
+ const supabase = getSupabaseClient2();
27113
+ const { data: { session } } = await supabase.auth.getSession();
27114
+ return session?.access_token || null;
27115
+ } catch (error) {
27116
+ console.error("[useWorkspaceCrop] Error getting auth token:", error);
27117
+ return null;
27118
+ }
27119
+ };
27120
+ function useWorkspaceCrop(workspaceId) {
27121
+ const [crop, setCrop] = React23.useState(null);
27122
+ const [isLoading, setIsLoading] = React23.useState(true);
27123
+ const [error, setError] = React23.useState(null);
27124
+ React23.useEffect(() => {
27125
+ if (!workspaceId) {
27126
+ setIsLoading(false);
27127
+ return;
27128
+ }
27129
+ const fetchCrop = async () => {
27130
+ setIsLoading(true);
27131
+ setError(null);
27132
+ try {
27133
+ const token = await getAuthToken3();
27134
+ if (!token) {
27135
+ throw new Error("Authentication required");
27136
+ }
27137
+ const response = await fetch("/api/clips/supabase", {
27138
+ method: "POST",
27139
+ headers: {
27140
+ "Content-Type": "application/json",
27141
+ "Authorization": `Bearer ${token}`
27142
+ },
27143
+ body: JSON.stringify({
27144
+ action: "crop",
27145
+ workspaceId
27146
+ })
27147
+ });
27148
+ if (!response.ok) {
27149
+ throw new Error(`Failed to fetch crop: ${response.statusText}`);
27150
+ }
27151
+ const data = await response.json();
27152
+ console.log(`[useWorkspaceCrop] Fetched crop for workspace ${workspaceId}:`, data.crop);
27153
+ setCrop(data.crop);
27154
+ } catch (err) {
27155
+ console.error("[useWorkspaceCrop] Error fetching crop:", err);
27156
+ setError(err instanceof Error ? err.message : "Failed to fetch crop configuration");
27157
+ setCrop(null);
27158
+ } finally {
27159
+ setIsLoading(false);
27160
+ }
27161
+ };
27162
+ fetchCrop();
27163
+ }, [workspaceId]);
27164
+ return { crop, isLoading, error };
27165
+ }
27166
+ function Skeleton({ className, ...props }) {
27167
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("animate-pulse rounded-md bg-muted", className), ...props });
27168
+ }
27169
+ var Select = SelectPrimitive__namespace.Root;
27170
+ var SelectGroup = SelectPrimitive__namespace.Group;
27171
+ var SelectValue = SelectPrimitive__namespace.Value;
27172
+ var SelectTrigger = React23__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
27173
+ SelectPrimitive__namespace.Trigger,
27174
+ {
27175
+ ref,
27176
+ className: cn(
27177
+ "flex h-11 sm:h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 touch-manipulation",
27178
+ className
27179
+ ),
27180
+ ...props,
27181
+ children: [
27182
+ children,
27183
+ /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Icon, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" }) })
27184
+ ]
27185
+ }
27186
+ ));
27187
+ SelectTrigger.displayName = SelectPrimitive__namespace.Trigger.displayName;
27188
+ var SelectScrollUpButton = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
27189
+ SelectPrimitive__namespace.ScrollUpButton,
27190
+ {
27191
+ ref,
27192
+ className: cn("flex cursor-default items-center justify-center py-1", className),
27193
+ ...props,
27194
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-4 w-4" })
27195
+ }
27196
+ ));
27197
+ SelectScrollUpButton.displayName = SelectPrimitive__namespace.ScrollUpButton.displayName;
27198
+ var SelectScrollDownButton = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
27199
+ SelectPrimitive__namespace.ScrollDownButton,
27200
+ {
27201
+ ref,
27202
+ className: cn("flex cursor-default items-center justify-center py-1", className),
27203
+ ...props,
27204
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" })
27205
+ }
27206
+ ));
27207
+ SelectScrollDownButton.displayName = SelectPrimitive__namespace.ScrollDownButton.displayName;
27208
+ var SelectContent = React23__namespace.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
27209
+ SelectPrimitive__namespace.Content,
27210
+ {
27211
+ ref,
27212
+ className: cn(
27213
+ "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
27214
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
27215
+ className
27216
+ ),
27217
+ position,
27218
+ ...props,
27219
+ children: [
27220
+ /* @__PURE__ */ jsxRuntime.jsx(SelectScrollUpButton, {}),
27221
+ /* @__PURE__ */ jsxRuntime.jsx(
27222
+ SelectPrimitive__namespace.Viewport,
27223
+ {
27224
+ className: cn(
27225
+ "p-1",
27226
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
27227
+ ),
27228
+ children
27229
+ }
27230
+ ),
27231
+ /* @__PURE__ */ jsxRuntime.jsx(SelectScrollDownButton, {})
27232
+ ]
27233
+ }
27234
+ ) }));
27235
+ SelectContent.displayName = SelectPrimitive__namespace.Content.displayName;
27236
+ var SelectLabel = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
27237
+ SelectPrimitive__namespace.Label,
27238
+ {
27239
+ ref,
27240
+ className: cn("px-2 py-1.5 text-sm font-semibold", className),
27241
+ ...props
27242
+ }
27243
+ ));
27244
+ SelectLabel.displayName = SelectPrimitive__namespace.Label.displayName;
27245
+ var SelectItem = React23__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
27246
+ SelectPrimitive__namespace.Item,
27247
+ {
27248
+ ref,
27249
+ className: cn(
27250
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
27251
+ className
27252
+ ),
27253
+ ...props,
27254
+ children: [
27255
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemIndicator, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }) }),
27256
+ /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemText, { children })
27257
+ ]
27258
+ }
27259
+ ));
27260
+ SelectItem.displayName = SelectPrimitive__namespace.Item.displayName;
27261
+ var SelectSeparator = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
27262
+ SelectPrimitive__namespace.Separator,
27263
+ {
27264
+ ref,
27265
+ className: cn("-mx-1 my-1 h-px bg-muted", className),
27266
+ ...props
27267
+ }
27268
+ ));
27269
+ SelectSeparator.displayName = SelectPrimitive__namespace.Separator.displayName;
27270
+ var LoadingOverlay = ({
27271
+ isVisible,
27272
+ message = "Loading...",
27273
+ className
27274
+ }) => {
27275
+ if (!isVisible) return null;
27276
+ return /* @__PURE__ */ jsxRuntime.jsx(
27277
+ motion.div,
27278
+ {
27279
+ initial: { opacity: 0 },
27280
+ animate: { opacity: 1 },
27281
+ exit: { opacity: 0 },
27282
+ transition: { duration: 0.2 },
27283
+ className: `fixed inset-0 z-[100] flex items-center justify-center bg-black/30 backdrop-blur-sm ${className || ""}`,
27284
+ "aria-modal": "true",
27285
+ role: "dialog",
27286
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center space-y-3 rounded-lg bg-white p-8 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message }) })
27287
+ }
27288
+ );
27289
+ };
27290
+ var LoadingOverlay_default = LoadingOverlay;
27291
+ var TimeDisplay = ({ className, variant = "default" }) => {
27292
+ const { dateTimeConfig } = useDashboardConfig();
27293
+ const [time2, setTime] = React23.useState("");
27294
+ const dbTimezone = useAppTimezone();
27295
+ const timezoneToDisplay = dbTimezone || dateTimeConfig?.defaultTimezone || "UTC";
27296
+ const localeToUse = dateTimeConfig?.defaultLocale || "en-US";
27297
+ const timeSuffix = "";
27298
+ React23.useEffect(() => {
27299
+ const updateTime = () => {
27300
+ const now2 = /* @__PURE__ */ new Date();
27301
+ const effectiveFormatOptions = {
27302
+ hour: "2-digit",
27303
+ minute: "2-digit",
27304
+ second: "2-digit",
27305
+ hour12: true,
27306
+ timeZone: timezoneToDisplay,
27307
+ ...dateTimeConfig?.timeFormatOptions || {}
27308
+ // Allow override from config
27309
+ };
27310
+ try {
27311
+ setTime(new Intl.DateTimeFormat(localeToUse, effectiveFormatOptions).format(now2));
27312
+ } catch (e) {
27313
+ console.error("Error formatting time:", e);
27314
+ setTime("Error");
27315
+ }
27316
+ };
27317
+ updateTime();
27318
+ const interval = setInterval(updateTime, 1e3);
27319
+ return () => clearInterval(interval);
27320
+ }, [timezoneToDisplay, dateTimeConfig?.timeFormatOptions, localeToUse]);
27321
+ if (!time2) return null;
27322
+ if (variant === "minimal") {
27323
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: className || "", children: [
27324
+ time2,
27325
+ " ",
27326
+ timeSuffix
27327
+ ] });
27328
+ }
27329
+ return /* @__PURE__ */ jsxRuntime.jsxs(
27330
+ motion.div,
27331
+ {
27332
+ initial: { opacity: 0, y: -5 },
27333
+ animate: { opacity: 1, y: 0 },
27334
+ transition: { duration: 0.3 },
27335
+ className: `flex items-center space-x-1.5 bg-white/60 backdrop-blur-sm px-2 py-0.5 rounded-md shadow-xs ${className || ""}`,
27336
+ children: [
27337
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-3 w-3 text-[var(--primary-DEFAULT)]", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z", clipRule: "evenodd" }) }),
27338
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs sm:text-[11px] font-medium text-gray-800 tabular-nums tracking-tight", children: [
27339
+ time2,
27340
+ " ",
27341
+ timeSuffix
27342
+ ] })
27343
+ ]
27344
+ }
27345
+ );
27346
+ };
27347
+ var TimeDisplay_default = TimeDisplay;
27348
+ var DateDisplay = ({ className, variant = "default" }) => {
27349
+ const { dateTimeConfig } = useDashboardConfig();
27350
+ const [date, setDate] = React23.useState("");
27351
+ const timezoneToDisplay = dateTimeConfig?.defaultTimezone || "UTC";
27352
+ const localeToUse = dateTimeConfig?.defaultLocale || "en-US";
27353
+ React23.useEffect(() => {
27354
+ const getCurrentFormattedDate = () => {
27355
+ const now2 = /* @__PURE__ */ new Date();
27356
+ const effectiveFormatOptions = {
27357
+ weekday: "short",
27358
+ day: "numeric",
27359
+ month: "short",
27360
+ timeZone: timezoneToDisplay,
27361
+ ...dateTimeConfig?.dateFormatOptions || {}
27362
+ // Allow override from config
27363
+ };
27364
+ try {
27365
+ return new Intl.DateTimeFormat(localeToUse, effectiveFormatOptions).format(now2);
27366
+ } catch (e) {
27367
+ console.error("Error formatting date:", e);
27368
+ return "Error";
27369
+ }
27370
+ };
27371
+ const updateDate = () => {
27372
+ setDate(getCurrentFormattedDate());
27373
+ };
27374
+ updateDate();
27375
+ const interval = setInterval(() => {
27376
+ const currentDateStr = getCurrentFormattedDate();
27377
+ if (currentDateStr !== date) {
27378
+ updateDate();
27379
+ }
27380
+ }, 60 * 1e3);
27381
+ return () => clearInterval(interval);
27382
+ }, [date, timezoneToDisplay, dateTimeConfig?.dateFormatOptions, localeToUse]);
27383
+ if (!date) return null;
27384
+ if (variant === "minimal") {
27385
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: className || "", children: date });
27386
+ }
27387
+ return /* @__PURE__ */ jsxRuntime.jsxs(
27388
+ motion.div,
27389
+ {
27390
+ initial: { opacity: 0, y: -5 },
27391
+ animate: { opacity: 1, y: 0 },
27392
+ transition: { duration: 0.3 },
27393
+ className: `flex items-center space-x-1.5 bg-white/60 backdrop-blur-sm px-2 py-0.5 rounded-md shadow-xs ${className || ""}`,
27394
+ children: [
27395
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-3 w-3 text-blue-600", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", clipRule: "evenodd" }) }),
27396
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-gray-800 tracking-tight", children: date })
27397
+ ]
27398
+ }
27399
+ );
27400
+ };
27401
+ var DateDisplay_default = DateDisplay;
27402
+ var Card3 = Card2;
27403
+ var CardHeader3 = CardHeader2;
27404
+ var CardTitle3 = CardTitle2;
27405
+ var CardContent3 = CardContent2;
27406
+ var MetricCard2 = ({ title, value, unit = "", trend = null }) => {
27407
+ const getTrendColor = (trendValue) => {
27408
+ if (trendValue === null || trendValue === void 0) return "";
27409
+ return trendValue > 0 ? "text-green-500" : trendValue < 0 ? "text-red-500" : "text-gray-500";
27410
+ };
27411
+ const getTrendSymbol = (trendValue) => {
27412
+ if (trendValue === null || trendValue === void 0) return "";
27413
+ return trendValue > 0 ? "\u2191" : trendValue < 0 ? "\u2193" : "";
27414
+ };
27415
+ return /* @__PURE__ */ jsxRuntime.jsxs(Card3, { className: "bg-white", children: [
27416
+ /* @__PURE__ */ jsxRuntime.jsx(CardHeader3, { className: "pb-2", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle3, { className: "text-sm font-medium text-gray-500", children: title }) }),
27417
+ /* @__PURE__ */ jsxRuntime.jsx(CardContent3, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between", children: [
27418
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl font-semibold", children: [
27419
+ value,
27420
+ unit && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-sm text-gray-500", children: unit })
27421
+ ] }),
27422
+ trend !== null && trend !== void 0 && // Check trend for null/undefined before accessing
27423
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex items-center ${getTrendColor(trend)}`, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm", children: [
27424
+ getTrendSymbol(trend),
27425
+ " ",
27426
+ Math.abs(trend),
27427
+ "%"
27428
+ ] }) })
27429
+ ] }) })
27430
+ ] });
27431
+ };
27432
+ var MetricCard_default = MetricCard2;
27433
+ var TimePickerDropdown = ({
27434
+ value,
27435
+ onChange,
27436
+ placeholder = "Select time",
27437
+ className = "",
27438
+ disabled = false
27439
+ }) => {
27440
+ const [isOpen, setIsOpen] = React23.useState(false);
27441
+ const [searchTerm, setSearchTerm] = React23.useState("");
27442
+ const dropdownRef = React23.useRef(null);
27443
+ const inputRef = React23.useRef(null);
27444
+ const generateTimeSlots = () => {
27445
+ const slots = [];
27446
+ for (let hour = 0; hour < 24; hour++) {
27447
+ for (let minute = 0; minute < 60; minute += 15) {
27448
+ const time24 = `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
27449
+ const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
27450
+ const ampm = hour < 12 ? "AM" : "PM";
27451
+ const time12 = `${hour12}:${minute.toString().padStart(2, "0")} ${ampm}`;
27452
+ slots.push({ value: time24, label: time12 });
27453
+ }
27454
+ }
27455
+ return slots;
27456
+ };
27457
+ const timeSlots = generateTimeSlots();
27458
+ const filteredSlots = timeSlots.filter(
27459
+ (slot) => slot.label.toLowerCase().includes(searchTerm.toLowerCase())
27460
+ );
27461
+ const getDisplayValue = (value2) => {
27462
+ if (!value2) return "";
27463
+ const normalizedValue = value2.substring(0, 5);
27464
+ const slot = timeSlots.find((s) => s.value === normalizedValue);
27465
+ return slot ? slot.label : value2;
27466
+ };
27467
+ React23.useEffect(() => {
27468
+ const handleClickOutside = (event) => {
27469
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
27470
+ setIsOpen(false);
27471
+ setSearchTerm("");
27472
+ }
27473
+ };
27474
+ document.addEventListener("mousedown", handleClickOutside);
27475
+ return () => document.removeEventListener("mousedown", handleClickOutside);
27476
+ }, []);
27477
+ const handleKeyDown = (e) => {
27478
+ if (e.key === "Escape") {
27479
+ setIsOpen(false);
27480
+ setSearchTerm("");
27481
+ } else if (e.key === "Enter") {
27482
+ e.preventDefault();
27483
+ if (filteredSlots.length > 0) {
27484
+ onChange(filteredSlots[0].value);
27485
+ setIsOpen(false);
27486
+ setSearchTerm("");
27229
27487
  }
27230
27488
  }
27231
- videoProps.onEnded?.(player);
27232
- }, [stopCanvasRendering, videoProps]);
27233
- const handleSeeking = React23.useCallback((player) => {
27234
- console.log("[CroppedVideoPlayer] Video seeking");
27235
- if (crop && !player.paused()) {
27236
- renderFrameToCanvas();
27237
- }
27238
- videoProps.onSeeking?.(player);
27239
- }, [crop, renderFrameToCanvas, videoProps]);
27240
- const handleSeeked = React23.useCallback((player) => {
27241
- console.log("[CroppedVideoPlayer] Video seeked");
27242
- if (crop && !player.paused()) {
27243
- renderFrameToCanvas();
27489
+ };
27490
+ const handleSelect = (timeValue) => {
27491
+ onChange(timeValue);
27492
+ setIsOpen(false);
27493
+ setSearchTerm("");
27494
+ };
27495
+ const handleToggle = () => {
27496
+ if (disabled) return;
27497
+ setIsOpen(!isOpen);
27498
+ if (!isOpen) {
27499
+ setTimeout(() => inputRef.current?.focus(), 100);
27244
27500
  }
27245
- videoProps.onSeeked?.(player);
27246
- }, [crop, renderFrameToCanvas, videoProps]);
27247
- const handleLoadedMetadata = React23.useCallback((player) => {
27248
- console.log("[CroppedVideoPlayer] Video metadata loaded");
27249
- calculateCanvasDimensions();
27250
- videoProps.onLoadedMetadata?.(player);
27251
- }, [calculateCanvasDimensions, videoProps]);
27252
- React23.useEffect(() => {
27253
- calculateCanvasDimensions();
27254
- const handleResize = () => {
27255
- calculateCanvasDimensions();
27501
+ };
27502
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className}`, ref: dropdownRef, children: [
27503
+ /* @__PURE__ */ jsxRuntime.jsx(
27504
+ "button",
27505
+ {
27506
+ type: "button",
27507
+ onClick: handleToggle,
27508
+ disabled,
27509
+ className: `
27510
+ w-full px-3 py-2 text-left bg-white border border-gray-300 rounded-md shadow-sm
27511
+ focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
27512
+ hover:border-gray-400 transition-colors duration-200
27513
+ ${disabled ? "bg-gray-50 cursor-not-allowed" : "cursor-pointer"}
27514
+ ${isOpen ? "ring-2 ring-blue-500 border-blue-500" : ""}
27515
+ `,
27516
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
27517
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
27518
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4 text-gray-400" }),
27519
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-sm ${value ? "text-gray-900" : "text-gray-500"}`, children: value ? getDisplayValue(value) : placeholder })
27520
+ ] }),
27521
+ /* @__PURE__ */ jsxRuntime.jsx(
27522
+ lucideReact.ChevronDown,
27523
+ {
27524
+ className: `h-4 w-4 text-gray-400 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`
27525
+ }
27526
+ )
27527
+ ] })
27528
+ }
27529
+ ),
27530
+ isOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-hidden", children: [
27531
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx(
27532
+ "input",
27533
+ {
27534
+ ref: inputRef,
27535
+ type: "text",
27536
+ placeholder: "Search time...",
27537
+ value: searchTerm,
27538
+ onChange: (e) => setSearchTerm(e.target.value),
27539
+ onKeyDown: handleKeyDown,
27540
+ className: "w-full px-3 py-2 text-sm border border-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
27541
+ }
27542
+ ) }),
27543
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-y-auto max-h-48", children: filteredSlots.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-gray-500 text-center", children: "No times found" }) : filteredSlots.map((slot) => /* @__PURE__ */ jsxRuntime.jsx(
27544
+ "button",
27545
+ {
27546
+ type: "button",
27547
+ onClick: () => handleSelect(slot.value),
27548
+ className: `
27549
+ w-full px-3 py-2 text-left text-sm hover:bg-blue-50 hover:text-blue-600
27550
+ transition-colors duration-150 border-b border-gray-100 last:border-b-0
27551
+ ${slot.value === value ? "bg-blue-50 text-blue-600 font-medium" : "text-gray-700"}
27552
+ `,
27553
+ children: slot.label
27554
+ },
27555
+ slot.value
27556
+ )) })
27557
+ ] })
27558
+ ] });
27559
+ };
27560
+ var SilentErrorBoundary = class extends React23__namespace.default.Component {
27561
+ constructor(props) {
27562
+ super(props);
27563
+ this.handleClearAndReload = () => {
27564
+ console.log("[ErrorBoundary] User initiated reset");
27565
+ if (typeof window !== "undefined") {
27566
+ try {
27567
+ localStorage.clear();
27568
+ sessionStorage.clear();
27569
+ console.log("[ErrorBoundary] Cleared all storage");
27570
+ } catch (error) {
27571
+ console.error("[ErrorBoundary] Failed to clear storage:", error);
27572
+ }
27573
+ }
27574
+ window.location.href = "/login";
27256
27575
  };
27257
- window.addEventListener("resize", handleResize);
27258
- return () => {
27259
- window.removeEventListener("resize", handleResize);
27576
+ this.state = {
27577
+ hasError: false,
27578
+ errorCount: 0,
27579
+ lastError: null,
27580
+ errorInfo: null
27260
27581
  };
27261
- }, [calculateCanvasDimensions]);
27262
- React23.useLayoutEffect(() => {
27263
- if (canvasRef.current && crop) {
27264
- const canvas = canvasRef.current;
27265
- const ctx = canvas.getContext("2d");
27266
- if (ctx) {
27267
- console.log("[CroppedVideoPlayer] Source changing - CLEARING CANVAS IMMEDIATELY");
27268
- ctx.clearRect(0, 0, canvas.width, canvas.height);
27269
- ctx.fillStyle = "black";
27270
- ctx.fillRect(0, 0, canvas.width, canvas.height);
27582
+ }
27583
+ static getDerivedStateFromError(error) {
27584
+ return { hasError: true };
27585
+ }
27586
+ componentDidCatch(error, errorInfo) {
27587
+ console.error("[ErrorBoundary] Caught render error:", {
27588
+ error: error.message,
27589
+ stack: error.stack,
27590
+ componentStack: errorInfo.componentStack,
27591
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
27592
+ });
27593
+ this.setState((prev) => ({
27594
+ errorCount: prev.errorCount + 1,
27595
+ lastError: error,
27596
+ errorInfo
27597
+ }));
27598
+ try {
27599
+ if (typeof window !== "undefined" && window.mixpanel) {
27600
+ window.mixpanel.track("React Render Error", {
27601
+ error: error.message,
27602
+ component: errorInfo.componentStack?.split("\n")[1] || "unknown",
27603
+ errorCount: this.state.errorCount + 1
27604
+ });
27271
27605
  }
27606
+ } catch (analyticsError) {
27607
+ console.warn("[ErrorBoundary] Analytics tracking failed:", analyticsError);
27272
27608
  }
27273
- }, [videoProps.src, crop]);
27274
- React23.useEffect(() => {
27275
- return () => {
27276
- stopCanvasRendering();
27277
- };
27278
- }, [stopCanvasRendering]);
27279
- if (!crop) {
27280
- return /* @__PURE__ */ jsxRuntime.jsx(VideoPlayer, { ref, ...videoProps, onClick });
27281
27609
  }
27282
- const handleClickWithIndicator = () => {
27283
- if (!onClick || !hiddenVideoRef.current?.player) return;
27284
- const player = hiddenVideoRef.current.player;
27285
- const willBePlaying = player.paused();
27286
- setIndicatorIsPlaying(willBePlaying);
27287
- setShowIndicator(false);
27288
- setTimeout(() => {
27289
- indicatorKeyRef.current += 1;
27290
- setShowIndicator(true);
27291
- }, 0);
27292
- onClick();
27293
- };
27294
- return /* @__PURE__ */ jsxRuntime.jsxs(
27295
- "div",
27296
- {
27297
- ref: videoContainerRef,
27298
- className: `relative w-full h-full flex items-center justify-center bg-black ${onClick ? "cursor-pointer" : ""} ${videoProps.className || ""}`,
27299
- onClick: handleClickWithIndicator,
27300
- children: [
27301
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
27302
- VideoPlayer,
27303
- {
27304
- ref: hiddenVideoRef,
27305
- ...videoProps,
27306
- onReady: handleVideoReady,
27307
- onPlay: handleVideoPlay,
27308
- onPause: handleVideoPause,
27309
- onEnded: handleVideoEnded,
27310
- onSeeking: handleSeeking,
27311
- onSeeked: handleSeeked,
27312
- onLoadedMetadata: handleLoadedMetadata,
27313
- onLoadedData: videoProps.onLoadedData,
27314
- onPlaying: videoProps.onPlaying,
27315
- onLoadingChange: videoProps.onLoadingChange
27316
- }
27317
- ) }),
27318
- /* @__PURE__ */ jsxRuntime.jsx(
27319
- "canvas",
27320
- {
27321
- ref: canvasRef,
27322
- width: canvasDimensions.width,
27323
- height: canvasDimensions.height,
27324
- className: "max-w-full max-h-full",
27325
- style: {
27326
- display: isVideoReady ? "block" : "none",
27327
- width: `${canvasDimensions.width}px`,
27328
- height: `${canvasDimensions.height}px`
27329
- }
27330
- }
27331
- ),
27332
- !isVideoReady && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27333
- debug && isVideoReady && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono", children: [
27334
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
27335
- "Crop: ",
27336
- crop.x,
27337
- ",",
27338
- crop.y,
27339
- " ",
27340
- crop.width,
27341
- "x",
27342
- crop.height,
27343
- "%"
27344
- ] }),
27345
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
27346
- "Canvas: ",
27347
- canvasDimensions.width,
27348
- "x",
27349
- canvasDimensions.height,
27350
- "px"
27351
- ] }),
27352
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
27353
- "Processing: ",
27354
- isProcessing ? "Yes" : "No"
27355
- ] })
27356
- ] }),
27357
- onClick && /* @__PURE__ */ jsxRuntime.jsx(
27358
- PlayPauseIndicator,
27359
- {
27360
- show: showIndicator,
27361
- isPlaying: indicatorIsPlaying
27362
- },
27363
- indicatorKeyRef.current
27364
- )
27365
- ]
27610
+ render() {
27611
+ if (!this.state.hasError) {
27612
+ return this.props.children;
27366
27613
  }
27367
- );
27368
- });
27369
- CroppedVideoPlayer.displayName = "CroppedVideoPlayer";
27614
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen w-screen items-center justify-center bg-slate-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center space-y-6 text-center", children: [
27615
+ /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading Dashboard..." }),
27616
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "Taking longer than usual..." }),
27617
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
27618
+ "a",
27619
+ {
27620
+ href: "#",
27621
+ onClick: (e) => {
27622
+ e.preventDefault();
27623
+ this.handleClearAndReload();
27624
+ },
27625
+ className: "text-xs text-gray-400 hover:text-gray-600 underline transition-colors",
27626
+ children: "Reset and try again"
27627
+ }
27628
+ ) }),
27629
+ process.env.NODE_ENV === "development" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400 italic mt-4", children: "Check console for error details" })
27630
+ ] }) });
27631
+ }
27632
+ };
27370
27633
  var BackButton = ({
27371
27634
  onClick,
27372
27635
  text = "Back",
@@ -27731,70 +27994,6 @@ var NewClipsNotification = ({
27731
27994
  }
27732
27995
  );
27733
27996
  };
27734
- var getSupabaseClient2 = () => {
27735
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
27736
- const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
27737
- if (!url || !key) {
27738
- throw new Error("Supabase configuration missing");
27739
- }
27740
- return supabaseJs.createClient(url, key);
27741
- };
27742
- var getAuthToken3 = async () => {
27743
- try {
27744
- const supabase = getSupabaseClient2();
27745
- const { data: { session } } = await supabase.auth.getSession();
27746
- return session?.access_token || null;
27747
- } catch (error) {
27748
- console.error("[useWorkspaceCrop] Error getting auth token:", error);
27749
- return null;
27750
- }
27751
- };
27752
- function useWorkspaceCrop(workspaceId) {
27753
- const [crop, setCrop] = React23.useState(null);
27754
- const [isLoading, setIsLoading] = React23.useState(true);
27755
- const [error, setError] = React23.useState(null);
27756
- React23.useEffect(() => {
27757
- if (!workspaceId) {
27758
- setIsLoading(false);
27759
- return;
27760
- }
27761
- const fetchCrop = async () => {
27762
- setIsLoading(true);
27763
- setError(null);
27764
- try {
27765
- const token = await getAuthToken3();
27766
- if (!token) {
27767
- throw new Error("Authentication required");
27768
- }
27769
- const response = await fetch("/api/clips/supabase", {
27770
- method: "POST",
27771
- headers: {
27772
- "Content-Type": "application/json",
27773
- "Authorization": `Bearer ${token}`
27774
- },
27775
- body: JSON.stringify({
27776
- action: "crop",
27777
- workspaceId
27778
- })
27779
- });
27780
- if (!response.ok) {
27781
- throw new Error(`Failed to fetch crop: ${response.statusText}`);
27782
- }
27783
- const data = await response.json();
27784
- console.log(`[useWorkspaceCrop] Fetched crop for workspace ${workspaceId}:`, data.crop);
27785
- setCrop(data.crop);
27786
- } catch (err) {
27787
- console.error("[useWorkspaceCrop] Error fetching crop:", err);
27788
- setError(err instanceof Error ? err.message : "Failed to fetch crop configuration");
27789
- setCrop(null);
27790
- } finally {
27791
- setIsLoading(false);
27792
- }
27793
- };
27794
- fetchCrop();
27795
- }, [workspaceId]);
27796
- return { crop, isLoading, error };
27797
- }
27798
27997
  var parseCycleTime = (value) => {
27799
27998
  if (typeof value === "number" && Number.isFinite(value)) {
27800
27999
  return value;
@@ -29050,6 +29249,11 @@ var BottlenecksContent = ({
29050
29249
  return Number.isFinite(numericValue) ? numericValue : null;
29051
29250
  })();
29052
29251
  const videoRef = React23.useRef(null);
29252
+ const videoPlayerOptions = React23.useMemo(() => ({
29253
+ fluid: false,
29254
+ responsive: false,
29255
+ fill: false
29256
+ }), []);
29053
29257
  const [initialFilter, setInitialFilter] = React23.useState("");
29054
29258
  const currentIndexRef = React23.useRef(0);
29055
29259
  const activeFilterRef = React23.useRef(initialFilter);
@@ -29060,6 +29264,7 @@ var BottlenecksContent = ({
29060
29264
  const [duration, setDuration] = React23.useState(0);
29061
29265
  const [currentIndex, setCurrentIndex] = React23.useState(0);
29062
29266
  const [currentClipId, setCurrentClipId] = React23.useState(null);
29267
+ const [playbackSpeed, setPlaybackSpeed] = React23.useState(1);
29063
29268
  const [isTransitioning, setIsTransitioning] = React23.useState(false);
29064
29269
  const [pendingVideo, setPendingVideo] = React23.useState(null);
29065
29270
  const [isVideoBuffering, setIsVideoBuffering] = React23.useState(false);
@@ -29077,6 +29282,31 @@ var BottlenecksContent = ({
29077
29282
  const [categoryMetadata, setCategoryMetadata] = React23.useState([]);
29078
29283
  const [currentMetadataIndex, setCurrentMetadataIndex] = React23.useState(0);
29079
29284
  const [metadataCache, setMetadataCache] = React23.useState({});
29285
+ const invalidateMetadataCache = React23.useCallback((categories) => {
29286
+ setMetadataCache((prevCache) => {
29287
+ if (!prevCache || Object.keys(prevCache).length === 0) {
29288
+ return prevCache;
29289
+ }
29290
+ const targetCategories = categories ? (Array.isArray(categories) ? categories : [categories]).filter(Boolean) : null;
29291
+ let updatedCache = null;
29292
+ const shouldInvalidate = (key) => {
29293
+ if (!targetCategories || targetCategories.length === 0) {
29294
+ return true;
29295
+ }
29296
+ const [categoryId] = key.split("-");
29297
+ return targetCategories.includes(categoryId);
29298
+ };
29299
+ Object.keys(prevCache).forEach((cacheKey) => {
29300
+ if (shouldInvalidate(cacheKey)) {
29301
+ if (!updatedCache) {
29302
+ updatedCache = { ...prevCache };
29303
+ }
29304
+ delete updatedCache[cacheKey];
29305
+ }
29306
+ });
29307
+ return updatedCache || prevCache;
29308
+ });
29309
+ }, []);
29080
29310
  const [triageClips, setTriageClips] = React23.useState([]);
29081
29311
  const [isLoadingTriageClips, setIsLoadingTriageClips] = React23.useState(false);
29082
29312
  const [isFullscreen, setIsFullscreen] = React23.useState(false);
@@ -29096,6 +29326,12 @@ var BottlenecksContent = ({
29096
29326
  onNewClips: (notification) => {
29097
29327
  console.log(`[BottlenecksContent] New clips detected:`, notification);
29098
29328
  if (notification.clips.length > 0) {
29329
+ const categoryIds = notification.clips.map((clip) => clip.clip_type).filter(Boolean).map((value) => String(value));
29330
+ if (categoryIds.length > 0) {
29331
+ invalidateMetadataCache(categoryIds);
29332
+ } else {
29333
+ invalidateMetadataCache();
29334
+ }
29099
29335
  fetchClipCounts();
29100
29336
  }
29101
29337
  }
@@ -29142,24 +29378,37 @@ var BottlenecksContent = ({
29142
29378
  shift: shift || "0"
29143
29379
  });
29144
29380
  React23.useEffect(() => {
29145
- if (clipTypes.length > 0 && !initialFilter) {
29146
- const priorityOrder = ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
29147
- let selectedType = null;
29148
- for (const priorityType of priorityOrder) {
29149
- const type = clipTypes.find((t) => t.type === priorityType && (dynamicCounts[t.type] || 0) > 0);
29150
- if (type) {
29151
- selectedType = type;
29152
- break;
29381
+ if (clipTypes.length > 0) {
29382
+ const currentFilterCount = initialFilter ? dynamicCounts[initialFilter] || 0 : 0;
29383
+ const hasAnyCounts = Object.values(dynamicCounts).some((c) => c > 0);
29384
+ const userHasNotNavigated = !initialFilter || activeFilterRef.current === initialFilter;
29385
+ const shouldRunSelection = !initialFilter || userHasNotNavigated && currentFilterCount === 0 && hasAnyCounts;
29386
+ if (shouldRunSelection) {
29387
+ let selectedType = null;
29388
+ if (clipTypes.length === 1) {
29389
+ selectedType = clipTypes[0];
29390
+ } else {
29391
+ const priorityOrder = ["cycle_completion", "fast-cycles", "slow-cycles", "idle_time"];
29392
+ for (const priorityType of priorityOrder) {
29393
+ const type = clipTypes.find((t) => t.type === priorityType && (dynamicCounts[t.type] || 0) > 0);
29394
+ if (type) {
29395
+ selectedType = type;
29396
+ break;
29397
+ }
29398
+ }
29399
+ if (!selectedType) {
29400
+ selectedType = clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0);
29401
+ }
29402
+ if (!selectedType) {
29403
+ selectedType = clipTypes[0];
29404
+ }
29405
+ }
29406
+ if (selectedType && selectedType.type !== initialFilter) {
29407
+ console.log(`[BottlenecksContent] Auto-selecting filter: ${selectedType.type} (count: ${dynamicCounts[selectedType.type] || 0})`);
29408
+ setInitialFilter(selectedType.type);
29409
+ setActiveFilter(selectedType.type);
29410
+ activeFilterRef.current = selectedType.type;
29153
29411
  }
29154
- }
29155
- if (!selectedType) {
29156
- selectedType = clipTypes.find((type) => (dynamicCounts[type.type] || 0) > 0);
29157
- }
29158
- const firstType = selectedType || clipTypes[0];
29159
- if (firstType) {
29160
- setInitialFilter(firstType.type);
29161
- setActiveFilter(firstType.type);
29162
- activeFilterRef.current = firstType.type;
29163
29412
  }
29164
29413
  }
29165
29414
  }, [clipTypes, dynamicCounts, initialFilter]);
@@ -29211,7 +29460,7 @@ var BottlenecksContent = ({
29211
29460
  } finally {
29212
29461
  fetchInProgressRef.current.delete(operationKey);
29213
29462
  }
29214
- }, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts]);
29463
+ }, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts, timezone, totalOutput]);
29215
29464
  const loadingCategoryRef = React23.useRef(null);
29216
29465
  const loadFirstVideoForCategory = React23.useCallback(async (category) => {
29217
29466
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
@@ -29301,15 +29550,16 @@ var BottlenecksContent = ({
29301
29550
  loadingCategoryRef.current = null;
29302
29551
  fetchInProgressRef.current.delete(operationKey);
29303
29552
  }
29304
- }, [workspaceId, date, s3ClipsService, mergedCounts, effectiveShift]);
29553
+ }, [workspaceId, date, s3ClipsService, mergedCounts, effectiveShift, timezone]);
29305
29554
  const handleRefreshClips = React23.useCallback(async () => {
29306
29555
  console.log("[BottlenecksContent] Refreshing clips after new additions");
29307
29556
  acknowledgeNewClips();
29557
+ invalidateMetadataCache();
29308
29558
  await fetchClipCounts();
29309
29559
  if (activeFilter && mergedCounts[activeFilter] > 0) {
29310
29560
  await loadFirstVideoForCategory(activeFilter);
29311
29561
  }
29312
- }, [acknowledgeNewClips, fetchClipCounts, activeFilter, mergedCounts, loadFirstVideoForCategory]);
29562
+ }, [acknowledgeNewClips, fetchClipCounts, activeFilter, mergedCounts, loadFirstVideoForCategory, invalidateMetadataCache]);
29313
29563
  React23.useEffect(() => {
29314
29564
  if (s3ClipsService) {
29315
29565
  fetchClipCounts();
@@ -29479,38 +29729,38 @@ var BottlenecksContent = ({
29479
29729
  loadingTimeoutRef.current = null;
29480
29730
  }
29481
29731
  }, []);
29482
- const loadCategoryMetadata = React23.useCallback(async (categoryId, autoLoadFirstVideo = false) => {
29732
+ const loadCategoryMetadata = React23.useCallback(async (categoryId, autoLoadFirstVideo = false, forceRefresh = false) => {
29483
29733
  if (!workspaceId) {
29484
29734
  return;
29485
29735
  }
29486
- const cacheKey = `${categoryId}-${date || getOperationalDate(timezone)}-${effectiveShift}`;
29487
- if (metadataCache[cacheKey]) {
29488
- console.log(`[BottlenecksContent] Using cached metadata for ${categoryId}`);
29489
- setCategoryMetadata(metadataCache[cacheKey]);
29490
- categoryMetadataRef.current = metadataCache[cacheKey];
29491
- if (autoLoadFirstVideo && metadataCache[cacheKey].length > 0 && s3ClipsService) {
29492
- const firstClipMeta = metadataCache[cacheKey][0];
29493
- try {
29494
- const video = await s3ClipsService.getClipById(firstClipMeta.clipId);
29495
- if (video && isMountedRef.current) {
29496
- setCurrentClipId(firstClipMeta.clipId);
29497
- setAllVideos([video]);
29498
- setCurrentIndex(0);
29499
- setCurrentMetadataIndex(0);
29500
- currentMetadataIndexRef.current = 0;
29501
- setIsCategoryLoading(false);
29502
- console.log(`[BottlenecksContent] Auto-loaded first video from cache: ${video.id} (1/${metadataCache[cacheKey].length})`);
29736
+ const resolvedDate = date || getOperationalDate(timezone);
29737
+ const cacheKey = `${categoryId}-${resolvedDate}-${effectiveShift}`;
29738
+ const cachedMetadata = !forceRefresh ? metadataCache[cacheKey] : void 0;
29739
+ try {
29740
+ if (cachedMetadata) {
29741
+ console.log(`[BottlenecksContent] Using cached metadata for ${categoryId}`);
29742
+ setCategoryMetadata(cachedMetadata);
29743
+ categoryMetadataRef.current = cachedMetadata;
29744
+ if (autoLoadFirstVideo && cachedMetadata.length > 0 && s3ClipsService) {
29745
+ const firstClipMeta = cachedMetadata[0];
29746
+ try {
29747
+ const video = await s3ClipsService.getClipById(firstClipMeta.clipId);
29748
+ if (video && isMountedRef.current) {
29749
+ setCurrentClipId(firstClipMeta.clipId);
29750
+ setAllVideos([video]);
29751
+ setCurrentIndex(0);
29752
+ setCurrentMetadataIndex(0);
29753
+ currentMetadataIndexRef.current = 0;
29754
+ console.log(`[BottlenecksContent] Auto-loaded first video from cache: ${video.id} (1/${cachedMetadata.length})`);
29755
+ }
29756
+ } catch (error2) {
29757
+ console.error(`[BottlenecksContent] Error loading first video from cache:`, error2);
29758
+ clearLoadingState();
29503
29759
  }
29504
- } catch (error2) {
29505
- console.error(`[BottlenecksContent] Error loading first video from cache:`, error2);
29506
- setIsCategoryLoading(false);
29507
- clearLoadingState();
29508
29760
  }
29761
+ return;
29509
29762
  }
29510
- return;
29511
- }
29512
- try {
29513
- console.log(`[BottlenecksContent] Loading metadata for category: ${categoryId}`);
29763
+ console.log(`[BottlenecksContent] Loading metadata for category: ${categoryId}${forceRefresh ? " (force refresh)" : ""}`);
29514
29764
  const { createClient: createClient5 } = await import('@supabase/supabase-js');
29515
29765
  const supabase = createClient5(
29516
29766
  process.env.NEXT_PUBLIC_SUPABASE_URL || "",
@@ -29535,8 +29785,8 @@ var BottlenecksContent = ({
29535
29785
  action: "percentile-clips",
29536
29786
  percentileAction: percentileType,
29537
29787
  workspaceId,
29538
- startDate: `${date || getOperationalDate(timezone)}T00:00:00Z`,
29539
- endDate: `${date || getOperationalDate(timezone)}T23:59:59Z`,
29788
+ startDate: `${resolvedDate}T00:00:00Z`,
29789
+ endDate: `${resolvedDate}T23:59:59Z`,
29540
29790
  percentile: 10,
29541
29791
  shiftId: effectiveShift,
29542
29792
  limit: 100
@@ -29552,7 +29802,7 @@ var BottlenecksContent = ({
29552
29802
  body: JSON.stringify({
29553
29803
  action: "clip-metadata",
29554
29804
  workspaceId,
29555
- date: date || getOperationalDate(timezone),
29805
+ date: resolvedDate,
29556
29806
  shift: effectiveShift,
29557
29807
  category: categoryId,
29558
29808
  page: 1,
@@ -29597,19 +29847,22 @@ var BottlenecksContent = ({
29597
29847
  setCurrentIndex(0);
29598
29848
  setCurrentMetadataIndex(0);
29599
29849
  currentMetadataIndexRef.current = 0;
29600
- setIsCategoryLoading(false);
29601
29850
  console.log(`[BottlenecksContent] Auto-loaded first video: ${video.id} (1/${metadataClips.length})`);
29602
29851
  }
29603
29852
  } catch (error2) {
29604
29853
  console.error(`[BottlenecksContent] Error loading first video:`, error2);
29605
- setIsCategoryLoading(false);
29606
29854
  }
29607
29855
  }
29856
+ } else {
29857
+ setCategoryMetadata([]);
29858
+ categoryMetadataRef.current = [];
29608
29859
  }
29609
29860
  } catch (error2) {
29610
29861
  console.error(`[BottlenecksContent] Error loading category metadata:`, error2);
29862
+ } finally {
29863
+ setIsCategoryLoading(false);
29611
29864
  }
29612
- }, [workspaceId, date, effectiveShift, isPercentileCategory, metadataCache, s3ClipsService]);
29865
+ }, [workspaceId, date, effectiveShift, isPercentileCategory, metadataCache, s3ClipsService, timezone, clearLoadingState]);
29613
29866
  const loadAndPlayClipById = React23.useCallback(async (clipId, categoryId, position) => {
29614
29867
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
29615
29868
  console.log(`[BottlenecksContent] Loading clip by ID: ${clipId}, category=${categoryId}, position=${position}`);
@@ -29633,21 +29886,31 @@ var BottlenecksContent = ({
29633
29886
  }
29634
29887
  try {
29635
29888
  await loadCategoryMetadata(categoryId, false);
29636
- const metadataArray = categoryMetadataRef.current;
29637
- if (metadataArray.length > 0) {
29638
- const clickedClipIndex = metadataArray.findIndex((clip) => clip.clipId === clipId);
29639
- if (clickedClipIndex !== -1) {
29640
- setCurrentMetadataIndex(clickedClipIndex);
29641
- currentMetadataIndexRef.current = clickedClipIndex;
29642
- const video = await s3ClipsService.getClipById(clipId);
29643
- if (video) {
29644
- setPendingVideo(video);
29645
- setCurrentClipId(clipId);
29646
- setAllVideos([video]);
29647
- setCurrentIndex(0);
29648
- console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
29649
- }
29650
- }
29889
+ let metadataArray = categoryMetadataRef.current;
29890
+ const clipExistsInMetadata = metadataArray.some((clip) => clip.clipId === clipId);
29891
+ if (metadataArray.length === 0 || !clipExistsInMetadata) {
29892
+ console.warn(`[BottlenecksContent] Clip ${clipId} not found in metadata for ${categoryId} (cache hit: ${metadataArray.length > 0}) - forcing refresh`);
29893
+ await loadCategoryMetadata(categoryId, false, true);
29894
+ metadataArray = categoryMetadataRef.current;
29895
+ }
29896
+ if (metadataArray.length === 0) {
29897
+ throw new Error(`No metadata available for category ${categoryId}`);
29898
+ }
29899
+ const clickedClipIndex = metadataArray.findIndex((clip) => clip.clipId === clipId);
29900
+ if (clickedClipIndex === -1) {
29901
+ throw new Error(`Clip ${clipId} not found after metadata refresh`);
29902
+ }
29903
+ setCurrentMetadataIndex(clickedClipIndex);
29904
+ currentMetadataIndexRef.current = clickedClipIndex;
29905
+ const video = await s3ClipsService.getClipById(clipId);
29906
+ if (video) {
29907
+ setPendingVideo(video);
29908
+ setCurrentClipId(clipId);
29909
+ setAllVideos([video]);
29910
+ setCurrentIndex(0);
29911
+ console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
29912
+ } else {
29913
+ throw new Error(`Failed to load video data for clip ${clipId}`);
29651
29914
  }
29652
29915
  } catch (error2) {
29653
29916
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
@@ -29661,7 +29924,7 @@ var BottlenecksContent = ({
29661
29924
  clearLoadingState();
29662
29925
  }
29663
29926
  }
29664
- }, [workspaceId, s3ClipsService, date, effectiveShift, updateActiveFilter, clearLoadingState]);
29927
+ }, [workspaceId, s3ClipsService, updateActiveFilter, clearLoadingState, loadCategoryMetadata]);
29665
29928
  React23.useCallback(async (categoryId, clipIndex) => {
29666
29929
  console.warn("[BottlenecksContent] loadAndPlayClip is deprecated, use loadAndPlayClipById instead");
29667
29930
  if (!workspaceId || !s3ClipsService || !isMountedRef.current) return;
@@ -29688,7 +29951,7 @@ var BottlenecksContent = ({
29688
29951
  });
29689
29952
  setIsNavigating(false);
29690
29953
  }
29691
- }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById]);
29954
+ }, [workspaceId, s3ClipsService, date, effectiveShift, loadAndPlayClipById, timezone]);
29692
29955
  const handleNext = React23.useCallback(async () => {
29693
29956
  if (!isMountedRef.current) return;
29694
29957
  const currentFilter = activeFilterRef.current;
@@ -29705,8 +29968,18 @@ var BottlenecksContent = ({
29705
29968
  }
29706
29969
  try {
29707
29970
  const currentMetaIndex = currentMetadataIndexRef.current;
29708
- const metadataArray = categoryMetadataRef.current;
29971
+ let metadataArray = categoryMetadataRef.current;
29972
+ if (metadataArray.length === 0) {
29973
+ console.log(`[handleNext] Metadata empty for ${currentFilter}, loading before navigation`);
29974
+ await loadCategoryMetadata(currentFilter, false);
29975
+ metadataArray = categoryMetadataRef.current;
29976
+ }
29709
29977
  console.log(`[handleNext] Unified navigation: ${currentFilter}, metadata index: ${currentMetaIndex}/${metadataArray.length}`);
29978
+ if (metadataArray.length === 0) {
29979
+ console.warn("[handleNext] No metadata available after refresh - stopping navigation");
29980
+ clearLoadingState();
29981
+ return;
29982
+ }
29710
29983
  if (currentMetaIndex < metadataArray.length - 1) {
29711
29984
  const nextMetadataIndex = currentMetaIndex + 1;
29712
29985
  const nextClipMeta = metadataArray[nextMetadataIndex];
@@ -29741,7 +30014,7 @@ var BottlenecksContent = ({
29741
30014
  });
29742
30015
  clearLoadingState();
29743
30016
  }
29744
- }, [clearLoadingState, s3ClipsService]);
30017
+ }, [clearLoadingState, s3ClipsService, loadCategoryMetadata]);
29745
30018
  const handlePrevious = React23.useCallback(async () => {
29746
30019
  if (!isMountedRef.current) return;
29747
30020
  const currentFilter = activeFilterRef.current;
@@ -29758,8 +30031,18 @@ var BottlenecksContent = ({
29758
30031
  }
29759
30032
  try {
29760
30033
  const currentMetaIndex = currentMetadataIndexRef.current;
29761
- const metadataArray = categoryMetadataRef.current;
30034
+ let metadataArray = categoryMetadataRef.current;
30035
+ if (metadataArray.length === 0) {
30036
+ console.log(`[handlePrevious] Metadata empty for ${currentFilter}, loading before navigation`);
30037
+ await loadCategoryMetadata(currentFilter, false);
30038
+ metadataArray = categoryMetadataRef.current;
30039
+ }
29762
30040
  console.log(`[handlePrevious] Unified navigation: ${currentFilter}, metadata index: ${currentMetaIndex}/${metadataArray.length}`);
30041
+ if (metadataArray.length === 0) {
30042
+ console.warn("[handlePrevious] No metadata available after refresh - stopping navigation");
30043
+ clearLoadingState();
30044
+ return;
30045
+ }
29763
30046
  if (currentMetaIndex > 0) {
29764
30047
  const prevMetadataIndex = currentMetaIndex - 1;
29765
30048
  const prevClipMeta = metadataArray[prevMetadataIndex];
@@ -29790,7 +30073,7 @@ var BottlenecksContent = ({
29790
30073
  });
29791
30074
  clearLoadingState();
29792
30075
  }
29793
- }, [clearLoadingState, s3ClipsService]);
30076
+ }, [clearLoadingState, s3ClipsService, loadCategoryMetadata]);
29794
30077
  const currentVideo = React23.useMemo(() => {
29795
30078
  if (!filteredVideos || filteredVideos.length === 0 || currentIndex >= filteredVideos.length) {
29796
30079
  return null;
@@ -29803,7 +30086,10 @@ var BottlenecksContent = ({
29803
30086
  if (error?.isRetrying) {
29804
30087
  setError(null);
29805
30088
  }
29806
- }, [error]);
30089
+ if (videoRef.current?.playbackRate && playbackSpeed !== 1) {
30090
+ videoRef.current.playbackRate(playbackSpeed);
30091
+ }
30092
+ }, [error, playbackSpeed]);
29807
30093
  const handleVideoPlay = React23.useCallback(async (player) => {
29808
30094
  setIsPlaying(true);
29809
30095
  setIsInitialLoading(false);
@@ -29973,7 +30259,13 @@ var BottlenecksContent = ({
29973
30259
  player.pause();
29974
30260
  }
29975
30261
  };
29976
- const toggleFullscreen = React23.useCallback((e) => {
30262
+ const handlePlaybackSpeedChange = React23.useCallback((speed) => {
30263
+ setPlaybackSpeed(speed);
30264
+ if (videoRef.current?.playbackRate) {
30265
+ videoRef.current.playbackRate(speed);
30266
+ }
30267
+ }, []);
30268
+ React23.useCallback((e) => {
29977
30269
  e.stopPropagation();
29978
30270
  setIsFullscreen((prev) => !prev);
29979
30271
  }, []);
@@ -30147,12 +30439,7 @@ var BottlenecksContent = ({
30147
30439
  onLoadedData: handleLoadedData,
30148
30440
  onPlaying: handleVideoPlaying,
30149
30441
  onLoadingChange: handleVideoLoadingChange,
30150
- options: {
30151
- // Ensure full height is always visible - no cropping
30152
- fluid: false,
30153
- responsive: false,
30154
- fill: false
30155
- }
30442
+ options: videoPlayerOptions
30156
30443
  }
30157
30444
  )
30158
30445
  }
@@ -30213,58 +30500,7 @@ var BottlenecksContent = ({
30213
30500
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium mr-2", children: getClipTypeLabel(currentVideo) }),
30214
30501
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-80 hidden sm:inline", children: currentVideo.description })
30215
30502
  ] }) })
30216
- ),
30217
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 z-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-white", children: [
30218
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
30219
- /* @__PURE__ */ jsxRuntime.jsx(
30220
- "button",
30221
- {
30222
- onClick: (e) => {
30223
- e.stopPropagation();
30224
- togglePlayback();
30225
- },
30226
- className: "p-1.5 hover:bg-white/20 rounded-full focus:outline-none focus:ring-2 focus:ring-white/50",
30227
- "aria-label": isPlaying ? "Pause" : "Play",
30228
- children: isPlaying ? /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 00-1 1v2a1 1 0 102 0V9a1 1 0 00-1-1zm5 0a1 1 0 00-1 1v2a1 1 0 102 0V9a1 1 0 00-1-1z", clipRule: "evenodd" }) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8.118l-.001 3.764a1 1 0 001.555.832l3.196-1.882a1 1 0 000-1.664l-3.196-1.882z", clipRule: "evenodd" }) })
30229
- }
30230
- ),
30231
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-mono px-2", children: [
30232
- formatTime2(currentTime),
30233
- " / ",
30234
- formatTime2(duration)
30235
- ] })
30236
- ] }),
30237
- /* @__PURE__ */ jsxRuntime.jsx(
30238
- "input",
30239
- {
30240
- type: "range",
30241
- min: "0",
30242
- max: duration || 0,
30243
- value: currentTime,
30244
- onChange: (e) => {
30245
- if (videoRef.current) {
30246
- videoRef.current.currentTime(Number(e.target.value));
30247
- }
30248
- },
30249
- className: "flex-grow mx-3 h-2.5 bg-white/30 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-white/50 touch-manipulation",
30250
- style: {
30251
- WebkitAppearance: "none",
30252
- appearance: "none"
30253
- },
30254
- "aria-label": "Seek slider"
30255
- }
30256
- ),
30257
- /* @__PURE__ */ jsxRuntime.jsx(
30258
- "button",
30259
- {
30260
- onClick: toggleFullscreen,
30261
- className: "p-1.5 hover:bg-white/20 rounded-full focus:outline-none focus:ring-2 focus:ring-white/50",
30262
- "aria-label": "Fullscreen",
30263
- title: "Expand to fullscreen",
30264
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-5 w-5" })
30265
- }
30266
- )
30267
- ] }) })
30503
+ )
30268
30504
  ] }) }) }) : (
30269
30505
  /* Priority 5: Show "no clips found" only if we have counts and there are truly no clips for workspace */
30270
30506
  hasInitialLoad && Object.keys(mergedCounts).length > 0 && Object.values(mergedCounts).every((count) => count === 0) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex items-center justify-center ${triageMode ? "h-full" : "h-[calc(100%-4rem)]"}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
@@ -30482,11 +30718,7 @@ var BottlenecksContent = ({
30482
30718
  onLoadedData: handleLoadedData,
30483
30719
  onPlaying: handleVideoPlaying,
30484
30720
  onLoadingChange: handleVideoLoadingChange,
30485
- options: {
30486
- fluid: false,
30487
- responsive: false,
30488
- fill: false
30489
- }
30721
+ options: videoPlayerOptions
30490
30722
  }
30491
30723
  )
30492
30724
  }
@@ -30494,45 +30726,32 @@ var BottlenecksContent = ({
30494
30726
  (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..." }) }),
30495
30727
  !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..." }) }),
30496
30728
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 z-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-white", children: [
30497
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
30498
- /* @__PURE__ */ jsxRuntime.jsx(
30499
- "button",
30729
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
30730
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Speed:" }),
30731
+ /* @__PURE__ */ jsxRuntime.jsxs(
30732
+ "select",
30500
30733
  {
30501
- onClick: (e) => {
30502
- e.stopPropagation();
30503
- togglePlayback();
30504
- },
30505
- className: "p-2 hover:bg-white/20 rounded-full focus:outline-none focus:ring-2 focus:ring-white/50",
30506
- "aria-label": isPlaying ? "Pause" : "Play",
30507
- children: isPlaying ? /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-6 w-6", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 00-1 1v2a1 1 0 102 0V9a1 1 0 00-1-1zm5 0a1 1 0 00-1 1v2a1 1 0 102 0V9a1 1 0 00-1-1z", clipRule: "evenodd" }) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-6 w-6", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8.118l-.001 3.764a1 1 0 001.555.832l3.196-1.882a1 1 0 000-1.664l-3.196-1.882z", clipRule: "evenodd" }) })
30734
+ value: playbackSpeed,
30735
+ onChange: (e) => handlePlaybackSpeedChange(Number(e.target.value)),
30736
+ className: "px-2 py-1 bg-white/20 hover:bg-white/30 rounded text-sm font-medium cursor-pointer focus:outline-none focus:ring-2 focus:ring-white/50 transition-colors",
30737
+ "aria-label": "Playback speed",
30738
+ children: [
30739
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "0.25", children: "0.25x" }),
30740
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "0.5", children: "0.5x" }),
30741
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "0.75", children: "0.75x" }),
30742
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "1", children: "1x" }),
30743
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "1.25", children: "1.25x" }),
30744
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "1.5", children: "1.5x" }),
30745
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "1.75", children: "1.75x" }),
30746
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "2", children: "2x" }),
30747
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "3", children: "3x" }),
30748
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "4", children: "4x" }),
30749
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "5", children: "5x" }),
30750
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "10", children: "10x" })
30751
+ ]
30508
30752
  }
30509
- ),
30510
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-mono px-2", children: [
30511
- formatTime2(currentTime),
30512
- " / ",
30513
- formatTime2(duration)
30514
- ] })
30753
+ )
30515
30754
  ] }),
30516
- /* @__PURE__ */ jsxRuntime.jsx(
30517
- "input",
30518
- {
30519
- type: "range",
30520
- min: "0",
30521
- max: duration || 0,
30522
- value: currentTime,
30523
- onChange: (e) => {
30524
- if (videoRef.current) {
30525
- videoRef.current.currentTime(Number(e.target.value));
30526
- }
30527
- },
30528
- className: "flex-grow mx-4 h-2.5 bg-white/30 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-white/50 touch-manipulation",
30529
- style: {
30530
- WebkitAppearance: "none",
30531
- appearance: "none"
30532
- },
30533
- "aria-label": "Seek slider"
30534
- }
30535
- ),
30536
30755
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
30537
30756
  /* @__PURE__ */ jsxRuntime.jsx(
30538
30757
  "button",
@@ -37633,26 +37852,26 @@ var SingleVideoStream = ({
37633
37852
  const hlsStreamUrl = streamUrl || getCameraStreamUrl(workspaceName, baseUrl);
37634
37853
  console.log(`Using camera URL for ${workspaceName}: ${hlsStreamUrl}`);
37635
37854
  const mergedHlsConfig = { ...DEFAULT_HLS_CONFIG, ...hlsConfig };
37636
- if (Hls2__default.default.isSupported()) {
37637
- const hls = new Hls2__default.default(mergedHlsConfig);
37855
+ if (Hls3__default.default.isSupported()) {
37856
+ const hls = new Hls3__default.default(mergedHlsConfig);
37638
37857
  hlsRef.current = hls;
37639
- hls.on(Hls2__default.default.Events.MEDIA_ATTACHED, () => {
37858
+ hls.on(Hls3__default.default.Events.MEDIA_ATTACHED, () => {
37640
37859
  console.log("HLS media attached");
37641
37860
  hls.loadSource(hlsStreamUrl);
37642
37861
  });
37643
- hls.on(Hls2__default.default.Events.MANIFEST_PARSED, () => {
37862
+ hls.on(Hls3__default.default.Events.MANIFEST_PARSED, () => {
37644
37863
  console.log("HLS manifest parsed");
37645
37864
  attemptPlay(video);
37646
37865
  });
37647
- hls.on(Hls2__default.default.Events.ERROR, (_, data) => {
37866
+ hls.on(Hls3__default.default.Events.ERROR, (_, data) => {
37648
37867
  if (data.fatal) {
37649
37868
  console.error("Fatal HLS error:", data.type, data.details);
37650
37869
  switch (data.type) {
37651
- case Hls2__default.default.ErrorTypes.NETWORK_ERROR:
37870
+ case Hls3__default.default.ErrorTypes.NETWORK_ERROR:
37652
37871
  console.error("Fatal network error encountered");
37653
37872
  setError("Network error: Please check your connection");
37654
37873
  break;
37655
- case Hls2__default.default.ErrorTypes.MEDIA_ERROR:
37874
+ case Hls3__default.default.ErrorTypes.MEDIA_ERROR:
37656
37875
  console.error("Fatal media error encountered, trying to recover");
37657
37876
  hls.recoverMediaError();
37658
37877
  break;
@@ -42413,9 +42632,9 @@ var MetricCards = React23.memo(({ lineInfo }) => {
42413
42632
  animate: "animate",
42414
42633
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 mb-2 md:h-[35vh] h-auto",
42415
42634
  children: [
42416
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[200px] sm:h-[280px] md:h-auto lg:col-span-1 sm:col-span-2 lg:col-span-1", children: [
42417
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-semibold text-gray-700 mb-1 sm:mb-2 text-center", children: "Line Output" }),
42418
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100%-1.75rem)] sm:h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
42635
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
42636
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-semibold text-gray-700 mb-2 text-center", children: "Line Output" }),
42637
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
42419
42638
  OutputProgressChart,
42420
42639
  {
42421
42640
  currentOutput: lineInfo?.metrics.current_output || 0,
@@ -42423,9 +42642,9 @@ var MetricCards = React23.memo(({ lineInfo }) => {
42423
42642
  }
42424
42643
  ) }) })
42425
42644
  ] }),
42426
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[140px] sm:h-[160px] md:h-auto", children: [
42645
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
42427
42646
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-semibold text-gray-700 text-center mb-2", children: "Underperforming Workspaces" }),
42428
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center h-[calc(100%-2rem)] sm:h-[calc(100%-2.5rem)]", children: [
42647
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center h-[calc(100%-2.5rem)]", children: [
42429
42648
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold text-red-600", children: lineInfo?.metrics.underperforming_workspaces }),
42430
42649
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xl sm:text-2xl md:text-2xl lg:text-3xl text-gray-500 ml-1 sm:ml-2", children: [
42431
42650
  "/ ",
@@ -42433,9 +42652,9 @@ var MetricCards = React23.memo(({ lineInfo }) => {
42433
42652
  ] })
42434
42653
  ] })
42435
42654
  ] }),
42436
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[140px] sm:h-[160px] md:h-auto", children: [
42655
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
42437
42656
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-semibold text-gray-700 text-center mb-2", children: "Average Efficiency" }),
42438
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-2rem)] sm:h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold ${(lineInfo?.metrics.avg_efficiency || 0) >= 90 ? "text-green-600" : (lineInfo?.metrics.avg_efficiency || 0) >= 70 ? "text-yellow-600" : "text-red-600"}`, children: [
42657
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold ${(lineInfo?.metrics.avg_efficiency || 0) >= 90 ? "text-green-600" : (lineInfo?.metrics.avg_efficiency || 0) >= 70 ? "text-yellow-600" : "text-red-600"}`, children: [
42439
42658
  lineInfo?.metrics.avg_efficiency.toFixed(1),
42440
42659
  "%"
42441
42660
  ] }) })
@@ -51992,6 +52211,7 @@ exports.CardTitle = CardTitle2;
51992
52211
  exports.ClipFilterProvider = ClipFilterProvider;
51993
52212
  exports.CompactWorkspaceHealthCard = CompactWorkspaceHealthCard;
51994
52213
  exports.CongratulationsOverlay = CongratulationsOverlay;
52214
+ exports.CroppedHlsVideoPlayer = CroppedHlsVideoPlayer;
51995
52215
  exports.CroppedVideoPlayer = CroppedVideoPlayer;
51996
52216
  exports.CycleTimeChart = CycleTimeChart;
51997
52217
  exports.CycleTimeOverTimeChart = CycleTimeOverTimeChart;
@@ -52032,6 +52252,7 @@ exports.Header = Header;
52032
52252
  exports.HealthStatusGrid = HealthStatusGrid;
52033
52253
  exports.HealthStatusIndicator = HealthStatusIndicator;
52034
52254
  exports.HelpView = HelpView_default;
52255
+ exports.HlsVideoPlayer = HlsVideoPlayer;
52035
52256
  exports.HomeView = HomeView_default;
52036
52257
  exports.HourlyOutputChart = HourlyOutputChart2;
52037
52258
  exports.ISTTimer = ISTTimer_default;