@optifye/dashboard-core 6.11.28 → 6.11.29

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
@@ -39220,6 +39220,456 @@ var MetricCard2 = ({ title, value, unit = "", trend = null }) => {
39220
39220
  ] });
39221
39221
  };
39222
39222
  var MetricCard_default = MetricCard2;
39223
+ var normalizeValue = (v) => {
39224
+ if (!v) return "";
39225
+ return v.substring(0, 5);
39226
+ };
39227
+ var to12h = (value24) => {
39228
+ const norm = normalizeValue(value24);
39229
+ if (!norm || norm.length < 5) return { hour: 12, minute: 0, period: "AM" };
39230
+ const [h, m] = norm.split(":").map(Number);
39231
+ const period = h >= 12 ? "PM" : "AM";
39232
+ let hour12 = h % 12;
39233
+ if (hour12 === 0) hour12 = 12;
39234
+ return { hour: hour12, minute: isNaN(m) ? 0 : m, period };
39235
+ };
39236
+ var to24h = (hour12, minute, period) => {
39237
+ let h = hour12;
39238
+ if (period === "AM" && h === 12) h = 0;
39239
+ if (period === "PM" && h !== 12) h += 12;
39240
+ return `${h.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
39241
+ };
39242
+ var pad22 = (n) => n.toString().padStart(2, "0");
39243
+ var SegmentedTimeInput = ({
39244
+ hour,
39245
+ minute,
39246
+ period,
39247
+ hasValue,
39248
+ placeholder,
39249
+ disabled,
39250
+ activeSegment,
39251
+ onSegmentFocus,
39252
+ onSegmentBlur,
39253
+ onHourChange,
39254
+ onMinuteChange,
39255
+ onPeriodChange,
39256
+ onTogglePopover,
39257
+ isPopoverOpen
39258
+ }) => {
39259
+ const containerRef = React142.useRef(null);
39260
+ const digitBufferRef = React142.useRef("");
39261
+ const digitTimerRef = React142.useRef(null);
39262
+ React142.useEffect(() => {
39263
+ digitBufferRef.current = "";
39264
+ if (digitTimerRef.current) clearTimeout(digitTimerRef.current);
39265
+ }, [activeSegment]);
39266
+ const commitHourDigits = React142.useCallback((digits) => {
39267
+ const val = parseInt(digits, 10);
39268
+ if (val >= 1 && val <= 12) onHourChange(val);
39269
+ else if (val === 0) onHourChange(12);
39270
+ digitBufferRef.current = "";
39271
+ }, [onHourChange]);
39272
+ const commitMinuteDigits = React142.useCallback((digits) => {
39273
+ const val = parseInt(digits, 10);
39274
+ onMinuteChange(Math.min(59, Math.max(0, val)));
39275
+ digitBufferRef.current = "";
39276
+ }, [onMinuteChange]);
39277
+ const handleKeyDown = React142.useCallback((e) => {
39278
+ if (disabled || !activeSegment) return;
39279
+ const key = e.key;
39280
+ if (key === "Tab") {
39281
+ if (e.shiftKey) {
39282
+ if (activeSegment === "minute") {
39283
+ e.preventDefault();
39284
+ onSegmentFocus("hour");
39285
+ } else if (activeSegment === "period") {
39286
+ e.preventDefault();
39287
+ onSegmentFocus("minute");
39288
+ }
39289
+ } else {
39290
+ if (activeSegment === "hour") {
39291
+ e.preventDefault();
39292
+ onSegmentFocus("minute");
39293
+ } else if (activeSegment === "minute") {
39294
+ e.preventDefault();
39295
+ onSegmentFocus("period");
39296
+ }
39297
+ }
39298
+ return;
39299
+ }
39300
+ e.preventDefault();
39301
+ if (key === "ArrowUp" || key === "ArrowDown") {
39302
+ const delta = key === "ArrowUp" ? 1 : -1;
39303
+ if (activeSegment === "hour") {
39304
+ let newH = hour + delta;
39305
+ if (newH > 12) newH = 1;
39306
+ if (newH < 1) newH = 12;
39307
+ onHourChange(newH);
39308
+ } else if (activeSegment === "minute") {
39309
+ let newM = minute + delta;
39310
+ if (newM > 59) newM = 0;
39311
+ if (newM < 0) newM = 59;
39312
+ onMinuteChange(newM);
39313
+ } else if (activeSegment === "period") {
39314
+ onPeriodChange(period === "AM" ? "PM" : "AM");
39315
+ }
39316
+ return;
39317
+ }
39318
+ if (key === "Enter" || key === " ") {
39319
+ onTogglePopover();
39320
+ return;
39321
+ }
39322
+ if (key === "Escape") {
39323
+ onSegmentBlur();
39324
+ return;
39325
+ }
39326
+ if (activeSegment === "period") {
39327
+ if (key.toLowerCase() === "a") onPeriodChange("AM");
39328
+ else if (key.toLowerCase() === "p") onPeriodChange("PM");
39329
+ return;
39330
+ }
39331
+ if (key >= "0" && key <= "9") {
39332
+ if (digitTimerRef.current) clearTimeout(digitTimerRef.current);
39333
+ if (activeSegment === "hour") {
39334
+ digitBufferRef.current += key;
39335
+ if (digitBufferRef.current.length >= 2) {
39336
+ commitHourDigits(digitBufferRef.current);
39337
+ onSegmentFocus("minute");
39338
+ } else {
39339
+ const firstDigit = parseInt(key, 10);
39340
+ if (firstDigit >= 2) {
39341
+ commitHourDigits(digitBufferRef.current);
39342
+ onSegmentFocus("minute");
39343
+ } else {
39344
+ digitTimerRef.current = setTimeout(() => {
39345
+ commitHourDigits(digitBufferRef.current);
39346
+ onSegmentFocus("minute");
39347
+ }, 800);
39348
+ }
39349
+ }
39350
+ } else if (activeSegment === "minute") {
39351
+ digitBufferRef.current += key;
39352
+ if (digitBufferRef.current.length >= 2) {
39353
+ commitMinuteDigits(digitBufferRef.current);
39354
+ onSegmentFocus("period");
39355
+ } else {
39356
+ const firstDigit = parseInt(key, 10);
39357
+ if (firstDigit >= 6) {
39358
+ commitMinuteDigits("0" + key);
39359
+ onSegmentFocus("period");
39360
+ } else {
39361
+ digitTimerRef.current = setTimeout(() => {
39362
+ commitMinuteDigits(digitBufferRef.current.padStart(2, "0"));
39363
+ onSegmentFocus("period");
39364
+ }, 800);
39365
+ }
39366
+ }
39367
+ }
39368
+ }
39369
+ }, [disabled, activeSegment, hour, minute, period, onHourChange, onMinuteChange, onPeriodChange, onSegmentFocus, onSegmentBlur, onTogglePopover, commitHourDigits, commitMinuteDigits]);
39370
+ const segmentClass = (seg) => `px-0.5 py-0.5 rounded cursor-pointer select-none transition-colors duration-150 ${activeSegment === seg ? "bg-blue-100 text-blue-700" : "text-gray-900 hover:bg-gray-100"}`;
39371
+ return /* @__PURE__ */ jsxRuntime.jsx(
39372
+ "div",
39373
+ {
39374
+ ref: containerRef,
39375
+ tabIndex: disabled ? -1 : 0,
39376
+ onFocus: () => {
39377
+ if (!activeSegment && !disabled) onSegmentFocus("hour");
39378
+ },
39379
+ onKeyDown: handleKeyDown,
39380
+ className: "flex items-center gap-0 outline-none flex-1 min-w-0",
39381
+ children: hasValue ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center text-sm tabular-nums text-gray-900", children: [
39382
+ /* @__PURE__ */ jsxRuntime.jsx(
39383
+ "span",
39384
+ {
39385
+ onClick: (e) => {
39386
+ e.stopPropagation();
39387
+ if (!disabled) onSegmentFocus("hour");
39388
+ },
39389
+ className: segmentClass("hour"),
39390
+ role: "spinbutton",
39391
+ "aria-label": "Hour",
39392
+ "aria-valuenow": hour,
39393
+ "aria-valuemin": 1,
39394
+ "aria-valuemax": 12,
39395
+ children: pad22(hour)
39396
+ }
39397
+ ),
39398
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 mx-px", children: ":" }),
39399
+ /* @__PURE__ */ jsxRuntime.jsx(
39400
+ "span",
39401
+ {
39402
+ onClick: (e) => {
39403
+ e.stopPropagation();
39404
+ if (!disabled) onSegmentFocus("minute");
39405
+ },
39406
+ className: segmentClass("minute"),
39407
+ role: "spinbutton",
39408
+ "aria-label": "Minute",
39409
+ "aria-valuenow": minute,
39410
+ "aria-valuemin": 0,
39411
+ "aria-valuemax": 59,
39412
+ children: pad22(minute)
39413
+ }
39414
+ ),
39415
+ /* @__PURE__ */ jsxRuntime.jsx(
39416
+ "span",
39417
+ {
39418
+ onClick: (e) => {
39419
+ e.stopPropagation();
39420
+ if (!disabled) onSegmentFocus("period");
39421
+ },
39422
+ className: `${segmentClass("period")} ml-1 text-gray-500`,
39423
+ role: "spinbutton",
39424
+ "aria-label": "AM or PM",
39425
+ "aria-valuenow": period === "AM" ? 0 : 1,
39426
+ "aria-valuemin": 0,
39427
+ "aria-valuemax": 1,
39428
+ children: period
39429
+ }
39430
+ )
39431
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-400 select-none", children: placeholder })
39432
+ }
39433
+ );
39434
+ };
39435
+ var SCROLLBAR_HIDE_ID = "tp-scrollbar-hide";
39436
+ var injectScrollbarHideCSS = () => {
39437
+ if (typeof document === "undefined") return;
39438
+ if (document.getElementById(SCROLLBAR_HIDE_ID)) return;
39439
+ const style = document.createElement("style");
39440
+ style.id = SCROLLBAR_HIDE_ID;
39441
+ style.textContent = ".tp-scroll-hide::-webkit-scrollbar{display:none}";
39442
+ document.head.appendChild(style);
39443
+ };
39444
+ var ITEM_H = 40;
39445
+ var VISIBLE = 5;
39446
+ var WHEEL_H = ITEM_H * VISIBLE;
39447
+ var PAD = Math.floor(VISIBLE / 2);
39448
+ var ScrollColumn = ({ items, selected, onSelect, width = 56 }) => {
39449
+ const elRef = React142.useRef(null);
39450
+ const suppressRef = React142.useRef(false);
39451
+ const snapTimerRef = React142.useRef(null);
39452
+ const mountedRef = React142.useRef(false);
39453
+ const dragging = React142.useRef(false);
39454
+ const dragStartY = React142.useRef(0);
39455
+ const dragStartScroll = React142.useRef(0);
39456
+ React142.useEffect(() => {
39457
+ injectScrollbarHideCSS();
39458
+ }, []);
39459
+ React142.useEffect(() => {
39460
+ const idx = items.findIndex((i) => i.value === selected);
39461
+ if (idx < 0 || !elRef.current || suppressRef.current) return;
39462
+ const target = idx * ITEM_H;
39463
+ if (!mountedRef.current) {
39464
+ suppressRef.current = true;
39465
+ elRef.current.scrollTop = target;
39466
+ setTimeout(() => {
39467
+ if (elRef.current) elRef.current.scrollTop = target;
39468
+ setTimeout(() => {
39469
+ suppressRef.current = false;
39470
+ mountedRef.current = true;
39471
+ }, 200);
39472
+ }, 30);
39473
+ } else {
39474
+ elRef.current.scrollTo({ top: target, behavior: "smooth" });
39475
+ }
39476
+ }, [selected, items]);
39477
+ const scheduleSnap = React142.useCallback(() => {
39478
+ if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
39479
+ snapTimerRef.current = setTimeout(() => {
39480
+ if (!elRef.current || suppressRef.current) return;
39481
+ const idx = Math.round(elRef.current.scrollTop / ITEM_H);
39482
+ const clamped = Math.max(0, Math.min(items.length - 1, idx));
39483
+ suppressRef.current = true;
39484
+ elRef.current.scrollTo({ top: clamped * ITEM_H, behavior: "smooth" });
39485
+ const item = items[clamped];
39486
+ if (item && item.value !== selected) onSelect(item.value);
39487
+ setTimeout(() => {
39488
+ suppressRef.current = false;
39489
+ }, 150);
39490
+ }, 100);
39491
+ }, [items, selected, onSelect]);
39492
+ const onScroll = React142.useCallback(() => {
39493
+ if (suppressRef.current) return;
39494
+ scheduleSnap();
39495
+ }, [scheduleSnap]);
39496
+ const onMouseDown = React142.useCallback((e) => {
39497
+ dragging.current = true;
39498
+ dragStartY.current = e.clientY;
39499
+ dragStartScroll.current = elRef.current?.scrollTop ?? 0;
39500
+ e.preventDefault();
39501
+ const onMove = (ev) => {
39502
+ if (!dragging.current || !elRef.current) return;
39503
+ const dy = dragStartY.current - ev.clientY;
39504
+ elRef.current.scrollTop = dragStartScroll.current + dy;
39505
+ };
39506
+ const onUp = () => {
39507
+ dragging.current = false;
39508
+ scheduleSnap();
39509
+ document.removeEventListener("mousemove", onMove);
39510
+ document.removeEventListener("mouseup", onUp);
39511
+ };
39512
+ document.addEventListener("mousemove", onMove);
39513
+ document.addEventListener("mouseup", onUp);
39514
+ }, [scheduleSnap]);
39515
+ const onTouchStart = React142.useCallback((e) => {
39516
+ dragging.current = true;
39517
+ dragStartY.current = e.touches[0].clientY;
39518
+ dragStartScroll.current = elRef.current?.scrollTop ?? 0;
39519
+ }, []);
39520
+ const onTouchMove = React142.useCallback((e) => {
39521
+ if (!dragging.current || !elRef.current) return;
39522
+ const dy = dragStartY.current - e.touches[0].clientY;
39523
+ elRef.current.scrollTop = dragStartScroll.current + dy;
39524
+ e.preventDefault();
39525
+ }, []);
39526
+ const onTouchEnd = React142.useCallback(() => {
39527
+ dragging.current = false;
39528
+ scheduleSnap();
39529
+ }, [scheduleSnap]);
39530
+ const handleClick = React142.useCallback((value, idx) => {
39531
+ onSelect(value);
39532
+ if (elRef.current) {
39533
+ suppressRef.current = true;
39534
+ elRef.current.scrollTo({ top: idx * ITEM_H, behavior: "smooth" });
39535
+ setTimeout(() => {
39536
+ suppressRef.current = false;
39537
+ }, 250);
39538
+ }
39539
+ }, [onSelect]);
39540
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative select-none", style: { width, height: WHEEL_H }, children: [
39541
+ /* @__PURE__ */ jsxRuntime.jsx(
39542
+ "div",
39543
+ {
39544
+ className: "absolute left-1 right-1 rounded-lg bg-blue-50/80 border border-blue-200/60 pointer-events-none z-0",
39545
+ style: { top: PAD * ITEM_H, height: ITEM_H }
39546
+ }
39547
+ ),
39548
+ /* @__PURE__ */ jsxRuntime.jsx(
39549
+ "div",
39550
+ {
39551
+ className: "absolute inset-x-0 top-0 pointer-events-none z-10",
39552
+ style: { height: ITEM_H * 1.8, background: "linear-gradient(to bottom, white 0%, rgba(255,255,255,0.85) 50%, transparent 100%)" }
39553
+ }
39554
+ ),
39555
+ /* @__PURE__ */ jsxRuntime.jsx(
39556
+ "div",
39557
+ {
39558
+ className: "absolute inset-x-0 bottom-0 pointer-events-none z-10",
39559
+ style: { height: ITEM_H * 1.8, background: "linear-gradient(to top, white 0%, rgba(255,255,255,0.85) 50%, transparent 100%)" }
39560
+ }
39561
+ ),
39562
+ /* @__PURE__ */ jsxRuntime.jsxs(
39563
+ "div",
39564
+ {
39565
+ ref: elRef,
39566
+ onScroll,
39567
+ onMouseDown,
39568
+ onTouchStart,
39569
+ onTouchMove,
39570
+ onTouchEnd,
39571
+ className: "h-full overflow-y-auto tp-scroll-hide relative z-[1] cursor-grab active:cursor-grabbing",
39572
+ style: { scrollbarWidth: "none", msOverflowStyle: "none" },
39573
+ role: "listbox",
39574
+ children: [
39575
+ Array.from({ length: PAD }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: ITEM_H }, "aria-hidden": true }, `pt-${i}`)),
39576
+ items.map((item, idx) => {
39577
+ const isSel = item.value === selected;
39578
+ return /* @__PURE__ */ jsxRuntime.jsx(
39579
+ "div",
39580
+ {
39581
+ onClick: () => handleClick(item.value, idx),
39582
+ className: `flex items-center justify-center transition-all duration-100 ${isSel ? "text-gray-900 font-semibold text-[15px]" : "text-gray-300 text-sm"}`,
39583
+ style: { height: ITEM_H },
39584
+ role: "option",
39585
+ "aria-selected": isSel,
39586
+ children: item.label
39587
+ },
39588
+ item.value
39589
+ );
39590
+ }),
39591
+ Array.from({ length: PAD }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: ITEM_H }, "aria-hidden": true }, `pb-${i}`))
39592
+ ]
39593
+ }
39594
+ )
39595
+ ] });
39596
+ };
39597
+ var HOURS = Array.from({ length: 12 }, (_, i) => ({ value: i + 1, label: (i + 1).toString() }));
39598
+ var MINUTES = Array.from({ length: 60 }, (_, i) => ({ value: i, label: pad22(i) }));
39599
+ var TimePopover = ({
39600
+ hour,
39601
+ minute,
39602
+ period,
39603
+ onHourChange,
39604
+ onMinuteChange,
39605
+ onPeriodChange,
39606
+ onClose,
39607
+ onNow
39608
+ }) => {
39609
+ const bandCenter = PAD * ITEM_H + ITEM_H / 2;
39610
+ return /* @__PURE__ */ jsxRuntime.jsxs(
39611
+ "div",
39612
+ {
39613
+ className: "absolute z-50 left-0 right-0 sm:left-auto sm:right-auto sm:w-[264px] mt-1.5 bg-white border border-gray-200 rounded-2xl shadow-[0_8px_30px_rgba(0,0,0,0.08),0_2px_8px_rgba(0,0,0,0.04)] overflow-hidden",
39614
+ role: "dialog",
39615
+ "aria-label": "Choose time",
39616
+ onClick: (e) => e.stopPropagation(),
39617
+ children: [
39618
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-center px-4 pt-4 pb-2", children: [
39619
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollColumn, { items: HOURS, selected: hour, onSelect: (v) => onHourChange(v), width: 52 }),
39620
+ /* @__PURE__ */ jsxRuntime.jsx(
39621
+ "div",
39622
+ {
39623
+ className: "text-gray-300 text-lg font-light flex-shrink-0 w-4 text-center select-none",
39624
+ style: { marginTop: bandCenter - 12 },
39625
+ children: ":"
39626
+ }
39627
+ ),
39628
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollColumn, { items: MINUTES, selected: minute, onSelect: (v) => onMinuteChange(v), width: 52 }),
39629
+ /* @__PURE__ */ jsxRuntime.jsx(
39630
+ "div",
39631
+ {
39632
+ className: "flex flex-col gap-1.5 ml-3 flex-shrink-0",
39633
+ style: { marginTop: bandCenter - 38 },
39634
+ children: ["AM", "PM"].map((p) => /* @__PURE__ */ jsxRuntime.jsx(
39635
+ "button",
39636
+ {
39637
+ type: "button",
39638
+ onClick: () => onPeriodChange(p),
39639
+ className: `w-12 h-8 rounded-lg text-[11px] font-bold tracking-widest transition-all duration-150 ${period === p ? "bg-blue-600 text-white shadow-sm" : "bg-gray-100 text-gray-400 hover:bg-gray-200 hover:text-gray-600"}`,
39640
+ "aria-pressed": period === p,
39641
+ children: p
39642
+ },
39643
+ p
39644
+ ))
39645
+ }
39646
+ )
39647
+ ] }),
39648
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-4 border-t border-gray-100" }),
39649
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-2.5", children: [
39650
+ /* @__PURE__ */ jsxRuntime.jsx(
39651
+ "button",
39652
+ {
39653
+ type: "button",
39654
+ onClick: onNow,
39655
+ className: "text-[13px] font-medium text-blue-600 hover:text-blue-700 transition-colors px-2.5 py-1 rounded-md hover:bg-blue-50",
39656
+ children: "Now"
39657
+ }
39658
+ ),
39659
+ /* @__PURE__ */ jsxRuntime.jsx(
39660
+ "button",
39661
+ {
39662
+ type: "button",
39663
+ onClick: onClose,
39664
+ className: "text-[13px] font-medium text-white bg-blue-600 hover:bg-blue-700 px-4 py-1.5 rounded-lg transition-colors shadow-sm",
39665
+ children: "Done"
39666
+ }
39667
+ )
39668
+ ] })
39669
+ ]
39670
+ }
39671
+ );
39672
+ };
39223
39673
  var TimePickerDropdown = ({
39224
39674
  value,
39225
39675
  onChange,
@@ -39227,124 +39677,155 @@ var TimePickerDropdown = ({
39227
39677
  className = "",
39228
39678
  disabled = false
39229
39679
  }) => {
39230
- const [isOpen, setIsOpen] = React142.useState(false);
39231
- const [searchTerm, setSearchTerm] = React142.useState("");
39232
- const dropdownRef = React142.useRef(null);
39233
- const inputRef = React142.useRef(null);
39234
- const generateTimeSlots = () => {
39235
- const slots = [];
39236
- for (let hour = 0; hour < 24; hour++) {
39237
- for (let minute = 0; minute < 60; minute += 10) {
39238
- const time24 = `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
39239
- const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
39240
- const ampm = hour < 12 ? "AM" : "PM";
39241
- const time12 = `${hour12}:${minute.toString().padStart(2, "0")} ${ampm}`;
39242
- slots.push({ value: time24, label: time12 });
39243
- }
39244
- }
39245
- return slots;
39246
- };
39247
- const timeSlots = generateTimeSlots();
39248
- const filteredSlots = timeSlots.filter(
39249
- (slot) => slot.label.toLowerCase().includes(searchTerm.toLowerCase())
39250
- );
39251
- const getDisplayValue = (value2) => {
39252
- if (!value2) return "";
39253
- const normalizedValue = value2.substring(0, 5);
39254
- const slot = timeSlots.find((s) => s.value === normalizedValue);
39255
- return slot ? slot.label : value2;
39256
- };
39680
+ const [isPopoverOpen, setIsPopoverOpen] = React142.useState(false);
39681
+ const [activeSegment, setActiveSegment] = React142.useState(null);
39682
+ const containerRef = React142.useRef(null);
39683
+ const normalizedValue = React142.useMemo(() => normalizeValue(value), [value]);
39684
+ const hasValue = normalizedValue.length === 5 && normalizedValue.includes(":");
39685
+ const { hour, minute, period } = React142.useMemo(() => to12h(normalizedValue), [normalizedValue]);
39257
39686
  React142.useEffect(() => {
39258
39687
  const handleClickOutside = (event) => {
39259
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
39260
- setIsOpen(false);
39261
- setSearchTerm("");
39688
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
39689
+ setIsPopoverOpen(false);
39690
+ setActiveSegment(null);
39262
39691
  }
39263
39692
  };
39264
39693
  document.addEventListener("mousedown", handleClickOutside);
39265
39694
  return () => document.removeEventListener("mousedown", handleClickOutside);
39266
39695
  }, []);
39267
- const handleKeyDown = (e) => {
39268
- if (e.key === "Escape") {
39269
- setIsOpen(false);
39270
- setSearchTerm("");
39271
- } else if (e.key === "Enter") {
39272
- e.preventDefault();
39273
- if (filteredSlots.length > 0) {
39274
- onChange(filteredSlots[0].value);
39275
- setIsOpen(false);
39276
- setSearchTerm("");
39277
- }
39696
+ const emitChange = React142.useCallback((h, m, p) => {
39697
+ onChange(to24h(h, m, p));
39698
+ }, [onChange]);
39699
+ const handleHourChange = React142.useCallback((h) => {
39700
+ emitChange(h, minute, period);
39701
+ }, [emitChange, minute, period]);
39702
+ const handleMinuteChange = React142.useCallback((m) => {
39703
+ emitChange(hour, m, period);
39704
+ }, [emitChange, hour, period]);
39705
+ const handlePeriodChange = React142.useCallback((p) => {
39706
+ emitChange(hour, minute, p);
39707
+ }, [emitChange, hour, minute]);
39708
+ const handleNow = React142.useCallback(() => {
39709
+ const now4 = /* @__PURE__ */ new Date();
39710
+ const h24 = now4.getHours();
39711
+ const m = now4.getMinutes();
39712
+ const p = h24 >= 12 ? "PM" : "AM";
39713
+ let h12 = h24 % 12;
39714
+ if (h12 === 0) h12 = 12;
39715
+ emitChange(h12, m, p);
39716
+ }, [emitChange]);
39717
+ const handleSegmentFocus = React142.useCallback((seg) => {
39718
+ setActiveSegment(seg);
39719
+ if (!hasValue) {
39720
+ onChange(to24h(12, 0, "AM"));
39721
+ }
39722
+ }, [hasValue, onChange]);
39723
+ const handleSegmentBlur = React142.useCallback(() => {
39724
+ setActiveSegment(null);
39725
+ }, []);
39726
+ const togglePopover = React142.useCallback(() => {
39727
+ if (disabled) return;
39728
+ setIsPopoverOpen((prev) => !prev);
39729
+ if (!hasValue) {
39730
+ onChange(to24h(12, 0, "AM"));
39278
39731
  }
39279
- };
39280
- const handleSelect = (timeValue) => {
39281
- onChange(timeValue);
39282
- setIsOpen(false);
39283
- setSearchTerm("");
39284
- };
39285
- const handleToggle = () => {
39732
+ }, [disabled, hasValue, onChange]);
39733
+ const handleContainerClick = React142.useCallback(() => {
39286
39734
  if (disabled) return;
39287
- setIsOpen(!isOpen);
39288
- if (!isOpen) {
39289
- setTimeout(() => inputRef.current?.focus(), 100);
39735
+ if (!activeSegment) {
39736
+ setActiveSegment("hour");
39737
+ if (!hasValue) {
39738
+ onChange(to24h(12, 0, "AM"));
39739
+ }
39290
39740
  }
39291
- };
39292
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className}`, ref: dropdownRef, children: [
39293
- /* @__PURE__ */ jsxRuntime.jsx(
39294
- "button",
39741
+ }, [disabled, activeSegment, hasValue, onChange]);
39742
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className}`, ref: containerRef, children: [
39743
+ /* @__PURE__ */ jsxRuntime.jsxs(
39744
+ "div",
39295
39745
  {
39296
- type: "button",
39297
- onClick: handleToggle,
39298
- disabled,
39746
+ onClick: handleContainerClick,
39299
39747
  className: `
39300
- w-full px-3 py-2 text-left bg-white border border-gray-300 rounded-md shadow-sm
39301
- focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
39302
- hover:border-gray-400 transition-colors duration-200
39303
- ${disabled ? "bg-gray-50 cursor-not-allowed" : "cursor-pointer"}
39304
- ${isOpen ? "ring-2 ring-blue-500 border-blue-500" : ""}
39748
+ w-full px-3 py-2 bg-white border rounded-md shadow-sm
39749
+ flex items-center gap-2
39750
+ transition-all duration-200
39751
+ ${disabled ? "bg-gray-50 cursor-not-allowed border-gray-200" : "cursor-pointer border-gray-300 hover:border-gray-400"}
39752
+ ${activeSegment || isPopoverOpen ? "ring-2 ring-blue-500 border-blue-500" : ""}
39305
39753
  `,
39306
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
39307
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
39308
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4 text-gray-400" }),
39309
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-sm ${value ? "text-gray-900" : "text-gray-500"}`, children: value ? getDisplayValue(value) : placeholder })
39310
- ] }),
39754
+ role: "group",
39755
+ "aria-label": "Time picker",
39756
+ children: [
39311
39757
  /* @__PURE__ */ jsxRuntime.jsx(
39312
- lucideReact.ChevronDown,
39758
+ "button",
39313
39759
  {
39314
- className: `h-4 w-4 text-gray-400 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`
39760
+ type: "button",
39761
+ onClick: (e) => {
39762
+ e.stopPropagation();
39763
+ togglePopover();
39764
+ },
39765
+ disabled,
39766
+ className: "text-gray-400 hover:text-blue-500 transition-colors flex-shrink-0 focus:outline-none",
39767
+ "aria-expanded": isPopoverOpen,
39768
+ "aria-haspopup": "dialog",
39769
+ "aria-label": "Open time picker",
39770
+ tabIndex: -1,
39771
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4" })
39772
+ }
39773
+ ),
39774
+ /* @__PURE__ */ jsxRuntime.jsx(
39775
+ SegmentedTimeInput,
39776
+ {
39777
+ hour,
39778
+ minute,
39779
+ period,
39780
+ hasValue,
39781
+ placeholder,
39782
+ disabled,
39783
+ activeSegment,
39784
+ onSegmentFocus: handleSegmentFocus,
39785
+ onSegmentBlur: handleSegmentBlur,
39786
+ onHourChange: handleHourChange,
39787
+ onMinuteChange: handleMinuteChange,
39788
+ onPeriodChange: handlePeriodChange,
39789
+ onTogglePopover: togglePopover,
39790
+ isPopoverOpen
39791
+ }
39792
+ ),
39793
+ /* @__PURE__ */ jsxRuntime.jsx(
39794
+ "button",
39795
+ {
39796
+ type: "button",
39797
+ onClick: (e) => {
39798
+ e.stopPropagation();
39799
+ togglePopover();
39800
+ },
39801
+ disabled,
39802
+ className: "text-gray-400 hover:text-gray-600 transition-transform duration-200 flex-shrink-0 focus:outline-none",
39803
+ tabIndex: -1,
39804
+ "aria-hidden": "true",
39805
+ children: /* @__PURE__ */ jsxRuntime.jsx(
39806
+ lucideReact.ChevronDown,
39807
+ {
39808
+ className: `h-4 w-4 transition-transform duration-200 ${isPopoverOpen ? "rotate-180" : ""}`
39809
+ }
39810
+ )
39315
39811
  }
39316
39812
  )
39317
- ] })
39813
+ ]
39318
39814
  }
39319
39815
  ),
39320
- 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: [
39321
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx(
39322
- "input",
39323
- {
39324
- ref: inputRef,
39325
- type: "text",
39326
- placeholder: "Search time...",
39327
- value: searchTerm,
39328
- onChange: (e) => setSearchTerm(e.target.value),
39329
- onKeyDown: handleKeyDown,
39330
- 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"
39331
- }
39332
- ) }),
39333
- /* @__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(
39334
- "button",
39335
- {
39336
- type: "button",
39337
- onClick: () => handleSelect(slot.value),
39338
- className: `
39339
- w-full px-3 py-2 text-left text-sm hover:bg-blue-50 hover:text-blue-600
39340
- transition-colors duration-150 border-b border-gray-100 last:border-b-0
39341
- ${slot.value === value ? "bg-blue-50 text-blue-600 font-medium" : "text-gray-700"}
39342
- `,
39343
- children: slot.label
39344
- },
39345
- slot.value
39346
- )) })
39347
- ] })
39816
+ isPopoverOpen && /* @__PURE__ */ jsxRuntime.jsx(
39817
+ TimePopover,
39818
+ {
39819
+ hour,
39820
+ minute,
39821
+ period,
39822
+ onHourChange: handleHourChange,
39823
+ onMinuteChange: handleMinuteChange,
39824
+ onPeriodChange: handlePeriodChange,
39825
+ onClose: () => setIsPopoverOpen(false),
39826
+ onNow: handleNow
39827
+ }
39828
+ )
39348
39829
  ] });
39349
39830
  };
39350
39831
  var SilentErrorBoundary = class extends React142__namespace.default.Component {
@@ -40082,6 +40563,7 @@ var AvatarUpload = ({
40082
40563
  )
40083
40564
  ] });
40084
40565
  };
40566
+ var CLIP_METADATA_PAGE_SIZE = 50;
40085
40567
  var parseCycleTime = (value) => {
40086
40568
  if (typeof value === "number" && Number.isFinite(value)) {
40087
40569
  return value;
@@ -40168,7 +40650,9 @@ var FileManagerFilters = ({
40168
40650
  className = "",
40169
40651
  targetCycleTime = null,
40170
40652
  idleTimeVlmEnabled = false,
40171
- showPercentileCycleFilters = true
40653
+ showPercentileCycleFilters = true,
40654
+ prefetchedClipMetadata,
40655
+ activeCategoryLoading
40172
40656
  }) => {
40173
40657
  const [expandedNodes, setExpandedNodes] = React142.useState(/* @__PURE__ */ new Set());
40174
40658
  const [startTime, setStartTime] = React142.useState("");
@@ -40189,10 +40673,72 @@ var FileManagerFilters = ({
40189
40673
  const [categoryPages, setCategoryPages] = React142.useState({});
40190
40674
  const [categoryHasMore, setCategoryHasMore] = React142.useState({});
40191
40675
  const [localClipClassifications, setLocalClipClassifications] = React142.useState({});
40676
+ const clipMetadataRef = React142.useRef({});
40677
+ const inFlightMetadataRequestsRef = React142.useRef(/* @__PURE__ */ new Set());
40192
40678
  const mergedClipClassifications = React142.useMemo(() => ({
40193
40679
  ...clipClassifications || {},
40194
40680
  ...localClipClassifications
40195
40681
  }), [clipClassifications, localClipClassifications]);
40682
+ React142.useEffect(() => {
40683
+ clipMetadataRef.current = clipMetadata;
40684
+ }, [clipMetadata]);
40685
+ const isExternallyManaged = prefetchedClipMetadata !== void 0 || activeCategoryLoading !== void 0;
40686
+ React142.useEffect(() => {
40687
+ if (!prefetchedClipMetadata) {
40688
+ return;
40689
+ }
40690
+ setClipMetadata((prev) => {
40691
+ let changed = false;
40692
+ const next = { ...prev };
40693
+ Object.entries(prefetchedClipMetadata).forEach(([categoryId, clips]) => {
40694
+ if (!Array.isArray(clips) || clips.length === 0) {
40695
+ return;
40696
+ }
40697
+ const previousClips = prev[categoryId] || [];
40698
+ const previousSignature = previousClips.map((clip) => clip.clipId || clip.id).join("|");
40699
+ const nextSignature = clips.map((clip) => clip.clipId || clip.id).join("|");
40700
+ if (previousSignature === nextSignature) {
40701
+ return;
40702
+ }
40703
+ next[categoryId] = clips;
40704
+ changed = true;
40705
+ });
40706
+ return changed ? next : prev;
40707
+ });
40708
+ setCategoryPages((prev) => {
40709
+ let changed = false;
40710
+ const next = { ...prev };
40711
+ Object.entries(prefetchedClipMetadata).forEach(([categoryId, clips]) => {
40712
+ if (!Array.isArray(clips) || clips.length === 0) {
40713
+ return;
40714
+ }
40715
+ const inferredPage = Math.max(1, Math.ceil(clips.length / CLIP_METADATA_PAGE_SIZE));
40716
+ if (next[categoryId] === inferredPage) {
40717
+ return;
40718
+ }
40719
+ next[categoryId] = inferredPage;
40720
+ changed = true;
40721
+ });
40722
+ return changed ? next : prev;
40723
+ });
40724
+ setCategoryHasMore((prev) => {
40725
+ let changed = false;
40726
+ const next = { ...prev };
40727
+ Object.entries(prefetchedClipMetadata).forEach(([categoryId, clips]) => {
40728
+ if (!Array.isArray(clips) || clips.length === 0) {
40729
+ return;
40730
+ }
40731
+ const knownTotal = typeof counts?.[categoryId] === "number" ? counts[categoryId] : null;
40732
+ const inferredHasMore = knownTotal !== null ? clips.length < knownTotal : prev[categoryId];
40733
+ if (typeof inferredHasMore !== "boolean" || next[categoryId] === inferredHasMore) {
40734
+ return;
40735
+ }
40736
+ next[categoryId] = inferredHasMore;
40737
+ changed = true;
40738
+ });
40739
+ return changed ? next : prev;
40740
+ });
40741
+ }, [prefetchedClipMetadata, counts]);
40196
40742
  const { state: filterState } = useClipFilter();
40197
40743
  const [percentileCounts, setPercentileCounts] = React142.useState({
40198
40744
  "fast-cycles": null,
@@ -40299,7 +40845,8 @@ var FileManagerFilters = ({
40299
40845
  shift: shift.toString(),
40300
40846
  category: categoryId,
40301
40847
  page,
40302
- limit: 50,
40848
+ limit: CLIP_METADATA_PAGE_SIZE,
40849
+ knownTotal: typeof counts?.[categoryId] === "number" ? counts[categoryId] : null,
40303
40850
  snapshotDateTime,
40304
40851
  snapshotClipId
40305
40852
  }),
@@ -40309,7 +40856,7 @@ var FileManagerFilters = ({
40309
40856
  throw new Error(`API error: ${response.status}`);
40310
40857
  }
40311
40858
  return response.json();
40312
- }, [workspaceId, date, shift, snapshotDateTime, snapshotClipId, supabase]);
40859
+ }, [workspaceId, date, shift, counts, snapshotDateTime, snapshotClipId, supabase]);
40313
40860
  const seedIdleClassifications = React142.useCallback(async (clips) => {
40314
40861
  if (!idleTimeVlmEnabled || clips.length === 0) {
40315
40862
  return;
@@ -40366,6 +40913,10 @@ var FileManagerFilters = ({
40366
40913
  return;
40367
40914
  }
40368
40915
  const loadingKey = `${categoryId}-${page}`;
40916
+ if (inFlightMetadataRequestsRef.current.has(loadingKey)) {
40917
+ return;
40918
+ }
40919
+ inFlightMetadataRequestsRef.current.add(loadingKey);
40369
40920
  setLoadingCategories((prev) => /* @__PURE__ */ new Set([...prev, loadingKey]));
40370
40921
  try {
40371
40922
  const data = await fetchClipMetadataPage(categoryId, page);
@@ -40382,6 +40933,7 @@ var FileManagerFilters = ({
40382
40933
  } catch (error) {
40383
40934
  console.error("[FileManager] Error fetching clip metadata:", error);
40384
40935
  } finally {
40936
+ inFlightMetadataRequestsRef.current.delete(loadingKey);
40385
40937
  setLoadingCategories((prev) => {
40386
40938
  const newSet = new Set(prev);
40387
40939
  newSet.delete(loadingKey);
@@ -40655,16 +41207,19 @@ var FileManagerFilters = ({
40655
41207
  React142.useEffect(() => {
40656
41208
  if (activeFilter) {
40657
41209
  setExpandedNodes((prev) => {
41210
+ if (prev.has(activeFilter)) {
41211
+ return prev;
41212
+ }
40658
41213
  const newExpanded = new Set(prev);
40659
41214
  newExpanded.add(activeFilter);
40660
41215
  return newExpanded;
40661
41216
  });
40662
41217
  const category = categories.find((cat) => cat.id === activeFilter);
40663
- if (category) {
41218
+ if (category && !isExternallyManaged && !clipMetadataRef.current[activeFilter]) {
40664
41219
  fetchClipMetadata(activeFilter, 1);
40665
41220
  }
40666
41221
  }
40667
- }, [activeFilter]);
41222
+ }, [activeFilter, isExternallyManaged]);
40668
41223
  React142.useEffect(() => {
40669
41224
  const handleEscape = (e) => {
40670
41225
  if (e.key === "Escape") {
@@ -40935,14 +41490,14 @@ var FileManagerFilters = ({
40935
41490
  } else {
40936
41491
  newExpanded.add(nodeId);
40937
41492
  const category = categories.find((cat) => cat.id === nodeId);
40938
- if (category && !clipMetadata[nodeId]) {
41493
+ if (category && !clipMetadata[nodeId] && !(isExternallyManaged && nodeId === activeFilter)) {
40939
41494
  console.log(`[FileManager] Fetching clips for expanded category: ${nodeId}`);
40940
41495
  fetchClipMetadata(nodeId, 1);
40941
41496
  }
40942
- if (showPercentileCycleFilters && nodeId === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
41497
+ if (!(isExternallyManaged && nodeId === activeFilter) && showPercentileCycleFilters && nodeId === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40943
41498
  fetchPercentileClips("fast-cycles");
40944
41499
  }
40945
- if (showPercentileCycleFilters && nodeId === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
41500
+ if (!(isExternallyManaged && nodeId === activeFilter) && showPercentileCycleFilters && nodeId === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40946
41501
  fetchPercentileClips("slow-cycles");
40947
41502
  }
40948
41503
  }
@@ -40956,14 +41511,14 @@ var FileManagerFilters = ({
40956
41511
  } else {
40957
41512
  newExpanded.add(node.id);
40958
41513
  const category = categories.find((cat) => cat.id === node.id);
40959
- if (category && !clipMetadata[node.id]) {
41514
+ if (category && !clipMetadata[node.id] && !isExternallyManaged) {
40960
41515
  console.log(`[FileManager] Fetching clips for expanded category: ${node.id}`);
40961
41516
  fetchClipMetadata(node.id, 1);
40962
41517
  }
40963
- if (showPercentileCycleFilters && node.id === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
41518
+ if (!isExternallyManaged && showPercentileCycleFilters && node.id === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40964
41519
  fetchPercentileClips("fast-cycles");
40965
41520
  }
40966
- if (showPercentileCycleFilters && node.id === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
41521
+ if (!isExternallyManaged && showPercentileCycleFilters && node.id === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40967
41522
  fetchPercentileClips("slow-cycles");
40968
41523
  }
40969
41524
  }
@@ -41016,6 +41571,9 @@ var FileManagerFilters = ({
41016
41571
  const isCurrentVideo = currentVideoId === node.id;
41017
41572
  const isCountUnknown = node.type === "percentile-category" && node.count === null;
41018
41573
  const hasChildren = isCountUnknown || (node.count || 0) > 0;
41574
+ const showExternalLoadingState = Boolean(
41575
+ activeCategoryLoading && isExpanded && node.id === activeFilter && (node.type === "category" || node.type === "percentile-category")
41576
+ );
41019
41577
  const colorClasses = node.color ? getColorClasses(node.color) : null;
41020
41578
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "select-none animate-in", children: [
41021
41579
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -41097,6 +41655,10 @@ var FileManagerFilters = ({
41097
41655
  ),
41098
41656
  hasChildren && isExpanded && (node.type === "category" || node.type === "percentile-category") && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 ml-3 animate-in border-l-2 border-slate-100 pl-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-h-64 overflow-y-auto space-y-1 scrollbar-thin scrollbar-track-slate-100 scrollbar-thumb-slate-300", children: [
41099
41657
  node.children.map((child) => renderNode(child, depth + 1)),
41658
+ showExternalLoadingState && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2 px-3 text-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center text-sm text-slate-500", children: [
41659
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
41660
+ "Loading clips..."
41661
+ ] }) }),
41100
41662
  loadingCategories.has(`${node.id}-${(categoryPages[node.id] || 0) + 1}`) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2 px-3 text-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center text-sm text-slate-500", children: [
41101
41663
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin mr-2 h-4 w-4 border-2 border-slate-300 border-t-blue-500 rounded-full" }),
41102
41664
  "Loading more clips..."
@@ -42569,6 +43131,7 @@ var BottlenecksContent = ({
42569
43131
  category: categoryId,
42570
43132
  page: 1,
42571
43133
  limit: 100,
43134
+ knownTotal: mergedCounts[categoryId] ?? null,
42572
43135
  snapshotDateTime,
42573
43136
  snapshotClipId
42574
43137
  }),
@@ -43223,6 +43786,14 @@ var BottlenecksContent = ({
43223
43786
  }
43224
43787
  return currentPosition;
43225
43788
  }, [activeFilter, categoryMetadata.length, currentMetadataIndex, currentPosition, isPercentileCategory]);
43789
+ const prefetchedExplorerMetadata = React142.useMemo(() => {
43790
+ if (!activeFilter || categoryMetadata.length === 0) {
43791
+ return void 0;
43792
+ }
43793
+ return {
43794
+ [activeFilter]: categoryMetadata
43795
+ };
43796
+ }, [activeFilter, categoryMetadata]);
43226
43797
  const classificationClipIds = React142.useMemo(() => {
43227
43798
  if (!idleTimeVlmEnabled) {
43228
43799
  return [];
@@ -43944,6 +44515,8 @@ var BottlenecksContent = ({
43944
44515
  clipClassifications,
43945
44516
  idleTimeVlmEnabled,
43946
44517
  showPercentileCycleFilters: isFastSlowClipFiltersEnabled,
44518
+ prefetchedClipMetadata: prefetchedExplorerMetadata,
44519
+ activeCategoryLoading: isCategoryLoading,
43947
44520
  onFilterChange: (filterId) => {
43948
44521
  updateActiveFilter(filterId);
43949
44522
  const category = categoriesToShow.find((cat) => cat.type === filterId);