@optifye/dashboard-core 6.11.27 → 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.mjs CHANGED
@@ -39191,6 +39191,456 @@ var MetricCard2 = ({ title, value, unit = "", trend = null }) => {
39191
39191
  ] });
39192
39192
  };
39193
39193
  var MetricCard_default = MetricCard2;
39194
+ var normalizeValue = (v) => {
39195
+ if (!v) return "";
39196
+ return v.substring(0, 5);
39197
+ };
39198
+ var to12h = (value24) => {
39199
+ const norm = normalizeValue(value24);
39200
+ if (!norm || norm.length < 5) return { hour: 12, minute: 0, period: "AM" };
39201
+ const [h, m] = norm.split(":").map(Number);
39202
+ const period = h >= 12 ? "PM" : "AM";
39203
+ let hour12 = h % 12;
39204
+ if (hour12 === 0) hour12 = 12;
39205
+ return { hour: hour12, minute: isNaN(m) ? 0 : m, period };
39206
+ };
39207
+ var to24h = (hour12, minute, period) => {
39208
+ let h = hour12;
39209
+ if (period === "AM" && h === 12) h = 0;
39210
+ if (period === "PM" && h !== 12) h += 12;
39211
+ return `${h.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
39212
+ };
39213
+ var pad22 = (n) => n.toString().padStart(2, "0");
39214
+ var SegmentedTimeInput = ({
39215
+ hour,
39216
+ minute,
39217
+ period,
39218
+ hasValue,
39219
+ placeholder,
39220
+ disabled,
39221
+ activeSegment,
39222
+ onSegmentFocus,
39223
+ onSegmentBlur,
39224
+ onHourChange,
39225
+ onMinuteChange,
39226
+ onPeriodChange,
39227
+ onTogglePopover,
39228
+ isPopoverOpen
39229
+ }) => {
39230
+ const containerRef = useRef(null);
39231
+ const digitBufferRef = useRef("");
39232
+ const digitTimerRef = useRef(null);
39233
+ useEffect(() => {
39234
+ digitBufferRef.current = "";
39235
+ if (digitTimerRef.current) clearTimeout(digitTimerRef.current);
39236
+ }, [activeSegment]);
39237
+ const commitHourDigits = useCallback((digits) => {
39238
+ const val = parseInt(digits, 10);
39239
+ if (val >= 1 && val <= 12) onHourChange(val);
39240
+ else if (val === 0) onHourChange(12);
39241
+ digitBufferRef.current = "";
39242
+ }, [onHourChange]);
39243
+ const commitMinuteDigits = useCallback((digits) => {
39244
+ const val = parseInt(digits, 10);
39245
+ onMinuteChange(Math.min(59, Math.max(0, val)));
39246
+ digitBufferRef.current = "";
39247
+ }, [onMinuteChange]);
39248
+ const handleKeyDown = useCallback((e) => {
39249
+ if (disabled || !activeSegment) return;
39250
+ const key = e.key;
39251
+ if (key === "Tab") {
39252
+ if (e.shiftKey) {
39253
+ if (activeSegment === "minute") {
39254
+ e.preventDefault();
39255
+ onSegmentFocus("hour");
39256
+ } else if (activeSegment === "period") {
39257
+ e.preventDefault();
39258
+ onSegmentFocus("minute");
39259
+ }
39260
+ } else {
39261
+ if (activeSegment === "hour") {
39262
+ e.preventDefault();
39263
+ onSegmentFocus("minute");
39264
+ } else if (activeSegment === "minute") {
39265
+ e.preventDefault();
39266
+ onSegmentFocus("period");
39267
+ }
39268
+ }
39269
+ return;
39270
+ }
39271
+ e.preventDefault();
39272
+ if (key === "ArrowUp" || key === "ArrowDown") {
39273
+ const delta = key === "ArrowUp" ? 1 : -1;
39274
+ if (activeSegment === "hour") {
39275
+ let newH = hour + delta;
39276
+ if (newH > 12) newH = 1;
39277
+ if (newH < 1) newH = 12;
39278
+ onHourChange(newH);
39279
+ } else if (activeSegment === "minute") {
39280
+ let newM = minute + delta;
39281
+ if (newM > 59) newM = 0;
39282
+ if (newM < 0) newM = 59;
39283
+ onMinuteChange(newM);
39284
+ } else if (activeSegment === "period") {
39285
+ onPeriodChange(period === "AM" ? "PM" : "AM");
39286
+ }
39287
+ return;
39288
+ }
39289
+ if (key === "Enter" || key === " ") {
39290
+ onTogglePopover();
39291
+ return;
39292
+ }
39293
+ if (key === "Escape") {
39294
+ onSegmentBlur();
39295
+ return;
39296
+ }
39297
+ if (activeSegment === "period") {
39298
+ if (key.toLowerCase() === "a") onPeriodChange("AM");
39299
+ else if (key.toLowerCase() === "p") onPeriodChange("PM");
39300
+ return;
39301
+ }
39302
+ if (key >= "0" && key <= "9") {
39303
+ if (digitTimerRef.current) clearTimeout(digitTimerRef.current);
39304
+ if (activeSegment === "hour") {
39305
+ digitBufferRef.current += key;
39306
+ if (digitBufferRef.current.length >= 2) {
39307
+ commitHourDigits(digitBufferRef.current);
39308
+ onSegmentFocus("minute");
39309
+ } else {
39310
+ const firstDigit = parseInt(key, 10);
39311
+ if (firstDigit >= 2) {
39312
+ commitHourDigits(digitBufferRef.current);
39313
+ onSegmentFocus("minute");
39314
+ } else {
39315
+ digitTimerRef.current = setTimeout(() => {
39316
+ commitHourDigits(digitBufferRef.current);
39317
+ onSegmentFocus("minute");
39318
+ }, 800);
39319
+ }
39320
+ }
39321
+ } else if (activeSegment === "minute") {
39322
+ digitBufferRef.current += key;
39323
+ if (digitBufferRef.current.length >= 2) {
39324
+ commitMinuteDigits(digitBufferRef.current);
39325
+ onSegmentFocus("period");
39326
+ } else {
39327
+ const firstDigit = parseInt(key, 10);
39328
+ if (firstDigit >= 6) {
39329
+ commitMinuteDigits("0" + key);
39330
+ onSegmentFocus("period");
39331
+ } else {
39332
+ digitTimerRef.current = setTimeout(() => {
39333
+ commitMinuteDigits(digitBufferRef.current.padStart(2, "0"));
39334
+ onSegmentFocus("period");
39335
+ }, 800);
39336
+ }
39337
+ }
39338
+ }
39339
+ }
39340
+ }, [disabled, activeSegment, hour, minute, period, onHourChange, onMinuteChange, onPeriodChange, onSegmentFocus, onSegmentBlur, onTogglePopover, commitHourDigits, commitMinuteDigits]);
39341
+ 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"}`;
39342
+ return /* @__PURE__ */ jsx(
39343
+ "div",
39344
+ {
39345
+ ref: containerRef,
39346
+ tabIndex: disabled ? -1 : 0,
39347
+ onFocus: () => {
39348
+ if (!activeSegment && !disabled) onSegmentFocus("hour");
39349
+ },
39350
+ onKeyDown: handleKeyDown,
39351
+ className: "flex items-center gap-0 outline-none flex-1 min-w-0",
39352
+ children: hasValue ? /* @__PURE__ */ jsxs("div", { className: "flex items-center text-sm tabular-nums text-gray-900", children: [
39353
+ /* @__PURE__ */ jsx(
39354
+ "span",
39355
+ {
39356
+ onClick: (e) => {
39357
+ e.stopPropagation();
39358
+ if (!disabled) onSegmentFocus("hour");
39359
+ },
39360
+ className: segmentClass("hour"),
39361
+ role: "spinbutton",
39362
+ "aria-label": "Hour",
39363
+ "aria-valuenow": hour,
39364
+ "aria-valuemin": 1,
39365
+ "aria-valuemax": 12,
39366
+ children: pad22(hour)
39367
+ }
39368
+ ),
39369
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400 mx-px", children: ":" }),
39370
+ /* @__PURE__ */ jsx(
39371
+ "span",
39372
+ {
39373
+ onClick: (e) => {
39374
+ e.stopPropagation();
39375
+ if (!disabled) onSegmentFocus("minute");
39376
+ },
39377
+ className: segmentClass("minute"),
39378
+ role: "spinbutton",
39379
+ "aria-label": "Minute",
39380
+ "aria-valuenow": minute,
39381
+ "aria-valuemin": 0,
39382
+ "aria-valuemax": 59,
39383
+ children: pad22(minute)
39384
+ }
39385
+ ),
39386
+ /* @__PURE__ */ jsx(
39387
+ "span",
39388
+ {
39389
+ onClick: (e) => {
39390
+ e.stopPropagation();
39391
+ if (!disabled) onSegmentFocus("period");
39392
+ },
39393
+ className: `${segmentClass("period")} ml-1 text-gray-500`,
39394
+ role: "spinbutton",
39395
+ "aria-label": "AM or PM",
39396
+ "aria-valuenow": period === "AM" ? 0 : 1,
39397
+ "aria-valuemin": 0,
39398
+ "aria-valuemax": 1,
39399
+ children: period
39400
+ }
39401
+ )
39402
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-400 select-none", children: placeholder })
39403
+ }
39404
+ );
39405
+ };
39406
+ var SCROLLBAR_HIDE_ID = "tp-scrollbar-hide";
39407
+ var injectScrollbarHideCSS = () => {
39408
+ if (typeof document === "undefined") return;
39409
+ if (document.getElementById(SCROLLBAR_HIDE_ID)) return;
39410
+ const style = document.createElement("style");
39411
+ style.id = SCROLLBAR_HIDE_ID;
39412
+ style.textContent = ".tp-scroll-hide::-webkit-scrollbar{display:none}";
39413
+ document.head.appendChild(style);
39414
+ };
39415
+ var ITEM_H = 40;
39416
+ var VISIBLE = 5;
39417
+ var WHEEL_H = ITEM_H * VISIBLE;
39418
+ var PAD = Math.floor(VISIBLE / 2);
39419
+ var ScrollColumn = ({ items, selected, onSelect, width = 56 }) => {
39420
+ const elRef = useRef(null);
39421
+ const suppressRef = useRef(false);
39422
+ const snapTimerRef = useRef(null);
39423
+ const mountedRef = useRef(false);
39424
+ const dragging = useRef(false);
39425
+ const dragStartY = useRef(0);
39426
+ const dragStartScroll = useRef(0);
39427
+ useEffect(() => {
39428
+ injectScrollbarHideCSS();
39429
+ }, []);
39430
+ useEffect(() => {
39431
+ const idx = items.findIndex((i) => i.value === selected);
39432
+ if (idx < 0 || !elRef.current || suppressRef.current) return;
39433
+ const target = idx * ITEM_H;
39434
+ if (!mountedRef.current) {
39435
+ suppressRef.current = true;
39436
+ elRef.current.scrollTop = target;
39437
+ setTimeout(() => {
39438
+ if (elRef.current) elRef.current.scrollTop = target;
39439
+ setTimeout(() => {
39440
+ suppressRef.current = false;
39441
+ mountedRef.current = true;
39442
+ }, 200);
39443
+ }, 30);
39444
+ } else {
39445
+ elRef.current.scrollTo({ top: target, behavior: "smooth" });
39446
+ }
39447
+ }, [selected, items]);
39448
+ const scheduleSnap = useCallback(() => {
39449
+ if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
39450
+ snapTimerRef.current = setTimeout(() => {
39451
+ if (!elRef.current || suppressRef.current) return;
39452
+ const idx = Math.round(elRef.current.scrollTop / ITEM_H);
39453
+ const clamped = Math.max(0, Math.min(items.length - 1, idx));
39454
+ suppressRef.current = true;
39455
+ elRef.current.scrollTo({ top: clamped * ITEM_H, behavior: "smooth" });
39456
+ const item = items[clamped];
39457
+ if (item && item.value !== selected) onSelect(item.value);
39458
+ setTimeout(() => {
39459
+ suppressRef.current = false;
39460
+ }, 150);
39461
+ }, 100);
39462
+ }, [items, selected, onSelect]);
39463
+ const onScroll = useCallback(() => {
39464
+ if (suppressRef.current) return;
39465
+ scheduleSnap();
39466
+ }, [scheduleSnap]);
39467
+ const onMouseDown = useCallback((e) => {
39468
+ dragging.current = true;
39469
+ dragStartY.current = e.clientY;
39470
+ dragStartScroll.current = elRef.current?.scrollTop ?? 0;
39471
+ e.preventDefault();
39472
+ const onMove = (ev) => {
39473
+ if (!dragging.current || !elRef.current) return;
39474
+ const dy = dragStartY.current - ev.clientY;
39475
+ elRef.current.scrollTop = dragStartScroll.current + dy;
39476
+ };
39477
+ const onUp = () => {
39478
+ dragging.current = false;
39479
+ scheduleSnap();
39480
+ document.removeEventListener("mousemove", onMove);
39481
+ document.removeEventListener("mouseup", onUp);
39482
+ };
39483
+ document.addEventListener("mousemove", onMove);
39484
+ document.addEventListener("mouseup", onUp);
39485
+ }, [scheduleSnap]);
39486
+ const onTouchStart = useCallback((e) => {
39487
+ dragging.current = true;
39488
+ dragStartY.current = e.touches[0].clientY;
39489
+ dragStartScroll.current = elRef.current?.scrollTop ?? 0;
39490
+ }, []);
39491
+ const onTouchMove = useCallback((e) => {
39492
+ if (!dragging.current || !elRef.current) return;
39493
+ const dy = dragStartY.current - e.touches[0].clientY;
39494
+ elRef.current.scrollTop = dragStartScroll.current + dy;
39495
+ e.preventDefault();
39496
+ }, []);
39497
+ const onTouchEnd = useCallback(() => {
39498
+ dragging.current = false;
39499
+ scheduleSnap();
39500
+ }, [scheduleSnap]);
39501
+ const handleClick = useCallback((value, idx) => {
39502
+ onSelect(value);
39503
+ if (elRef.current) {
39504
+ suppressRef.current = true;
39505
+ elRef.current.scrollTo({ top: idx * ITEM_H, behavior: "smooth" });
39506
+ setTimeout(() => {
39507
+ suppressRef.current = false;
39508
+ }, 250);
39509
+ }
39510
+ }, [onSelect]);
39511
+ return /* @__PURE__ */ jsxs("div", { className: "relative select-none", style: { width, height: WHEEL_H }, children: [
39512
+ /* @__PURE__ */ jsx(
39513
+ "div",
39514
+ {
39515
+ className: "absolute left-1 right-1 rounded-lg bg-blue-50/80 border border-blue-200/60 pointer-events-none z-0",
39516
+ style: { top: PAD * ITEM_H, height: ITEM_H }
39517
+ }
39518
+ ),
39519
+ /* @__PURE__ */ jsx(
39520
+ "div",
39521
+ {
39522
+ className: "absolute inset-x-0 top-0 pointer-events-none z-10",
39523
+ style: { height: ITEM_H * 1.8, background: "linear-gradient(to bottom, white 0%, rgba(255,255,255,0.85) 50%, transparent 100%)" }
39524
+ }
39525
+ ),
39526
+ /* @__PURE__ */ jsx(
39527
+ "div",
39528
+ {
39529
+ className: "absolute inset-x-0 bottom-0 pointer-events-none z-10",
39530
+ style: { height: ITEM_H * 1.8, background: "linear-gradient(to top, white 0%, rgba(255,255,255,0.85) 50%, transparent 100%)" }
39531
+ }
39532
+ ),
39533
+ /* @__PURE__ */ jsxs(
39534
+ "div",
39535
+ {
39536
+ ref: elRef,
39537
+ onScroll,
39538
+ onMouseDown,
39539
+ onTouchStart,
39540
+ onTouchMove,
39541
+ onTouchEnd,
39542
+ className: "h-full overflow-y-auto tp-scroll-hide relative z-[1] cursor-grab active:cursor-grabbing",
39543
+ style: { scrollbarWidth: "none", msOverflowStyle: "none" },
39544
+ role: "listbox",
39545
+ children: [
39546
+ Array.from({ length: PAD }).map((_, i) => /* @__PURE__ */ jsx("div", { style: { height: ITEM_H }, "aria-hidden": true }, `pt-${i}`)),
39547
+ items.map((item, idx) => {
39548
+ const isSel = item.value === selected;
39549
+ return /* @__PURE__ */ jsx(
39550
+ "div",
39551
+ {
39552
+ onClick: () => handleClick(item.value, idx),
39553
+ className: `flex items-center justify-center transition-all duration-100 ${isSel ? "text-gray-900 font-semibold text-[15px]" : "text-gray-300 text-sm"}`,
39554
+ style: { height: ITEM_H },
39555
+ role: "option",
39556
+ "aria-selected": isSel,
39557
+ children: item.label
39558
+ },
39559
+ item.value
39560
+ );
39561
+ }),
39562
+ Array.from({ length: PAD }).map((_, i) => /* @__PURE__ */ jsx("div", { style: { height: ITEM_H }, "aria-hidden": true }, `pb-${i}`))
39563
+ ]
39564
+ }
39565
+ )
39566
+ ] });
39567
+ };
39568
+ var HOURS = Array.from({ length: 12 }, (_, i) => ({ value: i + 1, label: (i + 1).toString() }));
39569
+ var MINUTES = Array.from({ length: 60 }, (_, i) => ({ value: i, label: pad22(i) }));
39570
+ var TimePopover = ({
39571
+ hour,
39572
+ minute,
39573
+ period,
39574
+ onHourChange,
39575
+ onMinuteChange,
39576
+ onPeriodChange,
39577
+ onClose,
39578
+ onNow
39579
+ }) => {
39580
+ const bandCenter = PAD * ITEM_H + ITEM_H / 2;
39581
+ return /* @__PURE__ */ jsxs(
39582
+ "div",
39583
+ {
39584
+ 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",
39585
+ role: "dialog",
39586
+ "aria-label": "Choose time",
39587
+ onClick: (e) => e.stopPropagation(),
39588
+ children: [
39589
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-center px-4 pt-4 pb-2", children: [
39590
+ /* @__PURE__ */ jsx(ScrollColumn, { items: HOURS, selected: hour, onSelect: (v) => onHourChange(v), width: 52 }),
39591
+ /* @__PURE__ */ jsx(
39592
+ "div",
39593
+ {
39594
+ className: "text-gray-300 text-lg font-light flex-shrink-0 w-4 text-center select-none",
39595
+ style: { marginTop: bandCenter - 12 },
39596
+ children: ":"
39597
+ }
39598
+ ),
39599
+ /* @__PURE__ */ jsx(ScrollColumn, { items: MINUTES, selected: minute, onSelect: (v) => onMinuteChange(v), width: 52 }),
39600
+ /* @__PURE__ */ jsx(
39601
+ "div",
39602
+ {
39603
+ className: "flex flex-col gap-1.5 ml-3 flex-shrink-0",
39604
+ style: { marginTop: bandCenter - 38 },
39605
+ children: ["AM", "PM"].map((p) => /* @__PURE__ */ jsx(
39606
+ "button",
39607
+ {
39608
+ type: "button",
39609
+ onClick: () => onPeriodChange(p),
39610
+ 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"}`,
39611
+ "aria-pressed": period === p,
39612
+ children: p
39613
+ },
39614
+ p
39615
+ ))
39616
+ }
39617
+ )
39618
+ ] }),
39619
+ /* @__PURE__ */ jsx("div", { className: "mx-4 border-t border-gray-100" }),
39620
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2.5", children: [
39621
+ /* @__PURE__ */ jsx(
39622
+ "button",
39623
+ {
39624
+ type: "button",
39625
+ onClick: onNow,
39626
+ 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",
39627
+ children: "Now"
39628
+ }
39629
+ ),
39630
+ /* @__PURE__ */ jsx(
39631
+ "button",
39632
+ {
39633
+ type: "button",
39634
+ onClick: onClose,
39635
+ 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",
39636
+ children: "Done"
39637
+ }
39638
+ )
39639
+ ] })
39640
+ ]
39641
+ }
39642
+ );
39643
+ };
39194
39644
  var TimePickerDropdown = ({
39195
39645
  value,
39196
39646
  onChange,
@@ -39198,124 +39648,155 @@ var TimePickerDropdown = ({
39198
39648
  className = "",
39199
39649
  disabled = false
39200
39650
  }) => {
39201
- const [isOpen, setIsOpen] = useState(false);
39202
- const [searchTerm, setSearchTerm] = useState("");
39203
- const dropdownRef = useRef(null);
39204
- const inputRef = useRef(null);
39205
- const generateTimeSlots = () => {
39206
- const slots = [];
39207
- for (let hour = 0; hour < 24; hour++) {
39208
- for (let minute = 0; minute < 60; minute += 10) {
39209
- const time24 = `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
39210
- const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
39211
- const ampm = hour < 12 ? "AM" : "PM";
39212
- const time12 = `${hour12}:${minute.toString().padStart(2, "0")} ${ampm}`;
39213
- slots.push({ value: time24, label: time12 });
39214
- }
39215
- }
39216
- return slots;
39217
- };
39218
- const timeSlots = generateTimeSlots();
39219
- const filteredSlots = timeSlots.filter(
39220
- (slot) => slot.label.toLowerCase().includes(searchTerm.toLowerCase())
39221
- );
39222
- const getDisplayValue = (value2) => {
39223
- if (!value2) return "";
39224
- const normalizedValue = value2.substring(0, 5);
39225
- const slot = timeSlots.find((s) => s.value === normalizedValue);
39226
- return slot ? slot.label : value2;
39227
- };
39651
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
39652
+ const [activeSegment, setActiveSegment] = useState(null);
39653
+ const containerRef = useRef(null);
39654
+ const normalizedValue = useMemo(() => normalizeValue(value), [value]);
39655
+ const hasValue = normalizedValue.length === 5 && normalizedValue.includes(":");
39656
+ const { hour, minute, period } = useMemo(() => to12h(normalizedValue), [normalizedValue]);
39228
39657
  useEffect(() => {
39229
39658
  const handleClickOutside = (event) => {
39230
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
39231
- setIsOpen(false);
39232
- setSearchTerm("");
39659
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
39660
+ setIsPopoverOpen(false);
39661
+ setActiveSegment(null);
39233
39662
  }
39234
39663
  };
39235
39664
  document.addEventListener("mousedown", handleClickOutside);
39236
39665
  return () => document.removeEventListener("mousedown", handleClickOutside);
39237
39666
  }, []);
39238
- const handleKeyDown = (e) => {
39239
- if (e.key === "Escape") {
39240
- setIsOpen(false);
39241
- setSearchTerm("");
39242
- } else if (e.key === "Enter") {
39243
- e.preventDefault();
39244
- if (filteredSlots.length > 0) {
39245
- onChange(filteredSlots[0].value);
39246
- setIsOpen(false);
39247
- setSearchTerm("");
39248
- }
39667
+ const emitChange = useCallback((h, m, p) => {
39668
+ onChange(to24h(h, m, p));
39669
+ }, [onChange]);
39670
+ const handleHourChange = useCallback((h) => {
39671
+ emitChange(h, minute, period);
39672
+ }, [emitChange, minute, period]);
39673
+ const handleMinuteChange = useCallback((m) => {
39674
+ emitChange(hour, m, period);
39675
+ }, [emitChange, hour, period]);
39676
+ const handlePeriodChange = useCallback((p) => {
39677
+ emitChange(hour, minute, p);
39678
+ }, [emitChange, hour, minute]);
39679
+ const handleNow = useCallback(() => {
39680
+ const now4 = /* @__PURE__ */ new Date();
39681
+ const h24 = now4.getHours();
39682
+ const m = now4.getMinutes();
39683
+ const p = h24 >= 12 ? "PM" : "AM";
39684
+ let h12 = h24 % 12;
39685
+ if (h12 === 0) h12 = 12;
39686
+ emitChange(h12, m, p);
39687
+ }, [emitChange]);
39688
+ const handleSegmentFocus = useCallback((seg) => {
39689
+ setActiveSegment(seg);
39690
+ if (!hasValue) {
39691
+ onChange(to24h(12, 0, "AM"));
39692
+ }
39693
+ }, [hasValue, onChange]);
39694
+ const handleSegmentBlur = useCallback(() => {
39695
+ setActiveSegment(null);
39696
+ }, []);
39697
+ const togglePopover = useCallback(() => {
39698
+ if (disabled) return;
39699
+ setIsPopoverOpen((prev) => !prev);
39700
+ if (!hasValue) {
39701
+ onChange(to24h(12, 0, "AM"));
39249
39702
  }
39250
- };
39251
- const handleSelect = (timeValue) => {
39252
- onChange(timeValue);
39253
- setIsOpen(false);
39254
- setSearchTerm("");
39255
- };
39256
- const handleToggle = () => {
39703
+ }, [disabled, hasValue, onChange]);
39704
+ const handleContainerClick = useCallback(() => {
39257
39705
  if (disabled) return;
39258
- setIsOpen(!isOpen);
39259
- if (!isOpen) {
39260
- setTimeout(() => inputRef.current?.focus(), 100);
39706
+ if (!activeSegment) {
39707
+ setActiveSegment("hour");
39708
+ if (!hasValue) {
39709
+ onChange(to24h(12, 0, "AM"));
39710
+ }
39261
39711
  }
39262
- };
39263
- return /* @__PURE__ */ jsxs("div", { className: `relative ${className}`, ref: dropdownRef, children: [
39264
- /* @__PURE__ */ jsx(
39265
- "button",
39712
+ }, [disabled, activeSegment, hasValue, onChange]);
39713
+ return /* @__PURE__ */ jsxs("div", { className: `relative ${className}`, ref: containerRef, children: [
39714
+ /* @__PURE__ */ jsxs(
39715
+ "div",
39266
39716
  {
39267
- type: "button",
39268
- onClick: handleToggle,
39269
- disabled,
39717
+ onClick: handleContainerClick,
39270
39718
  className: `
39271
- w-full px-3 py-2 text-left bg-white border border-gray-300 rounded-md shadow-sm
39272
- focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
39273
- hover:border-gray-400 transition-colors duration-200
39274
- ${disabled ? "bg-gray-50 cursor-not-allowed" : "cursor-pointer"}
39275
- ${isOpen ? "ring-2 ring-blue-500 border-blue-500" : ""}
39719
+ w-full px-3 py-2 bg-white border rounded-md shadow-sm
39720
+ flex items-center gap-2
39721
+ transition-all duration-200
39722
+ ${disabled ? "bg-gray-50 cursor-not-allowed border-gray-200" : "cursor-pointer border-gray-300 hover:border-gray-400"}
39723
+ ${activeSegment || isPopoverOpen ? "ring-2 ring-blue-500 border-blue-500" : ""}
39276
39724
  `,
39277
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
39278
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
39279
- /* @__PURE__ */ jsx(Clock, { className: "h-4 w-4 text-gray-400" }),
39280
- /* @__PURE__ */ jsx("span", { className: `text-sm ${value ? "text-gray-900" : "text-gray-500"}`, children: value ? getDisplayValue(value) : placeholder })
39281
- ] }),
39725
+ role: "group",
39726
+ "aria-label": "Time picker",
39727
+ children: [
39282
39728
  /* @__PURE__ */ jsx(
39283
- ChevronDown,
39729
+ "button",
39284
39730
  {
39285
- className: `h-4 w-4 text-gray-400 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`
39731
+ type: "button",
39732
+ onClick: (e) => {
39733
+ e.stopPropagation();
39734
+ togglePopover();
39735
+ },
39736
+ disabled,
39737
+ className: "text-gray-400 hover:text-blue-500 transition-colors flex-shrink-0 focus:outline-none",
39738
+ "aria-expanded": isPopoverOpen,
39739
+ "aria-haspopup": "dialog",
39740
+ "aria-label": "Open time picker",
39741
+ tabIndex: -1,
39742
+ children: /* @__PURE__ */ jsx(Clock, { className: "h-4 w-4" })
39743
+ }
39744
+ ),
39745
+ /* @__PURE__ */ jsx(
39746
+ SegmentedTimeInput,
39747
+ {
39748
+ hour,
39749
+ minute,
39750
+ period,
39751
+ hasValue,
39752
+ placeholder,
39753
+ disabled,
39754
+ activeSegment,
39755
+ onSegmentFocus: handleSegmentFocus,
39756
+ onSegmentBlur: handleSegmentBlur,
39757
+ onHourChange: handleHourChange,
39758
+ onMinuteChange: handleMinuteChange,
39759
+ onPeriodChange: handlePeriodChange,
39760
+ onTogglePopover: togglePopover,
39761
+ isPopoverOpen
39762
+ }
39763
+ ),
39764
+ /* @__PURE__ */ jsx(
39765
+ "button",
39766
+ {
39767
+ type: "button",
39768
+ onClick: (e) => {
39769
+ e.stopPropagation();
39770
+ togglePopover();
39771
+ },
39772
+ disabled,
39773
+ className: "text-gray-400 hover:text-gray-600 transition-transform duration-200 flex-shrink-0 focus:outline-none",
39774
+ tabIndex: -1,
39775
+ "aria-hidden": "true",
39776
+ children: /* @__PURE__ */ jsx(
39777
+ ChevronDown,
39778
+ {
39779
+ className: `h-4 w-4 transition-transform duration-200 ${isPopoverOpen ? "rotate-180" : ""}`
39780
+ }
39781
+ )
39286
39782
  }
39287
39783
  )
39288
- ] })
39784
+ ]
39289
39785
  }
39290
39786
  ),
39291
- isOpen && /* @__PURE__ */ 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: [
39292
- /* @__PURE__ */ jsx("div", { className: "p-2 border-b border-gray-200", children: /* @__PURE__ */ jsx(
39293
- "input",
39294
- {
39295
- ref: inputRef,
39296
- type: "text",
39297
- placeholder: "Search time...",
39298
- value: searchTerm,
39299
- onChange: (e) => setSearchTerm(e.target.value),
39300
- onKeyDown: handleKeyDown,
39301
- 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"
39302
- }
39303
- ) }),
39304
- /* @__PURE__ */ jsx("div", { className: "overflow-y-auto max-h-48", children: filteredSlots.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-gray-500 text-center", children: "No times found" }) : filteredSlots.map((slot) => /* @__PURE__ */ jsx(
39305
- "button",
39306
- {
39307
- type: "button",
39308
- onClick: () => handleSelect(slot.value),
39309
- className: `
39310
- w-full px-3 py-2 text-left text-sm hover:bg-blue-50 hover:text-blue-600
39311
- transition-colors duration-150 border-b border-gray-100 last:border-b-0
39312
- ${slot.value === value ? "bg-blue-50 text-blue-600 font-medium" : "text-gray-700"}
39313
- `,
39314
- children: slot.label
39315
- },
39316
- slot.value
39317
- )) })
39318
- ] })
39787
+ isPopoverOpen && /* @__PURE__ */ jsx(
39788
+ TimePopover,
39789
+ {
39790
+ hour,
39791
+ minute,
39792
+ period,
39793
+ onHourChange: handleHourChange,
39794
+ onMinuteChange: handleMinuteChange,
39795
+ onPeriodChange: handlePeriodChange,
39796
+ onClose: () => setIsPopoverOpen(false),
39797
+ onNow: handleNow
39798
+ }
39799
+ )
39319
39800
  ] });
39320
39801
  };
39321
39802
  var SilentErrorBoundary = class extends React142__default.Component {
@@ -40053,6 +40534,7 @@ var AvatarUpload = ({
40053
40534
  )
40054
40535
  ] });
40055
40536
  };
40537
+ var CLIP_METADATA_PAGE_SIZE = 50;
40056
40538
  var parseCycleTime = (value) => {
40057
40539
  if (typeof value === "number" && Number.isFinite(value)) {
40058
40540
  return value;
@@ -40139,7 +40621,9 @@ var FileManagerFilters = ({
40139
40621
  className = "",
40140
40622
  targetCycleTime = null,
40141
40623
  idleTimeVlmEnabled = false,
40142
- showPercentileCycleFilters = true
40624
+ showPercentileCycleFilters = true,
40625
+ prefetchedClipMetadata,
40626
+ activeCategoryLoading
40143
40627
  }) => {
40144
40628
  const [expandedNodes, setExpandedNodes] = useState(/* @__PURE__ */ new Set());
40145
40629
  const [startTime, setStartTime] = useState("");
@@ -40160,10 +40644,72 @@ var FileManagerFilters = ({
40160
40644
  const [categoryPages, setCategoryPages] = useState({});
40161
40645
  const [categoryHasMore, setCategoryHasMore] = useState({});
40162
40646
  const [localClipClassifications, setLocalClipClassifications] = useState({});
40647
+ const clipMetadataRef = useRef({});
40648
+ const inFlightMetadataRequestsRef = useRef(/* @__PURE__ */ new Set());
40163
40649
  const mergedClipClassifications = useMemo(() => ({
40164
40650
  ...clipClassifications || {},
40165
40651
  ...localClipClassifications
40166
40652
  }), [clipClassifications, localClipClassifications]);
40653
+ useEffect(() => {
40654
+ clipMetadataRef.current = clipMetadata;
40655
+ }, [clipMetadata]);
40656
+ const isExternallyManaged = prefetchedClipMetadata !== void 0 || activeCategoryLoading !== void 0;
40657
+ useEffect(() => {
40658
+ if (!prefetchedClipMetadata) {
40659
+ return;
40660
+ }
40661
+ setClipMetadata((prev) => {
40662
+ let changed = false;
40663
+ const next = { ...prev };
40664
+ Object.entries(prefetchedClipMetadata).forEach(([categoryId, clips]) => {
40665
+ if (!Array.isArray(clips) || clips.length === 0) {
40666
+ return;
40667
+ }
40668
+ const previousClips = prev[categoryId] || [];
40669
+ const previousSignature = previousClips.map((clip) => clip.clipId || clip.id).join("|");
40670
+ const nextSignature = clips.map((clip) => clip.clipId || clip.id).join("|");
40671
+ if (previousSignature === nextSignature) {
40672
+ return;
40673
+ }
40674
+ next[categoryId] = clips;
40675
+ changed = true;
40676
+ });
40677
+ return changed ? next : prev;
40678
+ });
40679
+ setCategoryPages((prev) => {
40680
+ let changed = false;
40681
+ const next = { ...prev };
40682
+ Object.entries(prefetchedClipMetadata).forEach(([categoryId, clips]) => {
40683
+ if (!Array.isArray(clips) || clips.length === 0) {
40684
+ return;
40685
+ }
40686
+ const inferredPage = Math.max(1, Math.ceil(clips.length / CLIP_METADATA_PAGE_SIZE));
40687
+ if (next[categoryId] === inferredPage) {
40688
+ return;
40689
+ }
40690
+ next[categoryId] = inferredPage;
40691
+ changed = true;
40692
+ });
40693
+ return changed ? next : prev;
40694
+ });
40695
+ setCategoryHasMore((prev) => {
40696
+ let changed = false;
40697
+ const next = { ...prev };
40698
+ Object.entries(prefetchedClipMetadata).forEach(([categoryId, clips]) => {
40699
+ if (!Array.isArray(clips) || clips.length === 0) {
40700
+ return;
40701
+ }
40702
+ const knownTotal = typeof counts?.[categoryId] === "number" ? counts[categoryId] : null;
40703
+ const inferredHasMore = knownTotal !== null ? clips.length < knownTotal : prev[categoryId];
40704
+ if (typeof inferredHasMore !== "boolean" || next[categoryId] === inferredHasMore) {
40705
+ return;
40706
+ }
40707
+ next[categoryId] = inferredHasMore;
40708
+ changed = true;
40709
+ });
40710
+ return changed ? next : prev;
40711
+ });
40712
+ }, [prefetchedClipMetadata, counts]);
40167
40713
  const { state: filterState } = useClipFilter();
40168
40714
  const [percentileCounts, setPercentileCounts] = useState({
40169
40715
  "fast-cycles": null,
@@ -40270,7 +40816,8 @@ var FileManagerFilters = ({
40270
40816
  shift: shift.toString(),
40271
40817
  category: categoryId,
40272
40818
  page,
40273
- limit: 50,
40819
+ limit: CLIP_METADATA_PAGE_SIZE,
40820
+ knownTotal: typeof counts?.[categoryId] === "number" ? counts[categoryId] : null,
40274
40821
  snapshotDateTime,
40275
40822
  snapshotClipId
40276
40823
  }),
@@ -40280,7 +40827,7 @@ var FileManagerFilters = ({
40280
40827
  throw new Error(`API error: ${response.status}`);
40281
40828
  }
40282
40829
  return response.json();
40283
- }, [workspaceId, date, shift, snapshotDateTime, snapshotClipId, supabase]);
40830
+ }, [workspaceId, date, shift, counts, snapshotDateTime, snapshotClipId, supabase]);
40284
40831
  const seedIdleClassifications = useCallback(async (clips) => {
40285
40832
  if (!idleTimeVlmEnabled || clips.length === 0) {
40286
40833
  return;
@@ -40337,6 +40884,10 @@ var FileManagerFilters = ({
40337
40884
  return;
40338
40885
  }
40339
40886
  const loadingKey = `${categoryId}-${page}`;
40887
+ if (inFlightMetadataRequestsRef.current.has(loadingKey)) {
40888
+ return;
40889
+ }
40890
+ inFlightMetadataRequestsRef.current.add(loadingKey);
40340
40891
  setLoadingCategories((prev) => /* @__PURE__ */ new Set([...prev, loadingKey]));
40341
40892
  try {
40342
40893
  const data = await fetchClipMetadataPage(categoryId, page);
@@ -40353,6 +40904,7 @@ var FileManagerFilters = ({
40353
40904
  } catch (error) {
40354
40905
  console.error("[FileManager] Error fetching clip metadata:", error);
40355
40906
  } finally {
40907
+ inFlightMetadataRequestsRef.current.delete(loadingKey);
40356
40908
  setLoadingCategories((prev) => {
40357
40909
  const newSet = new Set(prev);
40358
40910
  newSet.delete(loadingKey);
@@ -40626,16 +41178,19 @@ var FileManagerFilters = ({
40626
41178
  useEffect(() => {
40627
41179
  if (activeFilter) {
40628
41180
  setExpandedNodes((prev) => {
41181
+ if (prev.has(activeFilter)) {
41182
+ return prev;
41183
+ }
40629
41184
  const newExpanded = new Set(prev);
40630
41185
  newExpanded.add(activeFilter);
40631
41186
  return newExpanded;
40632
41187
  });
40633
41188
  const category = categories.find((cat) => cat.id === activeFilter);
40634
- if (category) {
41189
+ if (category && !isExternallyManaged && !clipMetadataRef.current[activeFilter]) {
40635
41190
  fetchClipMetadata(activeFilter, 1);
40636
41191
  }
40637
41192
  }
40638
- }, [activeFilter]);
41193
+ }, [activeFilter, isExternallyManaged]);
40639
41194
  useEffect(() => {
40640
41195
  const handleEscape = (e) => {
40641
41196
  if (e.key === "Escape") {
@@ -40906,14 +41461,14 @@ var FileManagerFilters = ({
40906
41461
  } else {
40907
41462
  newExpanded.add(nodeId);
40908
41463
  const category = categories.find((cat) => cat.id === nodeId);
40909
- if (category && !clipMetadata[nodeId]) {
41464
+ if (category && !clipMetadata[nodeId] && !(isExternallyManaged && nodeId === activeFilter)) {
40910
41465
  console.log(`[FileManager] Fetching clips for expanded category: ${nodeId}`);
40911
41466
  fetchClipMetadata(nodeId, 1);
40912
41467
  }
40913
- if (showPercentileCycleFilters && nodeId === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
41468
+ if (!(isExternallyManaged && nodeId === activeFilter) && showPercentileCycleFilters && nodeId === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40914
41469
  fetchPercentileClips("fast-cycles");
40915
41470
  }
40916
- if (showPercentileCycleFilters && nodeId === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
41471
+ if (!(isExternallyManaged && nodeId === activeFilter) && showPercentileCycleFilters && nodeId === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40917
41472
  fetchPercentileClips("slow-cycles");
40918
41473
  }
40919
41474
  }
@@ -40927,14 +41482,14 @@ var FileManagerFilters = ({
40927
41482
  } else {
40928
41483
  newExpanded.add(node.id);
40929
41484
  const category = categories.find((cat) => cat.id === node.id);
40930
- if (category && !clipMetadata[node.id]) {
41485
+ if (category && !clipMetadata[node.id] && !isExternallyManaged) {
40931
41486
  console.log(`[FileManager] Fetching clips for expanded category: ${node.id}`);
40932
41487
  fetchClipMetadata(node.id, 1);
40933
41488
  }
40934
- if (showPercentileCycleFilters && node.id === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
41489
+ if (!isExternallyManaged && showPercentileCycleFilters && node.id === "fast-cycles" && (percentileClips["fast-cycles"] || []).length === 0) {
40935
41490
  fetchPercentileClips("fast-cycles");
40936
41491
  }
40937
- if (showPercentileCycleFilters && node.id === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
41492
+ if (!isExternallyManaged && showPercentileCycleFilters && node.id === "slow-cycles" && (percentileClips["slow-cycles"] || []).length === 0) {
40938
41493
  fetchPercentileClips("slow-cycles");
40939
41494
  }
40940
41495
  }
@@ -40987,6 +41542,9 @@ var FileManagerFilters = ({
40987
41542
  const isCurrentVideo = currentVideoId === node.id;
40988
41543
  const isCountUnknown = node.type === "percentile-category" && node.count === null;
40989
41544
  const hasChildren = isCountUnknown || (node.count || 0) > 0;
41545
+ const showExternalLoadingState = Boolean(
41546
+ activeCategoryLoading && isExpanded && node.id === activeFilter && (node.type === "category" || node.type === "percentile-category")
41547
+ );
40990
41548
  const colorClasses = node.color ? getColorClasses(node.color) : null;
40991
41549
  return /* @__PURE__ */ jsxs("div", { className: "select-none animate-in", children: [
40992
41550
  /* @__PURE__ */ jsxs(
@@ -41068,6 +41626,10 @@ var FileManagerFilters = ({
41068
41626
  ),
41069
41627
  hasChildren && isExpanded && (node.type === "category" || node.type === "percentile-category") && /* @__PURE__ */ jsx("div", { className: "mt-2 ml-3 animate-in border-l-2 border-slate-100 pl-3", children: /* @__PURE__ */ jsxs("div", { className: "max-h-64 overflow-y-auto space-y-1 scrollbar-thin scrollbar-track-slate-100 scrollbar-thumb-slate-300", children: [
41070
41628
  node.children.map((child) => renderNode(child, depth + 1)),
41629
+ showExternalLoadingState && /* @__PURE__ */ jsx("div", { className: "py-2 px-3 text-center", children: /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center text-sm text-slate-500", children: [
41630
+ /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
41631
+ "Loading clips..."
41632
+ ] }) }),
41071
41633
  loadingCategories.has(`${node.id}-${(categoryPages[node.id] || 0) + 1}`) && /* @__PURE__ */ jsx("div", { className: "py-2 px-3 text-center", children: /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center text-sm text-slate-500", children: [
41072
41634
  /* @__PURE__ */ jsx("div", { className: "animate-spin mr-2 h-4 w-4 border-2 border-slate-300 border-t-blue-500 rounded-full" }),
41073
41635
  "Loading more clips..."
@@ -42540,6 +43102,7 @@ var BottlenecksContent = ({
42540
43102
  category: categoryId,
42541
43103
  page: 1,
42542
43104
  limit: 100,
43105
+ knownTotal: mergedCounts[categoryId] ?? null,
42543
43106
  snapshotDateTime,
42544
43107
  snapshotClipId
42545
43108
  }),
@@ -43194,6 +43757,14 @@ var BottlenecksContent = ({
43194
43757
  }
43195
43758
  return currentPosition;
43196
43759
  }, [activeFilter, categoryMetadata.length, currentMetadataIndex, currentPosition, isPercentileCategory]);
43760
+ const prefetchedExplorerMetadata = useMemo(() => {
43761
+ if (!activeFilter || categoryMetadata.length === 0) {
43762
+ return void 0;
43763
+ }
43764
+ return {
43765
+ [activeFilter]: categoryMetadata
43766
+ };
43767
+ }, [activeFilter, categoryMetadata]);
43197
43768
  const classificationClipIds = useMemo(() => {
43198
43769
  if (!idleTimeVlmEnabled) {
43199
43770
  return [];
@@ -43915,6 +44486,8 @@ var BottlenecksContent = ({
43915
44486
  clipClassifications,
43916
44487
  idleTimeVlmEnabled,
43917
44488
  showPercentileCycleFilters: isFastSlowClipFiltersEnabled,
44489
+ prefetchedClipMetadata: prefetchedExplorerMetadata,
44490
+ activeCategoryLoading: isCategoryLoading,
43918
44491
  onFilterChange: (filterId) => {
43919
44492
  updateActiveFilter(filterId);
43920
44493
  const category = categoriesToShow.find((cat) => cat.type === filterId);
@@ -60401,7 +60974,7 @@ function HomeView({
60401
60974
  ]
60402
60975
  }
60403
60976
  ),
60404
- isLineSelectorOpen ? /* @__PURE__ */ jsxs("div", { className: "absolute right-0 top-full z-50 mt-2 w-[280px] rounded-lg border border-slate-200 bg-white p-3 shadow-xl flex flex-col gap-2", children: [
60977
+ isLineSelectorOpen ? /* @__PURE__ */ jsxs("div", { className: "absolute left-0 sm:right-0 sm:left-auto top-full z-50 mt-2 w-[280px] max-w-[calc(100vw-32px)] rounded-lg border border-slate-200 bg-white p-3 shadow-xl flex flex-col gap-2", children: [
60405
60978
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pb-2 border-b border-slate-100", children: [
60406
60979
  /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-slate-800", children: "Select Lines" }),
60407
60980
  /* @__PURE__ */ jsx(