@optifye/dashboard-core 6.10.30 → 6.10.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +8 -0
- package/dist/index.d.mts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +328 -37
- package/dist/index.mjs +329 -39
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { createClient, REALTIME_SUBSCRIBE_STATES } from '@supabase/supabase-js';
|
|
|
10
10
|
import Hls, { Events, ErrorTypes } from 'hls.js';
|
|
11
11
|
import useSWR from 'swr';
|
|
12
12
|
import { memo, noop, warning, invariant, progress, secondsToMilliseconds, millisecondsToSeconds } from 'motion-utils';
|
|
13
|
-
import { Camera, ChevronDown, ChevronUp, Check, Map as Map$1, Video, ShieldCheck, Star, Award, Filter, X, Coffee, Plus, ArrowLeft, Clock, Calendar, Save, AlertCircle, Loader2, Minus, ArrowDown, ArrowUp, ChevronLeft, ChevronRight, TrendingUp, Sparkles, Pause, Play, Wrench, XCircle, Package, UserX, Zap, HelpCircle,
|
|
13
|
+
import { Camera, AlertTriangle, ChevronDown, ChevronUp, Check, Map as Map$1, Video, ShieldCheck, Star, Award, Filter, X, Coffee, Plus, ArrowLeft, Clock, Calendar, Save, AlertCircle, Loader2, Minus, ArrowDown, ArrowUp, ChevronLeft, ChevronRight, TrendingUp, Sparkles, Pause, Play, Wrench, XCircle, Package, UserX, Zap, HelpCircle, Tag, Palette, CheckCircle2, RefreshCw, TrendingDown, FolderOpen, Folder, Sliders, Activity, Layers, Search, Edit2, ArrowRight, CheckCircle, User, Users, Shield, Building2, Mail, Lock, Info, Share2, Trophy, Target, Download, Sun, Moon, MousePointer, UserPlus, UserCog, Trash2, Eye, MoreVertical, BarChart3, Pencil, UserCheck, LogOut, MessageSquare, Menu, Send, Copy, Settings, LifeBuoy, EyeOff, UserCircle, Flame, Crown, Medal } from 'lucide-react';
|
|
14
14
|
import { toast } from 'sonner';
|
|
15
15
|
import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, ReferenceLine, Tooltip, Legend, Bar, LabelList, ResponsiveContainer, LineChart as LineChart$1, Line, PieChart, Pie, Cell, ComposedChart, Area, ScatterChart, Scatter } from 'recharts';
|
|
16
16
|
import { Slot } from '@radix-ui/react-slot';
|
|
@@ -2540,6 +2540,7 @@ var workspaceService = {
|
|
|
2540
2540
|
}
|
|
2541
2541
|
}
|
|
2542
2542
|
};
|
|
2543
|
+
var DATA_PROCESSING_DELAY_MINUTES = 5;
|
|
2543
2544
|
var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
2544
2545
|
constructor() {
|
|
2545
2546
|
this.cache = /* @__PURE__ */ new Map();
|
|
@@ -2595,9 +2596,11 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2595
2596
|
const totalMinutes = getShiftDurationMinutes(shiftStartStr, shiftEndStr);
|
|
2596
2597
|
const shiftEndDate = addMinutes(shiftStartDate, totalMinutes);
|
|
2597
2598
|
const now4 = /* @__PURE__ */ new Date();
|
|
2598
|
-
let
|
|
2599
|
-
if (
|
|
2600
|
-
if (
|
|
2599
|
+
let rawCompletedMinutes = differenceInMinutes(now4 < shiftEndDate ? now4 : shiftEndDate, shiftStartDate);
|
|
2600
|
+
if (rawCompletedMinutes < 0) rawCompletedMinutes = 0;
|
|
2601
|
+
if (rawCompletedMinutes > totalMinutes) rawCompletedMinutes = totalMinutes;
|
|
2602
|
+
const isShiftComplete = shiftEndDate <= now4;
|
|
2603
|
+
const completedMinutes = isShiftComplete ? rawCompletedMinutes : Math.max(0, rawCompletedMinutes - DATA_PROCESSING_DELAY_MINUTES);
|
|
2601
2604
|
const pendingMinutes = Math.max(0, totalMinutes - completedMinutes);
|
|
2602
2605
|
return {
|
|
2603
2606
|
shiftId,
|
|
@@ -2632,7 +2635,8 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2632
2635
|
} else if (shiftStartDate > now4) {
|
|
2633
2636
|
completedMinutes = 0;
|
|
2634
2637
|
} else {
|
|
2635
|
-
|
|
2638
|
+
const rawCompleted = Math.floor((now4.getTime() - shiftStartDate.getTime()) / (1e3 * 60));
|
|
2639
|
+
completedMinutes = Math.max(0, rawCompleted - DATA_PROCESSING_DELAY_MINUTES);
|
|
2636
2640
|
}
|
|
2637
2641
|
const pendingMinutes = totalMinutes - completedMinutes;
|
|
2638
2642
|
return {
|
|
@@ -11658,6 +11662,8 @@ var FAILURE_EXPIRY_MS = 5 * 60 * 1e3;
|
|
|
11658
11662
|
var LIVE_RELOAD_MIN_INTERVAL_MS = 15 * 1e3;
|
|
11659
11663
|
var DEFAULT_LIVE_OFFSET_SECONDS = 120;
|
|
11660
11664
|
var DEFAULT_MAX_MANIFEST_AGE_MS = 10 * 60 * 1e3;
|
|
11665
|
+
var SEGMENT_MAX_AGE_MS = 10 * 60 * 1e3;
|
|
11666
|
+
var SEGMENT_TIMESTAMP_REGEX = /(\d{8}T\d{6}Z)(?=\.ts(?:$|[?#]))/;
|
|
11661
11667
|
var STALE_MANIFEST_POLL_INITIAL_DELAY_MS = 15 * 1e3;
|
|
11662
11668
|
var STALE_MANIFEST_POLL_MAX_DELAY_MS = 60 * 1e3;
|
|
11663
11669
|
function resetFailedUrl(url) {
|
|
@@ -11693,6 +11699,8 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11693
11699
|
});
|
|
11694
11700
|
};
|
|
11695
11701
|
const [restartKey, setRestartKey] = useState(0);
|
|
11702
|
+
const [isStale, setIsStale] = useState(false);
|
|
11703
|
+
const [staleReason, setStaleReason] = useState(null);
|
|
11696
11704
|
const hlsRef = useRef(null);
|
|
11697
11705
|
const stallCheckIntervalRef = useRef(null);
|
|
11698
11706
|
const noProgressTimerRef = useRef(null);
|
|
@@ -11704,6 +11712,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11704
11712
|
const playRetryTimerRef = useRef(null);
|
|
11705
11713
|
const playRetryCountRef = useRef(0);
|
|
11706
11714
|
const manifestWatchdogRef = useRef(null);
|
|
11715
|
+
const nativeFreshnessIntervalRef = useRef(null);
|
|
11707
11716
|
const lastHiddenAtRef = useRef(null);
|
|
11708
11717
|
const manifestRetryTimerRef = useRef(null);
|
|
11709
11718
|
const manifestRetryDelayRef = useRef(5e3);
|
|
@@ -11728,6 +11737,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11728
11737
|
const staleManifestEndSnRef = useRef(null);
|
|
11729
11738
|
const staleManifestPollTimerRef = useRef(null);
|
|
11730
11739
|
const staleManifestPollDelayRef = useRef(STALE_MANIFEST_POLL_INITIAL_DELAY_MS);
|
|
11740
|
+
const lastSegmentTimestampMsRef = useRef(null);
|
|
11731
11741
|
const authTokenRef = useRef(null);
|
|
11732
11742
|
const proxyEnabled = process.env.NEXT_PUBLIC_HLS_PROXY_ENABLED === "true";
|
|
11733
11743
|
const proxyBaseUrl = (process.env.NEXT_PUBLIC_HLS_PROXY_URL || "/api/stream").replace(/\/$/, "");
|
|
@@ -11737,6 +11747,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11737
11747
|
const parsed = raw ? Number(raw) : NaN;
|
|
11738
11748
|
return Number.isFinite(parsed) ? parsed : DEFAULT_MAX_MANIFEST_AGE_MS;
|
|
11739
11749
|
})();
|
|
11750
|
+
const manifestStaleThresholdMs = maxManifestAgeMs > 0 ? Math.min(maxManifestAgeMs, SEGMENT_MAX_AGE_MS) : SEGMENT_MAX_AGE_MS;
|
|
11740
11751
|
const debugLog = (...args) => {
|
|
11741
11752
|
if (debugEnabled) {
|
|
11742
11753
|
console.log(...args);
|
|
@@ -11751,10 +11762,49 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11751
11762
|
}
|
|
11752
11763
|
return null;
|
|
11753
11764
|
};
|
|
11765
|
+
const parseSegmentTimestampMs = (value) => {
|
|
11766
|
+
const match = value.match(SEGMENT_TIMESTAMP_REGEX);
|
|
11767
|
+
if (!match) return null;
|
|
11768
|
+
const stamp = match[1];
|
|
11769
|
+
const year = Number(stamp.slice(0, 4));
|
|
11770
|
+
const month = Number(stamp.slice(4, 6));
|
|
11771
|
+
const day = Number(stamp.slice(6, 8));
|
|
11772
|
+
const hour = Number(stamp.slice(9, 11));
|
|
11773
|
+
const minute = Number(stamp.slice(11, 13));
|
|
11774
|
+
const second = Number(stamp.slice(13, 15));
|
|
11775
|
+
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day) || !Number.isFinite(hour) || !Number.isFinite(minute) || !Number.isFinite(second) || month < 1 || month > 12 || day < 1 || day > 31 || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
|
|
11776
|
+
return null;
|
|
11777
|
+
}
|
|
11778
|
+
const timestampMs = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
11779
|
+
return Number.isNaN(timestampMs) ? null : timestampMs;
|
|
11780
|
+
};
|
|
11781
|
+
const getFragmentTimestampMs = (fragment, enforceSegmentAge = false) => {
|
|
11782
|
+
if (fragment?.relurl) {
|
|
11783
|
+
const parsed = parseSegmentTimestampMs(fragment.relurl);
|
|
11784
|
+
if (parsed !== null) return parsed;
|
|
11785
|
+
}
|
|
11786
|
+
if (fragment?.url) {
|
|
11787
|
+
const parsed = parseSegmentTimestampMs(fragment.url);
|
|
11788
|
+
if (parsed !== null) return parsed;
|
|
11789
|
+
}
|
|
11790
|
+
if (enforceSegmentAge) return null;
|
|
11791
|
+
return getProgramDateTimeMs(fragment?.programDateTime);
|
|
11792
|
+
};
|
|
11793
|
+
const getLatestFragmentTimestampMs = (fragments, enforceSegmentAge = false) => {
|
|
11794
|
+
if (!Array.isArray(fragments) || fragments.length === 0) return null;
|
|
11795
|
+
for (let i = fragments.length - 1; i >= 0; i -= 1) {
|
|
11796
|
+
const timestampMs = getFragmentTimestampMs(fragments[i], enforceSegmentAge);
|
|
11797
|
+
if (timestampMs !== null) {
|
|
11798
|
+
return timestampMs;
|
|
11799
|
+
}
|
|
11800
|
+
}
|
|
11801
|
+
return null;
|
|
11802
|
+
};
|
|
11754
11803
|
const parseManifestStatus = (manifestText) => {
|
|
11755
11804
|
let mediaSequence = null;
|
|
11756
11805
|
let segmentCount = 0;
|
|
11757
11806
|
let lastProgramDateTimeMs = null;
|
|
11807
|
+
let lastSegmentTimestampMs = null;
|
|
11758
11808
|
const lines = manifestText.split(/\r?\n/);
|
|
11759
11809
|
for (const rawLine of lines) {
|
|
11760
11810
|
const line = rawLine.trim();
|
|
@@ -11772,13 +11822,60 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11772
11822
|
}
|
|
11773
11823
|
} else if (line.startsWith("#EXTINF:")) {
|
|
11774
11824
|
segmentCount += 1;
|
|
11825
|
+
} else if (!line.startsWith("#")) {
|
|
11826
|
+
const segmentTimestampMs = parseSegmentTimestampMs(line);
|
|
11827
|
+
if (segmentTimestampMs !== null) {
|
|
11828
|
+
lastSegmentTimestampMs = segmentTimestampMs;
|
|
11829
|
+
}
|
|
11775
11830
|
}
|
|
11776
11831
|
}
|
|
11777
11832
|
let lastSequenceNumber = null;
|
|
11778
11833
|
if (mediaSequence !== null && segmentCount > 0) {
|
|
11779
11834
|
lastSequenceNumber = mediaSequence + segmentCount - 1;
|
|
11780
11835
|
}
|
|
11781
|
-
return { lastProgramDateTimeMs, lastSequenceNumber };
|
|
11836
|
+
return { lastProgramDateTimeMs, lastSequenceNumber, lastSegmentTimestampMs };
|
|
11837
|
+
};
|
|
11838
|
+
const evaluateSegmentFreshness = ({
|
|
11839
|
+
segmentTimestampMs,
|
|
11840
|
+
fallbackTimestampMs,
|
|
11841
|
+
enforceSegmentAge
|
|
11842
|
+
}) => {
|
|
11843
|
+
if (segmentTimestampMs !== null) {
|
|
11844
|
+
const ageMs = Date.now() - segmentTimestampMs;
|
|
11845
|
+
return {
|
|
11846
|
+
isFresh: ageMs <= SEGMENT_MAX_AGE_MS,
|
|
11847
|
+
ageMs,
|
|
11848
|
+
reason: `segment age ${Math.round(ageMs / 1e3)}s`
|
|
11849
|
+
};
|
|
11850
|
+
}
|
|
11851
|
+
if (enforceSegmentAge) {
|
|
11852
|
+
return { isFresh: false, ageMs: null, reason: "segment timestamp missing" };
|
|
11853
|
+
}
|
|
11854
|
+
if (fallbackTimestampMs !== null) {
|
|
11855
|
+
const ageMs = Date.now() - fallbackTimestampMs;
|
|
11856
|
+
return {
|
|
11857
|
+
isFresh: ageMs <= SEGMENT_MAX_AGE_MS,
|
|
11858
|
+
ageMs,
|
|
11859
|
+
reason: `program date time age ${Math.round(ageMs / 1e3)}s`
|
|
11860
|
+
};
|
|
11861
|
+
}
|
|
11862
|
+
return { isFresh: true, ageMs: null, reason: null };
|
|
11863
|
+
};
|
|
11864
|
+
const fetchManifestStatus = async (manifestUrl) => {
|
|
11865
|
+
const headers = {};
|
|
11866
|
+
if (authTokenRef.current) {
|
|
11867
|
+
headers.Authorization = `Bearer ${authTokenRef.current}`;
|
|
11868
|
+
}
|
|
11869
|
+
const response = await fetch(buildCacheBustedUrl(manifestUrl), {
|
|
11870
|
+
method: "GET",
|
|
11871
|
+
cache: "no-store",
|
|
11872
|
+
headers
|
|
11873
|
+
});
|
|
11874
|
+
if (!response.ok) {
|
|
11875
|
+
throw new Error(`Manifest fetch failed: ${response.status}`);
|
|
11876
|
+
}
|
|
11877
|
+
const manifestText = await response.text();
|
|
11878
|
+
return parseManifestStatus(manifestText);
|
|
11782
11879
|
};
|
|
11783
11880
|
const stopStaleManifestPolling = () => {
|
|
11784
11881
|
if (staleManifestPollTimerRef.current) {
|
|
@@ -11789,6 +11886,8 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11789
11886
|
staleManifestUrlRef.current = null;
|
|
11790
11887
|
staleManifestEndSnRef.current = null;
|
|
11791
11888
|
staleManifestPollDelayRef.current = STALE_MANIFEST_POLL_INITIAL_DELAY_MS;
|
|
11889
|
+
setIsStale(false);
|
|
11890
|
+
setStaleReason(null);
|
|
11792
11891
|
};
|
|
11793
11892
|
const pollStaleManifestOnce = async () => {
|
|
11794
11893
|
if (!staleManifestTriggeredRef.current) return;
|
|
@@ -11802,25 +11901,24 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11802
11901
|
return;
|
|
11803
11902
|
}
|
|
11804
11903
|
try {
|
|
11805
|
-
const
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
|
|
11904
|
+
const {
|
|
11905
|
+
lastProgramDateTimeMs,
|
|
11906
|
+
lastSequenceNumber,
|
|
11907
|
+
lastSegmentTimestampMs
|
|
11908
|
+
} = await fetchManifestStatus(manifestUrl);
|
|
11909
|
+
if (lastSegmentTimestampMs !== null) {
|
|
11910
|
+
lastSegmentTimestampMsRef.current = lastSegmentTimestampMs;
|
|
11911
|
+
}
|
|
11912
|
+
const enforceSegmentAge = isR2StreamRef.current;
|
|
11913
|
+
const hasAnyTimestamp = lastSegmentTimestampMs !== null || lastProgramDateTimeMs !== null;
|
|
11914
|
+
const freshness = evaluateSegmentFreshness({
|
|
11915
|
+
segmentTimestampMs: lastSegmentTimestampMs,
|
|
11916
|
+
fallbackTimestampMs: lastProgramDateTimeMs,
|
|
11917
|
+
enforceSegmentAge
|
|
11813
11918
|
});
|
|
11814
|
-
if (!response.ok) {
|
|
11815
|
-
throw new Error(`Manifest poll failed: ${response.status}`);
|
|
11816
|
-
}
|
|
11817
|
-
const manifestText = await response.text();
|
|
11818
|
-
const { lastProgramDateTimeMs, lastSequenceNumber } = parseManifestStatus(manifestText);
|
|
11819
|
-
const now4 = Date.now();
|
|
11820
|
-
const isFreshByProgramDateTime = lastProgramDateTimeMs !== null && now4 - lastProgramDateTimeMs <= maxManifestAgeMs;
|
|
11821
11919
|
const priorEndSn = staleManifestEndSnRef.current;
|
|
11822
11920
|
const isSequenceAdvanced = typeof lastSequenceNumber === "number" && (priorEndSn === null || lastSequenceNumber > priorEndSn);
|
|
11823
|
-
if (
|
|
11921
|
+
if (freshness.isFresh || !enforceSegmentAge && !hasAnyTimestamp && isSequenceAdvanced) {
|
|
11824
11922
|
stopStaleManifestPolling();
|
|
11825
11923
|
if (hlsRef.current) {
|
|
11826
11924
|
hlsRef.current.startLoad(-1);
|
|
@@ -11855,6 +11953,8 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11855
11953
|
staleManifestUrlRef.current = activeStreamUrlRef.current || latestSrcRef.current;
|
|
11856
11954
|
staleManifestEndSnRef.current = lastManifestEndSnRef.current;
|
|
11857
11955
|
staleManifestPollDelayRef.current = STALE_MANIFEST_POLL_INITIAL_DELAY_MS;
|
|
11956
|
+
setIsStale(true);
|
|
11957
|
+
setStaleReason(reason);
|
|
11858
11958
|
const hls = hlsRef.current;
|
|
11859
11959
|
if (hls) {
|
|
11860
11960
|
hls.stopLoad();
|
|
@@ -11912,6 +12012,10 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11912
12012
|
clearInterval(manifestWatchdogRef.current);
|
|
11913
12013
|
manifestWatchdogRef.current = null;
|
|
11914
12014
|
}
|
|
12015
|
+
if (nativeFreshnessIntervalRef.current) {
|
|
12016
|
+
clearInterval(nativeFreshnessIntervalRef.current);
|
|
12017
|
+
nativeFreshnessIntervalRef.current = null;
|
|
12018
|
+
}
|
|
11915
12019
|
if (manifestRetryTimerRef.current) {
|
|
11916
12020
|
clearTimeout(manifestRetryTimerRef.current);
|
|
11917
12021
|
manifestRetryTimerRef.current = null;
|
|
@@ -11936,6 +12040,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
11936
12040
|
lastManifestEndSnRef.current = null;
|
|
11937
12041
|
lastManifestEndSnUpdatedAtRef.current = null;
|
|
11938
12042
|
staleManifestTriggeredRef.current = false;
|
|
12043
|
+
lastSegmentTimestampMsRef.current = null;
|
|
11939
12044
|
manifestRetryDelayRef.current = 5e3;
|
|
11940
12045
|
playRetryCountRef.current = 0;
|
|
11941
12046
|
if (hlsRef.current) {
|
|
@@ -12055,6 +12160,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12055
12160
|
const attemptPlay = (reason) => {
|
|
12056
12161
|
const video = videoRef.current;
|
|
12057
12162
|
if (!video || !shouldPlayRef.current) return;
|
|
12163
|
+
if (staleManifestTriggeredRef.current) return;
|
|
12058
12164
|
if (!video.paused || video.seeking) return;
|
|
12059
12165
|
if (video.readyState < 2) return;
|
|
12060
12166
|
video.play().then(() => {
|
|
@@ -12080,7 +12186,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12080
12186
|
lastHiddenAtRef.current = null;
|
|
12081
12187
|
if (!lastHiddenAt) return;
|
|
12082
12188
|
if (Date.now() - lastHiddenAt < 3e4) return;
|
|
12083
|
-
refreshLiveStream("tab visible after idle");
|
|
12189
|
+
void refreshLiveStream("tab visible after idle");
|
|
12084
12190
|
attemptPlay();
|
|
12085
12191
|
};
|
|
12086
12192
|
const startManifestWatchdog = () => {
|
|
@@ -12156,7 +12262,38 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12156
12262
|
return `${url}${separator}ts=${Date.now()}`;
|
|
12157
12263
|
}
|
|
12158
12264
|
};
|
|
12159
|
-
const
|
|
12265
|
+
const ensureManifestFreshness = async (manifestUrl, enforceSegmentAge, reason) => {
|
|
12266
|
+
try {
|
|
12267
|
+
const status = await fetchManifestStatus(manifestUrl);
|
|
12268
|
+
if (status.lastSegmentTimestampMs !== null) {
|
|
12269
|
+
lastSegmentTimestampMsRef.current = status.lastSegmentTimestampMs;
|
|
12270
|
+
}
|
|
12271
|
+
const freshness = evaluateSegmentFreshness({
|
|
12272
|
+
segmentTimestampMs: status.lastSegmentTimestampMs,
|
|
12273
|
+
fallbackTimestampMs: status.lastProgramDateTimeMs,
|
|
12274
|
+
enforceSegmentAge
|
|
12275
|
+
});
|
|
12276
|
+
if (!freshness.isFresh) {
|
|
12277
|
+
markStaleStream(freshness.reason || reason);
|
|
12278
|
+
return false;
|
|
12279
|
+
}
|
|
12280
|
+
return true;
|
|
12281
|
+
} catch (error) {
|
|
12282
|
+
debugLog("[HLS] Manifest freshness check failed", error);
|
|
12283
|
+
{
|
|
12284
|
+
markStaleStream("manifest freshness check failed");
|
|
12285
|
+
return false;
|
|
12286
|
+
}
|
|
12287
|
+
}
|
|
12288
|
+
};
|
|
12289
|
+
const startNativeFreshnessMonitor = (manifestUrl) => {
|
|
12290
|
+
if (nativeFreshnessIntervalRef.current) return;
|
|
12291
|
+
nativeFreshnessIntervalRef.current = setInterval(() => {
|
|
12292
|
+
if (staleManifestTriggeredRef.current) return;
|
|
12293
|
+
void ensureManifestFreshness(manifestUrl, true, "native freshness check");
|
|
12294
|
+
}, 3e4);
|
|
12295
|
+
};
|
|
12296
|
+
const refreshLiveStream = async (reason) => {
|
|
12160
12297
|
if (!isR2StreamRef.current) return;
|
|
12161
12298
|
const now4 = Date.now();
|
|
12162
12299
|
if (now4 - lastLiveReloadRef.current < LIVE_RELOAD_MIN_INTERVAL_MS) {
|
|
@@ -12172,6 +12309,10 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12172
12309
|
}
|
|
12173
12310
|
if (video && nativeStreamUrlRef.current) {
|
|
12174
12311
|
console.log(`[HLS] Native live reload (${reason})`);
|
|
12312
|
+
const isFresh = await ensureManifestFreshness(nativeStreamUrlRef.current, true, "native live reload");
|
|
12313
|
+
if (!isFresh) {
|
|
12314
|
+
return;
|
|
12315
|
+
}
|
|
12175
12316
|
const refreshedUrl = buildCacheBustedUrl(nativeStreamUrlRef.current);
|
|
12176
12317
|
video.src = refreshedUrl;
|
|
12177
12318
|
video.load();
|
|
@@ -12263,7 +12404,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12263
12404
|
};
|
|
12264
12405
|
const handleEnded = () => {
|
|
12265
12406
|
if (isNativeHlsRef.current) {
|
|
12266
|
-
refreshLiveStream("ended");
|
|
12407
|
+
void refreshLiveStream("ended");
|
|
12267
12408
|
return;
|
|
12268
12409
|
}
|
|
12269
12410
|
if (isR2StreamRef.current) {
|
|
@@ -12277,7 +12418,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12277
12418
|
if (isNativeHlsRef.current) {
|
|
12278
12419
|
if (!isR2StreamRef.current) return;
|
|
12279
12420
|
waitingTimerRef.current = setTimeout(() => {
|
|
12280
|
-
refreshLiveStream("native waiting timeout");
|
|
12421
|
+
void refreshLiveStream("native waiting timeout");
|
|
12281
12422
|
}, getWaitingTimeoutMs());
|
|
12282
12423
|
return;
|
|
12283
12424
|
}
|
|
@@ -12400,12 +12541,17 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12400
12541
|
nativeStreamUrlRef.current = resolvedSrc;
|
|
12401
12542
|
activeStreamUrlRef.current = resolvedSrc;
|
|
12402
12543
|
console.log("[HLS] Using proxy playlist for Safari R2 stream");
|
|
12544
|
+
const isFresh = await ensureManifestFreshness(resolvedSrc, true, "native proxy start");
|
|
12545
|
+
if (!isFresh) {
|
|
12546
|
+
return;
|
|
12547
|
+
}
|
|
12403
12548
|
video.src = resolvedSrc;
|
|
12404
12549
|
video.addEventListener("waiting", handleWaiting);
|
|
12405
12550
|
video.addEventListener("loadedmetadata", handleLoadedMetadata);
|
|
12406
12551
|
video.addEventListener("canplay", handleCanPlay);
|
|
12407
12552
|
video.addEventListener("ended", handleEnded);
|
|
12408
12553
|
video.addEventListener("error", handleNativeError);
|
|
12554
|
+
startNativeFreshnessMonitor(resolvedSrc);
|
|
12409
12555
|
attemptPlay();
|
|
12410
12556
|
return;
|
|
12411
12557
|
}
|
|
@@ -12573,20 +12719,29 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12573
12719
|
}
|
|
12574
12720
|
}
|
|
12575
12721
|
}
|
|
12576
|
-
if (
|
|
12722
|
+
if (!details.endList) {
|
|
12577
12723
|
const now4 = Date.now();
|
|
12578
12724
|
const fragments = Array.isArray(details.fragments) ? details.fragments : [];
|
|
12579
12725
|
const lastFragment = fragments.length ? fragments[fragments.length - 1] : void 0;
|
|
12580
|
-
const
|
|
12581
|
-
|
|
12582
|
-
|
|
12726
|
+
const segmentTimestampMs = getLatestFragmentTimestampMs(fragments, true);
|
|
12727
|
+
const enforceSegmentAge = isR2StreamRef.current;
|
|
12728
|
+
if (segmentTimestampMs !== null) {
|
|
12729
|
+
lastSegmentTimestampMsRef.current = segmentTimestampMs;
|
|
12730
|
+
}
|
|
12731
|
+
const freshness = evaluateSegmentFreshness({
|
|
12732
|
+
segmentTimestampMs,
|
|
12733
|
+
fallbackTimestampMs: getProgramDateTimeMs(lastFragment?.programDateTime),
|
|
12734
|
+
enforceSegmentAge
|
|
12735
|
+
});
|
|
12736
|
+
if (!freshness.isFresh) {
|
|
12737
|
+
markStaleStream(freshness.reason || "segment stale");
|
|
12583
12738
|
return;
|
|
12584
12739
|
}
|
|
12585
12740
|
const endSn = typeof details.endSN === "number" ? details.endSN : lastFragment?.sn;
|
|
12586
12741
|
if (typeof endSn === "number") {
|
|
12587
12742
|
if (lastManifestEndSnRef.current === endSn) {
|
|
12588
12743
|
const lastUpdatedAt = lastManifestEndSnUpdatedAtRef.current;
|
|
12589
|
-
if (lastUpdatedAt && now4 - lastUpdatedAt >
|
|
12744
|
+
if (lastUpdatedAt && now4 - lastUpdatedAt > manifestStaleThresholdMs) {
|
|
12590
12745
|
markStaleStream(`sequence stalled at ${endSn}`);
|
|
12591
12746
|
return;
|
|
12592
12747
|
}
|
|
@@ -12611,6 +12766,27 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12611
12766
|
lastManifestLoadRef.current = Date.now();
|
|
12612
12767
|
resetManifestRetry();
|
|
12613
12768
|
});
|
|
12769
|
+
hls.on(Hls.Events.FRAG_LOADING, (_event, data) => {
|
|
12770
|
+
if (staleManifestTriggeredRef.current) return;
|
|
12771
|
+
const frag = data?.frag;
|
|
12772
|
+
if (!frag || frag.sn === "initSegment") return;
|
|
12773
|
+
const enforceSegmentAge = isR2StreamRef.current;
|
|
12774
|
+
const segmentTimestampMs = getFragmentTimestampMs(frag, true);
|
|
12775
|
+
if (segmentTimestampMs !== null) {
|
|
12776
|
+
lastSegmentTimestampMsRef.current = segmentTimestampMs;
|
|
12777
|
+
}
|
|
12778
|
+
const freshness = evaluateSegmentFreshness({
|
|
12779
|
+
segmentTimestampMs,
|
|
12780
|
+
fallbackTimestampMs: getProgramDateTimeMs(frag.programDateTime),
|
|
12781
|
+
enforceSegmentAge
|
|
12782
|
+
});
|
|
12783
|
+
if (!freshness.isFresh) {
|
|
12784
|
+
if (frag.loader?.abort) {
|
|
12785
|
+
frag.loader.abort();
|
|
12786
|
+
}
|
|
12787
|
+
markStaleStream(freshness.reason || "segment stale");
|
|
12788
|
+
}
|
|
12789
|
+
});
|
|
12614
12790
|
hls.on(Hls.Events.FRAG_LOADED, (_event, data) => {
|
|
12615
12791
|
if (!isR2Stream) return;
|
|
12616
12792
|
lastFragLoadRef.current = Date.now();
|
|
@@ -12635,9 +12811,15 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12635
12811
|
if (canUseNative) {
|
|
12636
12812
|
isNativeHlsRef.current = true;
|
|
12637
12813
|
console.log("[HLS] Using native HLS");
|
|
12638
|
-
video.src = resolvedSrc;
|
|
12639
12814
|
nativeStreamUrlRef.current = resolvedSrc;
|
|
12640
12815
|
activeStreamUrlRef.current = resolvedSrc;
|
|
12816
|
+
if (isR2Stream) {
|
|
12817
|
+
const isFresh = await ensureManifestFreshness(resolvedSrc, true, "native start");
|
|
12818
|
+
if (!isFresh) {
|
|
12819
|
+
return;
|
|
12820
|
+
}
|
|
12821
|
+
}
|
|
12822
|
+
video.src = resolvedSrc;
|
|
12641
12823
|
video.addEventListener("waiting", handleWaiting);
|
|
12642
12824
|
video.addEventListener("loadedmetadata", handleLoadedMetadata);
|
|
12643
12825
|
video.addEventListener("canplay", handleCanPlay);
|
|
@@ -12645,6 +12827,9 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12645
12827
|
video.addEventListener("error", handleNativeError);
|
|
12646
12828
|
startPlaybackGovernor();
|
|
12647
12829
|
startManifestWatchdog();
|
|
12830
|
+
if (isR2Stream) {
|
|
12831
|
+
startNativeFreshnessMonitor(resolvedSrc);
|
|
12832
|
+
}
|
|
12648
12833
|
attemptPlay();
|
|
12649
12834
|
} else {
|
|
12650
12835
|
console.error("[HLS] HLS not supported");
|
|
@@ -12658,7 +12843,10 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
|
|
|
12658
12843
|
}, [src, shouldPlay, restartKey, isPermanentlyFailed]);
|
|
12659
12844
|
return {
|
|
12660
12845
|
restartKey,
|
|
12661
|
-
isNativeHls: isNativeHlsRef.current
|
|
12846
|
+
isNativeHls: isNativeHlsRef.current,
|
|
12847
|
+
isStale,
|
|
12848
|
+
staleReason,
|
|
12849
|
+
lastSegmentTimestampMs: lastSegmentTimestampMsRef.current
|
|
12662
12850
|
};
|
|
12663
12851
|
}
|
|
12664
12852
|
function useHlsStreamWithCropping(videoRef, canvasRef, options) {
|
|
@@ -14794,7 +14982,77 @@ function getNextUpdateInterval(timestamp) {
|
|
|
14794
14982
|
}
|
|
14795
14983
|
}
|
|
14796
14984
|
|
|
14797
|
-
// src/lib/hooks/
|
|
14985
|
+
// src/lib/hooks/useWorkspaceHealthLastSeen.ts
|
|
14986
|
+
var DEFAULT_REFRESH_INTERVAL_MS = 3e4;
|
|
14987
|
+
var useWorkspaceHealthLastSeen = (workspaceIds, options = {}) => {
|
|
14988
|
+
const supabase = useSupabase();
|
|
14989
|
+
const databaseConfig = useDatabaseConfig();
|
|
14990
|
+
const refreshInterval = options.refreshInterval ?? DEFAULT_REFRESH_INTERVAL_MS;
|
|
14991
|
+
const workspaceIdsKey = useMemo(() => {
|
|
14992
|
+
const ids = Array.from(new Set(workspaceIds.filter(Boolean)));
|
|
14993
|
+
return ids.sort().join(",");
|
|
14994
|
+
}, [workspaceIds]);
|
|
14995
|
+
const [lastSeenByWorkspaceId, setLastSeenByWorkspaceId] = useState({});
|
|
14996
|
+
const [isLoading, setIsLoading] = useState(Boolean(workspaceIdsKey));
|
|
14997
|
+
const [error, setError] = useState(null);
|
|
14998
|
+
const isFetchingRef = useRef(false);
|
|
14999
|
+
const refreshIntervalRef = useRef(null);
|
|
15000
|
+
const fetchLastSeen = useCallback(async () => {
|
|
15001
|
+
if (!supabase || !workspaceIdsKey || isFetchingRef.current) return;
|
|
15002
|
+
const healthTable = databaseConfig?.tables?.workspace_health || "workspace_health_status";
|
|
15003
|
+
try {
|
|
15004
|
+
isFetchingRef.current = true;
|
|
15005
|
+
setIsLoading(true);
|
|
15006
|
+
setError(null);
|
|
15007
|
+
const workspaceIdsList = workspaceIdsKey.split(",").filter(Boolean);
|
|
15008
|
+
if (!workspaceIdsList.length) {
|
|
15009
|
+
setLastSeenByWorkspaceId({});
|
|
15010
|
+
return;
|
|
15011
|
+
}
|
|
15012
|
+
const { data, error: fetchError } = await supabase.from(healthTable).select("workspace_id,last_heartbeat,is_healthy").in("workspace_id", workspaceIdsList);
|
|
15013
|
+
if (fetchError) throw fetchError;
|
|
15014
|
+
const nextMap = {};
|
|
15015
|
+
(data || []).forEach((row) => {
|
|
15016
|
+
if (!row?.workspace_id) return;
|
|
15017
|
+
const lastHeartbeat = row.last_heartbeat || null;
|
|
15018
|
+
nextMap[row.workspace_id] = {
|
|
15019
|
+
lastHeartbeat,
|
|
15020
|
+
timeSinceLastUpdate: formatRelativeTime(lastHeartbeat),
|
|
15021
|
+
isHealthy: Boolean(row.is_healthy)
|
|
15022
|
+
};
|
|
15023
|
+
});
|
|
15024
|
+
setLastSeenByWorkspaceId(nextMap);
|
|
15025
|
+
} catch (err) {
|
|
15026
|
+
console.error("[useWorkspaceHealthLastSeen] Error fetching workspace health:", err);
|
|
15027
|
+
setError(err?.message || "Failed to load workspace health");
|
|
15028
|
+
setLastSeenByWorkspaceId({});
|
|
15029
|
+
} finally {
|
|
15030
|
+
setIsLoading(false);
|
|
15031
|
+
isFetchingRef.current = false;
|
|
15032
|
+
}
|
|
15033
|
+
}, [supabase, workspaceIdsKey, databaseConfig?.tables?.workspace_health]);
|
|
15034
|
+
useEffect(() => {
|
|
15035
|
+
fetchLastSeen();
|
|
15036
|
+
}, [fetchLastSeen]);
|
|
15037
|
+
useEffect(() => {
|
|
15038
|
+
if (!refreshInterval || !workspaceIdsKey) return;
|
|
15039
|
+
refreshIntervalRef.current = setInterval(() => {
|
|
15040
|
+
fetchLastSeen();
|
|
15041
|
+
}, refreshInterval);
|
|
15042
|
+
return () => {
|
|
15043
|
+
if (refreshIntervalRef.current) {
|
|
15044
|
+
clearInterval(refreshIntervalRef.current);
|
|
15045
|
+
refreshIntervalRef.current = null;
|
|
15046
|
+
}
|
|
15047
|
+
};
|
|
15048
|
+
}, [fetchLastSeen, refreshInterval, workspaceIdsKey]);
|
|
15049
|
+
return {
|
|
15050
|
+
lastSeenByWorkspaceId,
|
|
15051
|
+
isLoading,
|
|
15052
|
+
error,
|
|
15053
|
+
refetch: fetchLastSeen
|
|
15054
|
+
};
|
|
15055
|
+
};
|
|
14798
15056
|
var useWorkspaceHealthStatus = (workspaceId) => {
|
|
14799
15057
|
const supabase = useSupabase();
|
|
14800
15058
|
const databaseConfig = useDatabaseConfig();
|
|
@@ -29759,13 +30017,14 @@ var VideoCard = React25__default.memo(({
|
|
|
29759
30017
|
className = "",
|
|
29760
30018
|
compact = false,
|
|
29761
30019
|
displayName,
|
|
30020
|
+
lastSeenLabel,
|
|
29762
30021
|
onMouseEnter,
|
|
29763
30022
|
onMouseLeave
|
|
29764
30023
|
}) => {
|
|
29765
30024
|
const videoRef = useRef(null);
|
|
29766
30025
|
const canvasRef = useRef(null);
|
|
29767
30026
|
const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
|
|
29768
|
-
useHlsStreamWithCropping(videoRef, canvasRef, {
|
|
30027
|
+
const { isStale: isStreamStale } = useHlsStreamWithCropping(videoRef, canvasRef, {
|
|
29769
30028
|
src: hlsUrl,
|
|
29770
30029
|
shouldPlay,
|
|
29771
30030
|
cropping,
|
|
@@ -29773,6 +30032,8 @@ var VideoCard = React25__default.memo(({
|
|
|
29773
30032
|
useRAF,
|
|
29774
30033
|
onFatalError: onFatalError ?? (() => throttledReloadDashboard())
|
|
29775
30034
|
});
|
|
30035
|
+
const showOffline = Boolean(isStreamStale);
|
|
30036
|
+
const lastSeenText = lastSeenLabel || "Unknown";
|
|
29776
30037
|
const workspaceDisplayName = displayName || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
|
|
29777
30038
|
const efficiencyColor = getEfficiencyColor(workspace.efficiency, effectiveLegend);
|
|
29778
30039
|
const efficiencyOverlayClass = efficiencyColor === "green" ? "bg-[#00D654]/25" : efficiencyColor === "yellow" ? "bg-[#FFD700]/30" : "bg-[#FF2D0A]/30";
|
|
@@ -29842,6 +30103,14 @@ var VideoCard = React25__default.memo(({
|
|
|
29842
30103
|
)
|
|
29843
30104
|
] }),
|
|
29844
30105
|
/* @__PURE__ */ jsx("div", { className: `absolute inset-0 z-20 pointer-events-none ${efficiencyOverlayClass}` }),
|
|
30106
|
+
showOffline && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-40 flex items-center justify-center bg-black/70 px-2 text-center", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
|
|
30107
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: `${compact ? "w-4 h-4" : "w-5 h-5"} text-amber-300` }),
|
|
30108
|
+
/* @__PURE__ */ jsx("span", { className: `font-semibold text-white ${compact ? "text-[11px]" : "text-xs"}`, children: "Not streaming" }),
|
|
30109
|
+
/* @__PURE__ */ jsxs("span", { className: `text-gray-200 ${compact ? "text-[10px]" : "text-[11px]"}`, children: [
|
|
30110
|
+
"Last seen: ",
|
|
30111
|
+
lastSeenText
|
|
30112
|
+
] })
|
|
30113
|
+
] }) }),
|
|
29845
30114
|
/* @__PURE__ */ jsxs("div", { className: `absolute ${compact ? "top-1 right-1" : "top-2 right-2"} z-30 bg-black/70 backdrop-blur-sm rounded ${compact ? "px-1.5 py-0.5" : "px-2 py-0.5"} text-white ${compact ? "text-[10px]" : "text-xs"} font-semibold border border-white/10`, children: [
|
|
29846
30115
|
Math.round(workspace.efficiency),
|
|
29847
30116
|
"%"
|
|
@@ -29868,8 +30137,13 @@ var VideoCard = React25__default.memo(({
|
|
|
29868
30137
|
children: trendInfo.arrow
|
|
29869
30138
|
}
|
|
29870
30139
|
),
|
|
29871
|
-
/* @__PURE__ */ jsx(
|
|
29872
|
-
|
|
30140
|
+
/* @__PURE__ */ jsx(
|
|
30141
|
+
"div",
|
|
30142
|
+
{
|
|
30143
|
+
className: `${compact ? "w-1 h-1" : "w-1.5 h-1.5"} rounded-full ${showOffline ? "bg-red-500" : "bg-green-500"}`
|
|
30144
|
+
}
|
|
30145
|
+
),
|
|
30146
|
+
/* @__PURE__ */ jsx("span", { className: `text-white text-[11px] sm:${compact ? "text-[10px]" : "text-xs"}`, children: showOffline ? "Offline" : "Live" })
|
|
29873
30147
|
] })
|
|
29874
30148
|
] })
|
|
29875
30149
|
]
|
|
@@ -29885,6 +30159,9 @@ var VideoCard = React25__default.memo(({
|
|
|
29885
30159
|
if (prevProps.displayName !== nextProps.displayName) {
|
|
29886
30160
|
return false;
|
|
29887
30161
|
}
|
|
30162
|
+
if (prevProps.lastSeenLabel !== nextProps.lastSeenLabel) {
|
|
30163
|
+
return false;
|
|
30164
|
+
}
|
|
29888
30165
|
if (prevProps.legend !== nextProps.legend) {
|
|
29889
30166
|
return false;
|
|
29890
30167
|
}
|
|
@@ -29942,6 +30219,16 @@ var VideoGridView = React25__default.memo(({
|
|
|
29942
30219
|
const supabase = useSupabase();
|
|
29943
30220
|
const { cropping, canvasConfig, hlsUrls } = videoConfig;
|
|
29944
30221
|
const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
|
|
30222
|
+
const workspaceHealthIds = useMemo(() => {
|
|
30223
|
+
const ids = /* @__PURE__ */ new Set();
|
|
30224
|
+
for (const workspace of workspaces) {
|
|
30225
|
+
if (workspace.workspace_uuid) {
|
|
30226
|
+
ids.add(workspace.workspace_uuid);
|
|
30227
|
+
}
|
|
30228
|
+
}
|
|
30229
|
+
return Array.from(ids);
|
|
30230
|
+
}, [workspaces]);
|
|
30231
|
+
const { lastSeenByWorkspaceId } = useWorkspaceHealthLastSeen(workspaceHealthIds);
|
|
29945
30232
|
useEffect(() => {
|
|
29946
30233
|
const sample = workspaces.slice(0, 3).map((workspace) => ({
|
|
29947
30234
|
id: workspace.workspace_uuid || workspace.workspace_name,
|
|
@@ -30193,6 +30480,7 @@ var VideoGridView = React25__default.memo(({
|
|
|
30193
30480
|
const isVeryLowEfficiency = workspace.show_exclamation ?? (workspace.efficiency < 50 && workspace.efficiency >= 10);
|
|
30194
30481
|
const workspaceCropping = getWorkspaceCropping(workspaceId, workspace.workspace_name);
|
|
30195
30482
|
const workspaceStream = videoStreamsByWorkspaceId?.[workspaceId];
|
|
30483
|
+
const lastSeenLabel = workspace.workspace_uuid ? lastSeenByWorkspaceId[workspace.workspace_uuid]?.timeSinceLastUpdate : void 0;
|
|
30196
30484
|
const r2Url = workspaceStream?.hls_url;
|
|
30197
30485
|
const fallbackUrl = getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id);
|
|
30198
30486
|
const hasR2Stream = Boolean(r2Url);
|
|
@@ -30210,7 +30498,8 @@ var VideoGridView = React25__default.memo(({
|
|
|
30210
30498
|
fallbackUrl,
|
|
30211
30499
|
hlsUrl,
|
|
30212
30500
|
isR2Stream,
|
|
30213
|
-
shouldPlay
|
|
30501
|
+
shouldPlay,
|
|
30502
|
+
lastSeenLabel
|
|
30214
30503
|
};
|
|
30215
30504
|
});
|
|
30216
30505
|
const croppedActiveCount = workspaceCards.reduce((count, card) => {
|
|
@@ -30248,6 +30537,7 @@ var VideoGridView = React25__default.memo(({
|
|
|
30248
30537
|
displayNames[`${card.workspace.line_id}_${card.workspace.workspace_name}`] || // Always pass line_id to fallback to ensure correct mapping per line
|
|
30249
30538
|
getWorkspaceDisplayName(card.workspace.workspace_name, card.workspace.line_id)
|
|
30250
30539
|
),
|
|
30540
|
+
lastSeenLabel: card.lastSeenLabel,
|
|
30251
30541
|
useRAF: effectiveUseRAF,
|
|
30252
30542
|
compact: !selectedLine,
|
|
30253
30543
|
onMouseEnter: onWorkspaceHover ? () => onWorkspaceHover(card.workspaceId) : void 0,
|
|
@@ -66221,4 +66511,4 @@ var streamProxyConfig = {
|
|
|
66221
66511
|
}
|
|
66222
66512
|
};
|
|
66223
66513
|
|
|
66224
|
-
export { ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AvatarUpload, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, FittingTitle, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, TeamUsagePdfGenerator, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UserAvatar, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, aggregateKPIsFromLineMetricsRows, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, awardsService, buildDateKey, buildKPIsFromLineMetricsRow, buildShiftGroupsKey, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearSentryContext, clearWorkspaceDisplayNamesCache, cn, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStorageService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, fetchIdleTimeReasons, filterDataByDateKeyRange, forceRefreshWorkspaceDisplayNames, formatAwardMonth, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAvailableShiftIds, getAwardBadgeType, getAwardDescription, getAwardTitle, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getInitials, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getNextUpdateInterval, getOperationalDate, getReasonColor, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isFullMonthRange, isLegacyConfiguration, isPrefetchError, isSafari, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeDateKeyRange, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, setSentryUserContext, setSentryWorkspaceContext, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useClipsInit, useCompanyUsersUsage, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMultiLineShiftConfigs, useNavigation, useOperationalShiftKey, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShiftGroups, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, weeklyTopPerformerService, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
|
|
66514
|
+
export { ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AvatarUpload, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, FittingTitle, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, TeamUsagePdfGenerator, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UserAvatar, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, aggregateKPIsFromLineMetricsRows, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, awardsService, buildDateKey, buildKPIsFromLineMetricsRow, buildShiftGroupsKey, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearSentryContext, clearWorkspaceDisplayNamesCache, cn, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStorageService, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, fetchIdleTimeReasons, filterDataByDateKeyRange, forceRefreshWorkspaceDisplayNames, formatAwardMonth, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAvailableShiftIds, getAwardBadgeType, getAwardDescription, getAwardTitle, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getInitials, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getNextUpdateInterval, getOperationalDate, getReasonColor, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isFullMonthRange, isLegacyConfiguration, isPrefetchError, isSafari, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeDateKeyRange, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, setSentryUserContext, setSentryWorkspaceContext, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useClipsInit, useCompanyUsersUsage, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMultiLineShiftConfigs, useNavigation, useOperationalShiftKey, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShiftGroups, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthLastSeen, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, weeklyTopPerformerService, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
|