@optifye/dashboard-core 6.4.1 → 6.5.0
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 +301 -12
- package/dist/index.d.mts +302 -67
- package/dist/index.d.ts +302 -67
- package/dist/index.js +2151 -819
- package/dist/index.mjs +2139 -823
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3,10 +3,9 @@ import React19__default, { createContext, useRef, useCallback, useState, useMemo
|
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import { useRouter } from 'next/router';
|
|
5
5
|
import { toZonedTime, formatInTimeZone } from 'date-fns-tz';
|
|
6
|
-
import { subDays, format, parseISO, isValid, isFuture, isToday } from 'date-fns';
|
|
6
|
+
import { subDays, format, parseISO, isValid, formatDistanceToNow, isFuture, isToday } from 'date-fns';
|
|
7
7
|
import mixpanel from 'mixpanel-browser';
|
|
8
8
|
import { EventEmitter } from 'events';
|
|
9
|
-
import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
10
9
|
import { REALTIME_SUBSCRIBE_STATES, createClient } from '@supabase/supabase-js';
|
|
11
10
|
import Hls2 from 'hls.js';
|
|
12
11
|
import useSWR from 'swr';
|
|
@@ -14,15 +13,17 @@ import { noop, warning, invariant, progress, secondsToMilliseconds, milliseconds
|
|
|
14
13
|
import { getValueTransition, hover, press, isPrimaryPointer, GroupPlaybackControls, setDragLock, supportsLinearEasing, attachTimeline, isGenerator, calcGeneratorDuration, isWaapiSupportedEasing, mapEasingToNativeEasing, maxGeneratorDuration, generateLinearEasing, isBezierDefinition } from 'motion-dom';
|
|
15
14
|
import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Bar, LabelList, ResponsiveContainer, LineChart as LineChart$1, Line, PieChart, Pie, Cell, ReferenceLine, ComposedChart, Area, ScatterChart, Scatter } from 'recharts';
|
|
16
15
|
import { Slot } from '@radix-ui/react-slot';
|
|
17
|
-
import { Camera, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, X, Coffee, Plus,
|
|
16
|
+
import { Camera, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, X, Coffee, Plus, ArrowLeft, Clock, Calendar, Save, Minus, ArrowDown, ArrowUp, Settings2, CheckCircle2, Search, Loader2, AlertCircle, Edit2, CheckCircle, AlertTriangle, Info, Share2, Trophy, Target, Download, User, XCircle, ChevronLeft, ChevronRight, WifiOff, Wifi, Grid3x3, List, Sun, Moon, MessageSquare, Trash2, RefreshCw, Menu, Send, Copy, UserCheck, LogOut, Package, TrendingUp, TrendingDown, Activity, Settings, LifeBuoy, EyeOff, Eye, Zap, UserCircle } from 'lucide-react';
|
|
18
17
|
import { DayPicker, useNavigation as useNavigation$1 } from 'react-day-picker';
|
|
19
|
-
import { XMarkIcon, ArrowRightIcon, HomeIcon, TrophyIcon, ChartBarIcon, AdjustmentsHorizontalIcon, ClockIcon, CubeIcon, SparklesIcon, QuestionMarkCircleIcon, UserCircleIcon, ExclamationCircleIcon, EnvelopeIcon, DocumentTextIcon,
|
|
18
|
+
import { XMarkIcon, ArrowRightIcon, HomeIcon, TrophyIcon, ChartBarIcon, AdjustmentsHorizontalIcon, ClockIcon, CubeIcon, SparklesIcon, QuestionMarkCircleIcon, HeartIcon, UserCircleIcon, ExclamationCircleIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ChevronDownIcon, Bars3Icon, CheckCircleIcon, ChatBubbleLeftRightIcon, XCircleIcon, InformationCircleIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
|
|
19
|
+
import { CheckIcon } from '@heroicons/react/24/solid';
|
|
20
20
|
import html2canvas from 'html2canvas';
|
|
21
21
|
import jsPDF, { jsPDF as jsPDF$1 } from 'jspdf';
|
|
22
22
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
23
23
|
import videojs from 'video.js';
|
|
24
24
|
import 'video.js/dist/video-js.css';
|
|
25
25
|
import { toast } from 'sonner';
|
|
26
|
+
import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
26
27
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
27
28
|
import { Readable } from 'stream';
|
|
28
29
|
|
|
@@ -1673,6 +1674,25 @@ var workspaceService = {
|
|
|
1673
1674
|
this._workspaceDisplayNamesCache.clear();
|
|
1674
1675
|
this._cacheTimestamp = 0;
|
|
1675
1676
|
},
|
|
1677
|
+
/**
|
|
1678
|
+
* Updates the display name for a workspace
|
|
1679
|
+
* @param workspaceId - The workspace UUID
|
|
1680
|
+
* @param displayName - The new display name
|
|
1681
|
+
* @returns Promise<void>
|
|
1682
|
+
*/
|
|
1683
|
+
async updateWorkspaceDisplayName(workspaceId, displayName) {
|
|
1684
|
+
const supabase = _getSupabaseInstance();
|
|
1685
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1686
|
+
const config = _getDashboardConfigInstance();
|
|
1687
|
+
const dbConfig = config?.databaseConfig;
|
|
1688
|
+
const workspacesTable = getTable3(dbConfig, "workspaces");
|
|
1689
|
+
const { error } = await supabase.from(workspacesTable).update({ display_name: displayName.trim() }).eq("id", workspaceId);
|
|
1690
|
+
if (error) {
|
|
1691
|
+
console.error(`Error updating workspace display name for ${workspaceId}:`, error);
|
|
1692
|
+
throw error;
|
|
1693
|
+
}
|
|
1694
|
+
this.clearWorkspaceDisplayNamesCache();
|
|
1695
|
+
},
|
|
1676
1696
|
async updateWorkspaceAction(updates) {
|
|
1677
1697
|
const supabase = _getSupabaseInstance();
|
|
1678
1698
|
if (!supabase) throw new Error("Supabase client not initialized");
|
|
@@ -1816,6 +1836,209 @@ var workspaceService = {
|
|
|
1816
1836
|
}
|
|
1817
1837
|
}
|
|
1818
1838
|
};
|
|
1839
|
+
var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
1840
|
+
constructor() {
|
|
1841
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1842
|
+
this.cacheExpiryMs = 30 * 1e3;
|
|
1843
|
+
}
|
|
1844
|
+
// 30 seconds cache
|
|
1845
|
+
static getInstance() {
|
|
1846
|
+
if (!_WorkspaceHealthService.instance) {
|
|
1847
|
+
_WorkspaceHealthService.instance = new _WorkspaceHealthService();
|
|
1848
|
+
}
|
|
1849
|
+
return _WorkspaceHealthService.instance;
|
|
1850
|
+
}
|
|
1851
|
+
getFromCache(key) {
|
|
1852
|
+
const cached = this.cache.get(key);
|
|
1853
|
+
if (cached && Date.now() - cached.timestamp < this.cacheExpiryMs) {
|
|
1854
|
+
return cached.data;
|
|
1855
|
+
}
|
|
1856
|
+
this.cache.delete(key);
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
setCache(key, data) {
|
|
1860
|
+
this.cache.set(key, { data, timestamp: Date.now() });
|
|
1861
|
+
}
|
|
1862
|
+
async getWorkspaceHealthStatus(options = {}) {
|
|
1863
|
+
const supabase = _getSupabaseInstance();
|
|
1864
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1865
|
+
let query = supabase.from("workspace_health_status").select("*").order("workspace_display_name", { ascending: true });
|
|
1866
|
+
if (options.lineId) {
|
|
1867
|
+
query = query.eq("line_id", options.lineId);
|
|
1868
|
+
}
|
|
1869
|
+
if (options.companyId) {
|
|
1870
|
+
query = query.eq("company_id", options.companyId);
|
|
1871
|
+
}
|
|
1872
|
+
const { data, error } = await query;
|
|
1873
|
+
if (error) {
|
|
1874
|
+
console.error("Error fetching workspace health status:", error);
|
|
1875
|
+
throw error;
|
|
1876
|
+
}
|
|
1877
|
+
const processedData = (data || []).map((item) => this.processHealthStatus(item));
|
|
1878
|
+
let filteredData = processedData;
|
|
1879
|
+
try {
|
|
1880
|
+
const { data: enabledWorkspaces, error: workspaceError } = await supabase.from("workspaces").select("workspace_id, display_name").eq("enable", true);
|
|
1881
|
+
if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length > 0) {
|
|
1882
|
+
const enabledWorkspaceNames = /* @__PURE__ */ new Set();
|
|
1883
|
+
enabledWorkspaces.forEach((w) => {
|
|
1884
|
+
if (w.workspace_id) enabledWorkspaceNames.add(w.workspace_id);
|
|
1885
|
+
if (w.display_name) enabledWorkspaceNames.add(w.display_name);
|
|
1886
|
+
});
|
|
1887
|
+
filteredData = filteredData.filter((item) => {
|
|
1888
|
+
const displayName = item.workspace_display_name || "";
|
|
1889
|
+
return enabledWorkspaceNames.has(displayName);
|
|
1890
|
+
});
|
|
1891
|
+
} else if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length === 0) {
|
|
1892
|
+
return [];
|
|
1893
|
+
} else if (workspaceError) {
|
|
1894
|
+
console.error("Error fetching enabled workspaces:", workspaceError);
|
|
1895
|
+
}
|
|
1896
|
+
} catch (e) {
|
|
1897
|
+
console.error("Error filtering workspaces:", e);
|
|
1898
|
+
}
|
|
1899
|
+
if (options.status) {
|
|
1900
|
+
filteredData = filteredData.filter((item) => item.status === options.status);
|
|
1901
|
+
}
|
|
1902
|
+
if (options.searchTerm) {
|
|
1903
|
+
const searchLower = options.searchTerm.toLowerCase();
|
|
1904
|
+
filteredData = filteredData.filter(
|
|
1905
|
+
(item) => item.workspace_display_name?.toLowerCase().includes(searchLower) || item.line_name?.toLowerCase().includes(searchLower) || item.company_name?.toLowerCase().includes(searchLower)
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
if (options.sortBy) {
|
|
1909
|
+
filteredData.sort((a, b) => {
|
|
1910
|
+
let compareValue = 0;
|
|
1911
|
+
switch (options.sortBy) {
|
|
1912
|
+
case "name":
|
|
1913
|
+
compareValue = (a.workspace_display_name || "").localeCompare(b.workspace_display_name || "");
|
|
1914
|
+
break;
|
|
1915
|
+
case "status":
|
|
1916
|
+
compareValue = this.getStatusPriority(a.status) - this.getStatusPriority(b.status);
|
|
1917
|
+
break;
|
|
1918
|
+
case "lastUpdate":
|
|
1919
|
+
compareValue = new Date(b.last_heartbeat).getTime() - new Date(a.last_heartbeat).getTime();
|
|
1920
|
+
break;
|
|
1921
|
+
}
|
|
1922
|
+
return options.sortOrder === "desc" ? -compareValue : compareValue;
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
return filteredData;
|
|
1926
|
+
}
|
|
1927
|
+
async getWorkspaceHealthById(workspaceId) {
|
|
1928
|
+
const cacheKey = `health-${workspaceId}`;
|
|
1929
|
+
const cached = this.getFromCache(cacheKey);
|
|
1930
|
+
if (cached) return cached;
|
|
1931
|
+
const supabase = _getSupabaseInstance();
|
|
1932
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1933
|
+
const { data, error } = await supabase.from("workspace_health_status").select("*").eq("workspace_id", workspaceId).single();
|
|
1934
|
+
if (error) {
|
|
1935
|
+
if (error.code === "PGRST116") {
|
|
1936
|
+
return null;
|
|
1937
|
+
}
|
|
1938
|
+
console.error("Error fetching workspace health:", error);
|
|
1939
|
+
throw error;
|
|
1940
|
+
}
|
|
1941
|
+
const processedData = data ? this.processHealthStatus(data) : null;
|
|
1942
|
+
if (processedData) {
|
|
1943
|
+
this.setCache(cacheKey, processedData);
|
|
1944
|
+
}
|
|
1945
|
+
return processedData;
|
|
1946
|
+
}
|
|
1947
|
+
async getHealthSummary(lineId, companyId) {
|
|
1948
|
+
this.clearCache();
|
|
1949
|
+
const workspaces = await this.getWorkspaceHealthStatus({ lineId, companyId });
|
|
1950
|
+
const totalWorkspaces = workspaces.length;
|
|
1951
|
+
const healthyWorkspaces = workspaces.filter((w) => w.status === "healthy").length;
|
|
1952
|
+
const unhealthyWorkspaces = workspaces.filter((w) => w.status === "unhealthy").length;
|
|
1953
|
+
const warningWorkspaces = workspaces.filter((w) => w.status === "warning").length;
|
|
1954
|
+
const uptimePercentage = totalWorkspaces > 0 ? healthyWorkspaces / totalWorkspaces * 100 : 0;
|
|
1955
|
+
return {
|
|
1956
|
+
totalWorkspaces,
|
|
1957
|
+
healthyWorkspaces,
|
|
1958
|
+
unhealthyWorkspaces,
|
|
1959
|
+
warningWorkspaces,
|
|
1960
|
+
uptimePercentage,
|
|
1961
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
async getHealthMetrics(workspaceId, startDate, endDate) {
|
|
1965
|
+
const supabase = _getSupabaseInstance();
|
|
1966
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1967
|
+
return {
|
|
1968
|
+
avgResponseTime: 250,
|
|
1969
|
+
totalDowntime: 0,
|
|
1970
|
+
incidentCount: 0,
|
|
1971
|
+
mttr: 0
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
processHealthStatus(data) {
|
|
1975
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
1976
|
+
const lastHeartbeat = new Date(data.last_heartbeat);
|
|
1977
|
+
const minutesSinceUpdate = Math.floor((now2.getTime() - lastHeartbeat.getTime()) / (1e3 * 60));
|
|
1978
|
+
let status = "unknown";
|
|
1979
|
+
let isStale = false;
|
|
1980
|
+
if (data.is_healthy) {
|
|
1981
|
+
if (minutesSinceUpdate < 3) {
|
|
1982
|
+
status = "healthy";
|
|
1983
|
+
} else if (minutesSinceUpdate < 5) {
|
|
1984
|
+
status = "warning";
|
|
1985
|
+
isStale = true;
|
|
1986
|
+
} else {
|
|
1987
|
+
status = "unhealthy";
|
|
1988
|
+
isStale = true;
|
|
1989
|
+
}
|
|
1990
|
+
} else {
|
|
1991
|
+
status = "unhealthy";
|
|
1992
|
+
if (minutesSinceUpdate > 5) {
|
|
1993
|
+
isStale = true;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
const timeSinceLastUpdate = formatDistanceToNow(lastHeartbeat, { addSuffix: true });
|
|
1997
|
+
return {
|
|
1998
|
+
...data,
|
|
1999
|
+
status,
|
|
2000
|
+
timeSinceLastUpdate,
|
|
2001
|
+
isStale
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
getStatusPriority(status) {
|
|
2005
|
+
const priorities = {
|
|
2006
|
+
unhealthy: 0,
|
|
2007
|
+
warning: 1,
|
|
2008
|
+
unknown: 2,
|
|
2009
|
+
healthy: 3
|
|
2010
|
+
};
|
|
2011
|
+
return priorities[status];
|
|
2012
|
+
}
|
|
2013
|
+
subscribeToHealthUpdates(callback, filters) {
|
|
2014
|
+
const supabase = _getSupabaseInstance();
|
|
2015
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
2016
|
+
let subscription = supabase.channel("workspace-health-updates").on(
|
|
2017
|
+
"postgres_changes",
|
|
2018
|
+
{
|
|
2019
|
+
event: "*",
|
|
2020
|
+
schema: "public",
|
|
2021
|
+
table: "workspace_health_status"
|
|
2022
|
+
},
|
|
2023
|
+
(payload) => {
|
|
2024
|
+
if (payload.new) {
|
|
2025
|
+
const newData = payload.new;
|
|
2026
|
+
if (filters?.lineId && newData.line_id !== filters.lineId) return;
|
|
2027
|
+
if (filters?.companyId && newData.company_id !== filters.companyId) return;
|
|
2028
|
+
this.clearCache();
|
|
2029
|
+
callback(newData);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
).subscribe();
|
|
2033
|
+
return () => {
|
|
2034
|
+
subscription.unsubscribe();
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
clearCache() {
|
|
2038
|
+
this.cache.clear();
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
2041
|
+
var workspaceHealthService = WorkspaceHealthService.getInstance();
|
|
1819
2042
|
|
|
1820
2043
|
// src/lib/services/skuService.ts
|
|
1821
2044
|
var getTable4 = (dbConfig, tableName) => {
|
|
@@ -2645,6 +2868,17 @@ var TicketHistoryService = class {
|
|
|
2645
2868
|
}
|
|
2646
2869
|
return data;
|
|
2647
2870
|
}
|
|
2871
|
+
/**
|
|
2872
|
+
* Update ticket serviced status
|
|
2873
|
+
*/
|
|
2874
|
+
static async updateTicketServicedStatus(ticketId, serviced) {
|
|
2875
|
+
const supabase = _getSupabaseInstance();
|
|
2876
|
+
const { data, error } = await supabase.from("support_ticket_history").update({ serviced, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", ticketId).select().single();
|
|
2877
|
+
if (error) {
|
|
2878
|
+
throw new Error(`Failed to update ticket serviced status: ${error.message}`);
|
|
2879
|
+
}
|
|
2880
|
+
return data;
|
|
2881
|
+
}
|
|
2648
2882
|
};
|
|
2649
2883
|
|
|
2650
2884
|
// src/lib/services/audioService.ts
|
|
@@ -3199,6 +3433,14 @@ function parseS3Uri(s3Uri, sopCategories) {
|
|
|
3199
3433
|
return null;
|
|
3200
3434
|
}
|
|
3201
3435
|
}
|
|
3436
|
+
function shuffleArray(array) {
|
|
3437
|
+
const shuffled = [...array];
|
|
3438
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
3439
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
3440
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
3441
|
+
}
|
|
3442
|
+
return shuffled;
|
|
3443
|
+
}
|
|
3202
3444
|
|
|
3203
3445
|
// src/lib/cache/clipsCache.ts
|
|
3204
3446
|
var LRUCache = class _LRUCache {
|
|
@@ -3711,300 +3953,321 @@ if (typeof window !== "undefined") {
|
|
|
3711
3953
|
});
|
|
3712
3954
|
});
|
|
3713
3955
|
}
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3956
|
+
var getSupabaseClient = () => {
|
|
3957
|
+
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
3958
|
+
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
3959
|
+
if (!url || !key) {
|
|
3960
|
+
throw new Error("Supabase configuration missing");
|
|
3961
|
+
}
|
|
3962
|
+
return createClient(url, key);
|
|
3963
|
+
};
|
|
3964
|
+
var getAuthToken = async () => {
|
|
3965
|
+
try {
|
|
3966
|
+
const supabase = getSupabaseClient();
|
|
3967
|
+
const { data: { session } } = await supabase.auth.getSession();
|
|
3968
|
+
return session?.access_token || null;
|
|
3969
|
+
} catch (error) {
|
|
3970
|
+
console.error("[S3ClipsAPIClient] Error getting auth token:", error);
|
|
3971
|
+
return null;
|
|
3972
|
+
}
|
|
3973
|
+
};
|
|
3974
|
+
var S3ClipsAPIClient = class {
|
|
3975
|
+
constructor(sopCategories) {
|
|
3976
|
+
this.baseUrl = "/api/clips";
|
|
3977
|
+
this.requestCache = /* @__PURE__ */ new Map();
|
|
3978
|
+
this.sopCategories = sopCategories;
|
|
3979
|
+
console.log("[S3ClipsAPIClient] \u2705 Initialized - Using secure API routes (no direct S3 access)");
|
|
3723
3980
|
}
|
|
3724
3981
|
/**
|
|
3725
|
-
*
|
|
3982
|
+
* Fetch with authentication and error handling
|
|
3726
3983
|
*/
|
|
3727
|
-
async
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3984
|
+
async fetchWithAuth(endpoint, body) {
|
|
3985
|
+
const token = await getAuthToken();
|
|
3986
|
+
if (!token) {
|
|
3987
|
+
throw new Error("Authentication required");
|
|
3988
|
+
}
|
|
3989
|
+
const response = await fetch(endpoint, {
|
|
3990
|
+
method: "POST",
|
|
3991
|
+
headers: {
|
|
3992
|
+
"Authorization": `Bearer ${token}`,
|
|
3993
|
+
"Content-Type": "application/json"
|
|
3994
|
+
},
|
|
3995
|
+
body: JSON.stringify(body)
|
|
3996
|
+
});
|
|
3997
|
+
if (!response.ok) {
|
|
3998
|
+
const error = await response.json().catch(() => ({ error: "Request failed" }));
|
|
3999
|
+
throw new Error(error.error || `API error: ${response.status}`);
|
|
4000
|
+
}
|
|
4001
|
+
return response.json();
|
|
4002
|
+
}
|
|
4003
|
+
/**
|
|
4004
|
+
* Deduplicate requests to prevent multiple API calls
|
|
4005
|
+
*/
|
|
4006
|
+
async deduplicate(key, factory) {
|
|
4007
|
+
if (this.requestCache.has(key)) {
|
|
4008
|
+
console.log(`[S3ClipsAPIClient] Deduplicating request: ${key}`);
|
|
4009
|
+
return this.requestCache.get(key);
|
|
3733
4010
|
}
|
|
3734
|
-
console.log(`[${logPrefix}] Creating new request for key: ${key}`);
|
|
3735
4011
|
const promise = factory().finally(() => {
|
|
3736
|
-
this.
|
|
3737
|
-
console.log(`[${logPrefix}] Completed and cleaned up request: ${key}`);
|
|
4012
|
+
this.requestCache.delete(key);
|
|
3738
4013
|
});
|
|
3739
|
-
this.
|
|
4014
|
+
this.requestCache.set(key, promise);
|
|
3740
4015
|
return promise;
|
|
3741
4016
|
}
|
|
3742
4017
|
/**
|
|
3743
|
-
*
|
|
4018
|
+
* List S3 clips
|
|
3744
4019
|
*/
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
this.
|
|
4020
|
+
async listS3Clips(params) {
|
|
4021
|
+
const cacheKey = `list:${JSON.stringify(params)}`;
|
|
4022
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4023
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4024
|
+
action: "list",
|
|
4025
|
+
workspaceId: params.workspaceId,
|
|
4026
|
+
date: params.date,
|
|
4027
|
+
shift: params.shiftId,
|
|
4028
|
+
maxKeys: params.maxKeys
|
|
4029
|
+
});
|
|
4030
|
+
return response.clips.map((clip) => clip.originalUri);
|
|
4031
|
+
});
|
|
3748
4032
|
}
|
|
3749
4033
|
/**
|
|
3750
|
-
* Get
|
|
4034
|
+
* Get clip counts
|
|
3751
4035
|
*/
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
4036
|
+
async getClipCounts(workspaceId, date, shiftId) {
|
|
4037
|
+
const cacheKey = `counts:${workspaceId}:${date}:${shiftId}`;
|
|
4038
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4039
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4040
|
+
action: "count",
|
|
4041
|
+
workspaceId,
|
|
4042
|
+
date,
|
|
4043
|
+
shift: shiftId.toString()
|
|
4044
|
+
});
|
|
4045
|
+
return response.counts;
|
|
4046
|
+
});
|
|
4047
|
+
}
|
|
4048
|
+
/**
|
|
4049
|
+
* Get clip counts with index (for compatibility)
|
|
4050
|
+
*/
|
|
4051
|
+
async getClipCountsWithIndex(workspaceId, date, shiftId) {
|
|
4052
|
+
const counts = await this.getClipCounts(workspaceId, date, shiftId.toString());
|
|
4053
|
+
const videoIndex = {
|
|
4054
|
+
byCategory: /* @__PURE__ */ new Map(),
|
|
4055
|
+
allVideos: [],
|
|
4056
|
+
counts,
|
|
4057
|
+
workspaceId,
|
|
4058
|
+
date,
|
|
4059
|
+
shiftId: shiftId.toString(),
|
|
4060
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
3756
4061
|
};
|
|
4062
|
+
return { counts, videoIndex };
|
|
3757
4063
|
}
|
|
3758
4064
|
/**
|
|
3759
|
-
*
|
|
4065
|
+
* Get metadata for a video
|
|
3760
4066
|
*/
|
|
3761
|
-
|
|
3762
|
-
const
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
4067
|
+
async getMetadata(playlistUri) {
|
|
4068
|
+
const cacheKey = `metadata:${playlistUri}`;
|
|
4069
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4070
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4071
|
+
action: "metadata",
|
|
4072
|
+
playlistUri
|
|
4073
|
+
});
|
|
4074
|
+
return response.metadata;
|
|
4075
|
+
});
|
|
4076
|
+
}
|
|
4077
|
+
/**
|
|
4078
|
+
* Get metadata cycle time
|
|
4079
|
+
*/
|
|
4080
|
+
async getMetadataCycleTime(playlistUri) {
|
|
4081
|
+
const metadata = await this.getMetadata(playlistUri);
|
|
4082
|
+
return metadata?.cycle_time_seconds || null;
|
|
4083
|
+
}
|
|
4084
|
+
/**
|
|
4085
|
+
* Get first clip for category
|
|
4086
|
+
*/
|
|
4087
|
+
async getFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4088
|
+
const cacheKey = `first:${workspaceId}:${date}:${shiftId}:${category}`;
|
|
4089
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4090
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4091
|
+
action: "first",
|
|
4092
|
+
workspaceId,
|
|
4093
|
+
date,
|
|
4094
|
+
shift: shiftId.toString(),
|
|
4095
|
+
category,
|
|
4096
|
+
sopCategories: this.sopCategories
|
|
4097
|
+
});
|
|
4098
|
+
return response.video;
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
/**
|
|
4102
|
+
* Get clip by index
|
|
4103
|
+
*/
|
|
4104
|
+
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4105
|
+
const cacheKey = `by-index:${workspaceId}:${date}:${shiftId}:${category}:${index}`;
|
|
4106
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4107
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4108
|
+
action: "by-index",
|
|
4109
|
+
workspaceId,
|
|
4110
|
+
date,
|
|
4111
|
+
shift: shiftId.toString(),
|
|
4112
|
+
category,
|
|
4113
|
+
index,
|
|
4114
|
+
sopCategories: this.sopCategories
|
|
4115
|
+
});
|
|
4116
|
+
const video = response.video;
|
|
4117
|
+
if (video && includeMetadata && video.originalUri) {
|
|
4118
|
+
try {
|
|
4119
|
+
const metadata = await this.getMetadata(video.originalUri);
|
|
4120
|
+
if (metadata) {
|
|
4121
|
+
video.cycle_time_seconds = metadata.cycle_time_seconds;
|
|
4122
|
+
video.creation_timestamp = metadata.creation_timestamp;
|
|
4123
|
+
}
|
|
4124
|
+
} catch (error) {
|
|
4125
|
+
console.warn("[S3ClipsAPIClient] Failed to fetch metadata:", error);
|
|
4126
|
+
}
|
|
3771
4127
|
}
|
|
3772
|
-
|
|
3773
|
-
}
|
|
4128
|
+
return video;
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
4131
|
+
/**
|
|
4132
|
+
* Get videos page with pagination
|
|
4133
|
+
*/
|
|
4134
|
+
async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
|
|
4135
|
+
const cacheKey = `page:${workspaceId}:${date}:${shiftId}:${category}:${pageSize}:${startAfter || "first"}`;
|
|
4136
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4137
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4138
|
+
action: "page",
|
|
4139
|
+
workspaceId,
|
|
4140
|
+
date,
|
|
4141
|
+
shift: shiftId.toString(),
|
|
4142
|
+
category,
|
|
4143
|
+
pageSize,
|
|
4144
|
+
startAfter,
|
|
4145
|
+
sopCategories: this.sopCategories
|
|
4146
|
+
});
|
|
4147
|
+
return {
|
|
4148
|
+
videos: response.videos,
|
|
4149
|
+
nextToken: response.nextToken,
|
|
4150
|
+
hasMore: response.hasMore
|
|
4151
|
+
};
|
|
4152
|
+
});
|
|
4153
|
+
}
|
|
4154
|
+
/**
|
|
4155
|
+
* Convert S3 URI to CloudFront URL
|
|
4156
|
+
* In the API client, URLs are already signed from the server
|
|
4157
|
+
*/
|
|
4158
|
+
s3UriToCloudfront(s3Uri) {
|
|
4159
|
+
return s3Uri;
|
|
4160
|
+
}
|
|
4161
|
+
/**
|
|
4162
|
+
* Clean up resources
|
|
4163
|
+
*/
|
|
4164
|
+
dispose() {
|
|
4165
|
+
this.requestCache.clear();
|
|
4166
|
+
}
|
|
4167
|
+
/**
|
|
4168
|
+
* Get service statistics
|
|
4169
|
+
*/
|
|
4170
|
+
getStats() {
|
|
4171
|
+
return {
|
|
4172
|
+
requestCache: {
|
|
4173
|
+
pendingCount: this.requestCache.size,
|
|
4174
|
+
maxSize: 1e3
|
|
4175
|
+
}
|
|
4176
|
+
};
|
|
3774
4177
|
}
|
|
3775
4178
|
};
|
|
4179
|
+
|
|
4180
|
+
// src/lib/api/s3-clips-secure.ts
|
|
3776
4181
|
var S3ClipsService = class {
|
|
3777
4182
|
constructor(config) {
|
|
3778
|
-
//
|
|
3779
|
-
this.requestCache = new RequestDeduplicationCache();
|
|
3780
|
-
// Flag to prevent metadata fetching during index building
|
|
4183
|
+
// Flags for compatibility
|
|
3781
4184
|
this.isIndexBuilding = false;
|
|
3782
|
-
// Flag to prevent metadata fetching during entire prefetch operation
|
|
3783
4185
|
this.isPrefetching = false;
|
|
3784
|
-
// Global safeguard: limit concurrent metadata fetches to prevent flooding
|
|
3785
4186
|
this.currentMetadataFetches = 0;
|
|
3786
4187
|
this.MAX_CONCURRENT_METADATA = 3;
|
|
3787
4188
|
this.config = config;
|
|
3788
4189
|
if (!config.s3Config) {
|
|
3789
4190
|
throw new Error("S3 configuration is required");
|
|
3790
4191
|
}
|
|
4192
|
+
const sopCategories = config.s3Config.sopCategories?.default;
|
|
4193
|
+
this.apiClient = new S3ClipsAPIClient(sopCategories);
|
|
3791
4194
|
const processing = config.s3Config.processing || {};
|
|
3792
4195
|
this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
|
|
3793
4196
|
this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
|
|
3794
4197
|
this.concurrencyLimit = processing.concurrencyLimit || 10;
|
|
3795
4198
|
this.maxInitialFetch = processing.maxInitialFetch || 60;
|
|
3796
|
-
|
|
3797
|
-
console.log(`S3ClipsService: Using AWS region: ${region}`);
|
|
3798
|
-
this.s3Client = new S3Client({
|
|
3799
|
-
region,
|
|
3800
|
-
credentials: config.s3Config.credentials ? {
|
|
3801
|
-
accessKeyId: config.s3Config.credentials.accessKeyId,
|
|
3802
|
-
secretAccessKey: config.s3Config.credentials.secretAccessKey
|
|
3803
|
-
} : void 0
|
|
3804
|
-
});
|
|
3805
|
-
}
|
|
3806
|
-
/**
|
|
3807
|
-
* Validates and sanitizes the AWS region
|
|
3808
|
-
*/
|
|
3809
|
-
validateAndSanitizeRegion(region) {
|
|
3810
|
-
const defaultRegion = "us-east-1";
|
|
3811
|
-
if (!region || typeof region !== "string") {
|
|
3812
|
-
console.warn(`S3ClipsService: Invalid region provided (${region}), using default: ${defaultRegion}`);
|
|
3813
|
-
return defaultRegion;
|
|
3814
|
-
}
|
|
3815
|
-
const sanitizedRegion = region.trim().toLowerCase();
|
|
3816
|
-
if (!sanitizedRegion) {
|
|
3817
|
-
console.warn(`S3ClipsService: Empty region provided, using default: ${defaultRegion}`);
|
|
3818
|
-
return defaultRegion;
|
|
3819
|
-
}
|
|
3820
|
-
const regionPattern = /^[a-z]{2,3}-[a-z]+-\d+$/;
|
|
3821
|
-
if (!regionPattern.test(sanitizedRegion)) {
|
|
3822
|
-
console.warn(`S3ClipsService: Invalid region format (${sanitizedRegion}), using default: ${defaultRegion}`);
|
|
3823
|
-
return defaultRegion;
|
|
3824
|
-
}
|
|
3825
|
-
return sanitizedRegion;
|
|
4199
|
+
console.log("[S3ClipsService] \u2705 Initialized with secure API client - AWS credentials are now server-side only!");
|
|
3826
4200
|
}
|
|
3827
4201
|
/**
|
|
3828
|
-
* Lists S3 clips
|
|
4202
|
+
* Lists S3 clips using API
|
|
3829
4203
|
*/
|
|
3830
4204
|
async listS3Clips(params) {
|
|
3831
|
-
const { workspaceId, date, shiftId
|
|
4205
|
+
const { workspaceId, date, shiftId } = params;
|
|
3832
4206
|
if (!isValidShiftId(shiftId)) {
|
|
3833
|
-
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}
|
|
4207
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
3834
4208
|
return [];
|
|
3835
4209
|
}
|
|
3836
|
-
|
|
3837
|
-
console.log(`[S3ClipsService] Listing clips for workspace: ${workspaceId}, date: ${date}, shift: ${shiftId}`);
|
|
3838
|
-
const deduplicationKey = `list-s3-clips:${prefix}:${maxKeys || "all"}`;
|
|
3839
|
-
return this.requestCache.deduplicate(
|
|
3840
|
-
deduplicationKey,
|
|
3841
|
-
() => this.executeListS3Clips(params),
|
|
3842
|
-
"ListS3Clips"
|
|
3843
|
-
);
|
|
3844
|
-
}
|
|
3845
|
-
/**
|
|
3846
|
-
* Internal implementation of S3 listing (called through deduplication)
|
|
3847
|
-
*/
|
|
3848
|
-
async executeListS3Clips(params) {
|
|
3849
|
-
const { workspaceId, date, shiftId, maxKeys } = params;
|
|
3850
|
-
const prefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
|
|
3851
|
-
console.log(`[S3ClipsService] Executing S3 list for prefix: ${prefix}`);
|
|
4210
|
+
console.log(`[S3ClipsService] Listing clips via API for workspace: ${workspaceId}`);
|
|
3852
4211
|
try {
|
|
3853
|
-
|
|
3854
|
-
let continuationToken = void 0;
|
|
3855
|
-
do {
|
|
3856
|
-
const command = new ListObjectsV2Command({
|
|
3857
|
-
Bucket: this.config.s3Config.bucketName,
|
|
3858
|
-
Prefix: prefix,
|
|
3859
|
-
ContinuationToken: continuationToken,
|
|
3860
|
-
MaxKeys: maxKeys ?? 1e3
|
|
3861
|
-
});
|
|
3862
|
-
const response = await this.s3Client.send(command);
|
|
3863
|
-
console.log(`Got S3 response for ${prefix}:`, {
|
|
3864
|
-
keyCount: response.KeyCount,
|
|
3865
|
-
contentsLength: response.Contents?.length || 0,
|
|
3866
|
-
hasContinuation: !!response.NextContinuationToken
|
|
3867
|
-
});
|
|
3868
|
-
if (response.Contents) {
|
|
3869
|
-
if (response.Contents.length > 0) {
|
|
3870
|
-
console.log("Sample Keys:", response.Contents.slice(0, 3).map((obj) => obj.Key));
|
|
3871
|
-
}
|
|
3872
|
-
for (const obj of response.Contents) {
|
|
3873
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
3874
|
-
if (obj.Key.includes("missed_qchecks")) {
|
|
3875
|
-
console.log(`Skipping missed_qchecks path: ${obj.Key}`);
|
|
3876
|
-
continue;
|
|
3877
|
-
}
|
|
3878
|
-
playlists.push(`s3://${this.config.s3Config.bucketName}/${obj.Key}`);
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
}
|
|
3882
|
-
continuationToken = response.NextContinuationToken;
|
|
3883
|
-
if (maxKeys && playlists.length >= maxKeys) {
|
|
3884
|
-
break;
|
|
3885
|
-
}
|
|
3886
|
-
} while (continuationToken && (!maxKeys || playlists.length < maxKeys));
|
|
3887
|
-
console.log(`Found ${playlists.length} HLS playlists in ${prefix}`);
|
|
3888
|
-
if (playlists.length > 0) {
|
|
3889
|
-
console.log("First playlist URI:", playlists[0]);
|
|
3890
|
-
}
|
|
3891
|
-
return playlists;
|
|
4212
|
+
return await this.apiClient.listS3Clips(params);
|
|
3892
4213
|
} catch (error) {
|
|
3893
|
-
console.error(
|
|
4214
|
+
console.error("[S3ClipsService] Error listing clips:", error);
|
|
3894
4215
|
return [];
|
|
3895
4216
|
}
|
|
3896
4217
|
}
|
|
3897
4218
|
/**
|
|
3898
|
-
*
|
|
4219
|
+
* Get metadata cycle time
|
|
3899
4220
|
*/
|
|
3900
4221
|
async getMetadataCycleTime(playlistUri) {
|
|
3901
|
-
const deduplicationKey = `metadata-cycle-time:${playlistUri}`;
|
|
3902
|
-
return this.requestCache.deduplicate(
|
|
3903
|
-
deduplicationKey,
|
|
3904
|
-
() => this.executeGetMetadataCycleTime(playlistUri),
|
|
3905
|
-
"MetadataCycleTime"
|
|
3906
|
-
);
|
|
3907
|
-
}
|
|
3908
|
-
/**
|
|
3909
|
-
* Internal implementation of metadata cycle time fetching
|
|
3910
|
-
*/
|
|
3911
|
-
async executeGetMetadataCycleTime(playlistUri) {
|
|
3912
4222
|
try {
|
|
3913
|
-
|
|
3914
|
-
const url = new URL(metadataUri);
|
|
3915
|
-
const bucket = url.hostname;
|
|
3916
|
-
const key = url.pathname.substring(1);
|
|
3917
|
-
console.log(`[S3ClipsService] Fetching metadata cycle time for: ${key}`);
|
|
3918
|
-
const command = new GetObjectCommand({
|
|
3919
|
-
Bucket: bucket,
|
|
3920
|
-
Key: key
|
|
3921
|
-
});
|
|
3922
|
-
const response = await this.s3Client.send(command);
|
|
3923
|
-
if (!response.Body) {
|
|
3924
|
-
console.warn(`Empty response body for metadata file: ${key}`);
|
|
3925
|
-
return null;
|
|
3926
|
-
}
|
|
3927
|
-
const metadataContent = await response.Body.transformToString();
|
|
3928
|
-
const metadata = JSON.parse(metadataContent);
|
|
3929
|
-
const cycleTimeFrames = metadata?.original_task_metadata?.cycle_time;
|
|
3930
|
-
if (typeof cycleTimeFrames === "number") {
|
|
3931
|
-
return cycleTimeFrames;
|
|
3932
|
-
}
|
|
3933
|
-
return null;
|
|
4223
|
+
return await this.apiClient.getMetadataCycleTime(playlistUri);
|
|
3934
4224
|
} catch (error) {
|
|
3935
|
-
console.error(
|
|
4225
|
+
console.error("[S3ClipsService] Error fetching metadata cycle time:", error);
|
|
3936
4226
|
return null;
|
|
3937
4227
|
}
|
|
3938
4228
|
}
|
|
3939
4229
|
/**
|
|
3940
|
-
* Control prefetch mode
|
|
4230
|
+
* Control prefetch mode
|
|
3941
4231
|
*/
|
|
3942
4232
|
setPrefetchMode(enabled) {
|
|
3943
4233
|
this.isPrefetching = enabled;
|
|
3944
|
-
console.log(`[S3ClipsService] Prefetch mode ${enabled ? "enabled" : "disabled"}
|
|
4234
|
+
console.log(`[S3ClipsService] Prefetch mode ${enabled ? "enabled" : "disabled"}`);
|
|
3945
4235
|
}
|
|
3946
4236
|
/**
|
|
3947
|
-
*
|
|
4237
|
+
* Get full metadata
|
|
3948
4238
|
*/
|
|
3949
4239
|
async getFullMetadata(playlistUri) {
|
|
3950
4240
|
if (this.isIndexBuilding || this.isPrefetching) {
|
|
3951
|
-
console.warn(
|
|
4241
|
+
console.warn("[S3ClipsService] Skipping metadata - operation in progress");
|
|
3952
4242
|
return null;
|
|
3953
4243
|
}
|
|
3954
4244
|
if (this.currentMetadataFetches >= this.MAX_CONCURRENT_METADATA) {
|
|
3955
|
-
console.warn(
|
|
4245
|
+
console.warn("[S3ClipsService] Skipping metadata - max concurrent fetches reached");
|
|
3956
4246
|
return null;
|
|
3957
4247
|
}
|
|
3958
4248
|
this.currentMetadataFetches++;
|
|
3959
4249
|
try {
|
|
3960
|
-
|
|
3961
|
-
return await this.requestCache.deduplicate(
|
|
3962
|
-
deduplicationKey,
|
|
3963
|
-
() => this.executeGetFullMetadata(playlistUri),
|
|
3964
|
-
"FullMetadata"
|
|
3965
|
-
);
|
|
3966
|
-
} finally {
|
|
3967
|
-
this.currentMetadataFetches--;
|
|
3968
|
-
}
|
|
3969
|
-
}
|
|
3970
|
-
/**
|
|
3971
|
-
* Internal implementation of full metadata fetching
|
|
3972
|
-
*/
|
|
3973
|
-
async executeGetFullMetadata(playlistUri) {
|
|
3974
|
-
try {
|
|
3975
|
-
const metadataUri = playlistUri.replace(/playlist\.m3u8$/, "metadata.json");
|
|
3976
|
-
const url = new URL(metadataUri);
|
|
3977
|
-
const bucket = url.hostname;
|
|
3978
|
-
const key = url.pathname.substring(1);
|
|
3979
|
-
console.log(`[S3ClipsService] Fetching full metadata for: ${key}`);
|
|
3980
|
-
const command = new GetObjectCommand({
|
|
3981
|
-
Bucket: bucket,
|
|
3982
|
-
Key: key
|
|
3983
|
-
});
|
|
3984
|
-
const response = await this.s3Client.send(command);
|
|
3985
|
-
if (!response.Body) {
|
|
3986
|
-
console.warn(`Empty response body for metadata file: ${key}`);
|
|
3987
|
-
return null;
|
|
3988
|
-
}
|
|
3989
|
-
const metadataContent = await response.Body.transformToString();
|
|
3990
|
-
const metadata = JSON.parse(metadataContent);
|
|
3991
|
-
return metadata;
|
|
4250
|
+
return await this.apiClient.getMetadata(playlistUri);
|
|
3992
4251
|
} catch (error) {
|
|
3993
|
-
console.error(
|
|
4252
|
+
console.error("[S3ClipsService] Error fetching metadata:", error);
|
|
3994
4253
|
return null;
|
|
4254
|
+
} finally {
|
|
4255
|
+
this.currentMetadataFetches--;
|
|
3995
4256
|
}
|
|
3996
4257
|
}
|
|
3997
4258
|
/**
|
|
3998
|
-
*
|
|
4259
|
+
* Convert S3 URI to CloudFront URL
|
|
4260
|
+
* URLs from API are already signed
|
|
3999
4261
|
*/
|
|
4000
4262
|
s3UriToCloudfront(s3Uri) {
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4263
|
+
if (s3Uri.startsWith("http")) {
|
|
4264
|
+
return s3Uri;
|
|
4265
|
+
}
|
|
4266
|
+
console.warn("[S3ClipsService] Unexpected S3 URI in secure mode:", s3Uri);
|
|
4267
|
+
return s3Uri;
|
|
4005
4268
|
}
|
|
4006
4269
|
/**
|
|
4007
|
-
*
|
|
4270
|
+
* Get SOP categories for workspace
|
|
4008
4271
|
*/
|
|
4009
4272
|
getSOPCategories(workspaceId) {
|
|
4010
4273
|
const sopConfig = this.config.s3Config?.sopCategories;
|
|
@@ -4016,375 +4279,85 @@ var S3ClipsService = class {
|
|
|
4016
4279
|
}
|
|
4017
4280
|
async getClipCounts(workspaceId, date, shiftId, buildIndex) {
|
|
4018
4281
|
if (!isValidShiftId(shiftId)) {
|
|
4019
|
-
console.error(`[S3ClipsService]
|
|
4020
|
-
return buildIndex ? { counts: {}, videoIndex:
|
|
4021
|
-
}
|
|
4022
|
-
const deduplicationKey = `clip-counts:${workspaceId}:${date}:${shiftId}:${buildIndex ? "with-index" : "counts-only"}`;
|
|
4023
|
-
return this.requestCache.deduplicate(
|
|
4024
|
-
deduplicationKey,
|
|
4025
|
-
() => this.executeGetClipCounts(workspaceId, date, shiftId, buildIndex),
|
|
4026
|
-
"ClipCounts"
|
|
4027
|
-
);
|
|
4028
|
-
}
|
|
4029
|
-
/**
|
|
4030
|
-
* Internal implementation of clip counts fetching
|
|
4031
|
-
*/
|
|
4032
|
-
async executeGetClipCounts(workspaceId, date, shiftId, buildIndex) {
|
|
4033
|
-
if (buildIndex) {
|
|
4034
|
-
this.isIndexBuilding = true;
|
|
4035
|
-
console.log(`[S3ClipsService] Starting index building - metadata fetching disabled`);
|
|
4282
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
4283
|
+
return buildIndex ? { counts: {}, videoIndex: this.createEmptyVideoIndex(workspaceId, date, shiftId.toString()) } : {};
|
|
4036
4284
|
}
|
|
4037
4285
|
try {
|
|
4038
|
-
const basePrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
|
|
4039
|
-
const counts = { total: 0 };
|
|
4040
|
-
const categoryFolders = [
|
|
4041
|
-
"idle_time",
|
|
4042
|
-
"low_value",
|
|
4043
|
-
"sop_deviation",
|
|
4044
|
-
"missing_quality_check",
|
|
4045
|
-
"best_cycle_time",
|
|
4046
|
-
"worst_cycle_time",
|
|
4047
|
-
"long_cycle_time",
|
|
4048
|
-
"cycle_completion",
|
|
4049
|
-
"bottleneck"
|
|
4050
|
-
];
|
|
4051
|
-
const shiftName = shiftId === 0 || shiftId === "0" ? "Day" : "Night";
|
|
4052
|
-
console.log(`[S3ClipsService] ${buildIndex ? "Building video index and counting" : "Fast counting"} clips for ${workspaceId} on ${date}, shift ${shiftId} (${shiftName} Shift)`);
|
|
4053
|
-
const startTime = performance.now();
|
|
4054
|
-
const videoIndex = buildIndex ? {
|
|
4055
|
-
byCategory: /* @__PURE__ */ new Map(),
|
|
4056
|
-
allVideos: [],
|
|
4057
|
-
counts: {},
|
|
4058
|
-
workspaceId,
|
|
4059
|
-
date,
|
|
4060
|
-
shiftId: shiftId.toString(),
|
|
4061
|
-
lastUpdated: /* @__PURE__ */ new Date(),
|
|
4062
|
-
_debugId: `vid_${Date.now()}_${Math.random().toString(36).substring(7)}`
|
|
4063
|
-
} : null;
|
|
4064
|
-
const countPromises = categoryFolders.map(async (category) => {
|
|
4065
|
-
const categoryPrefix = `${basePrefix}${category}/videos/`;
|
|
4066
|
-
const categoryVideos = [];
|
|
4067
|
-
try {
|
|
4068
|
-
if (buildIndex) {
|
|
4069
|
-
const command = new ListObjectsV2Command({
|
|
4070
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4071
|
-
Prefix: categoryPrefix,
|
|
4072
|
-
MaxKeys: 1e3
|
|
4073
|
-
});
|
|
4074
|
-
let continuationToken;
|
|
4075
|
-
do {
|
|
4076
|
-
if (continuationToken) {
|
|
4077
|
-
command.input.ContinuationToken = continuationToken;
|
|
4078
|
-
}
|
|
4079
|
-
const response = await this.s3Client.send(command);
|
|
4080
|
-
if (response.Contents) {
|
|
4081
|
-
for (const obj of response.Contents) {
|
|
4082
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
4083
|
-
if (obj.Key.includes("missed_qchecks")) {
|
|
4084
|
-
continue;
|
|
4085
|
-
}
|
|
4086
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${obj.Key}`;
|
|
4087
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4088
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4089
|
-
const belongsToCategory = parsedInfo && (parsedInfo.type === category || // Handle specific mismatches between folder names and parsed types
|
|
4090
|
-
category === "cycle_completion" && parsedInfo.type === "cycle_completion" || category === "sop_deviation" && parsedInfo.type === "missing_quality_check" || category === "missing_quality_check" && parsedInfo.type === "missing_quality_check" || category === "idle_time" && parsedInfo.type === "low_value" || category === "low_value" && parsedInfo.type === "low_value");
|
|
4091
|
-
if (belongsToCategory) {
|
|
4092
|
-
const videoEntry = {
|
|
4093
|
-
uri: s3Uri,
|
|
4094
|
-
category: parsedInfo.type,
|
|
4095
|
-
// Use the parsed type, not the folder name
|
|
4096
|
-
timestamp: parsedInfo.timestamp,
|
|
4097
|
-
videoId: `${workspaceId}-${parsedInfo.timestamp}`,
|
|
4098
|
-
workspaceId,
|
|
4099
|
-
date,
|
|
4100
|
-
shiftId: shiftId.toString()
|
|
4101
|
-
};
|
|
4102
|
-
categoryVideos.push(videoEntry);
|
|
4103
|
-
}
|
|
4104
|
-
}
|
|
4105
|
-
}
|
|
4106
|
-
}
|
|
4107
|
-
continuationToken = response.NextContinuationToken;
|
|
4108
|
-
} while (continuationToken);
|
|
4109
|
-
categoryVideos.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
4110
|
-
if (categoryVideos.length > 0) {
|
|
4111
|
-
console.log(`[S3ClipsService] Found ${categoryVideos.length} videos for category '${category}' (parsed types: ${[...new Set(categoryVideos.map((v) => v.category))].join(", ")})`);
|
|
4112
|
-
}
|
|
4113
|
-
return { category, count: categoryVideos.length, videos: categoryVideos };
|
|
4114
|
-
} else {
|
|
4115
|
-
const command = new ListObjectsV2Command({
|
|
4116
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4117
|
-
Prefix: categoryPrefix,
|
|
4118
|
-
Delimiter: "/",
|
|
4119
|
-
MaxKeys: 1e3
|
|
4120
|
-
});
|
|
4121
|
-
let folderCount = 0;
|
|
4122
|
-
let continuationToken;
|
|
4123
|
-
do {
|
|
4124
|
-
if (continuationToken) {
|
|
4125
|
-
command.input.ContinuationToken = continuationToken;
|
|
4126
|
-
}
|
|
4127
|
-
const response = await this.s3Client.send(command);
|
|
4128
|
-
if (response.CommonPrefixes) {
|
|
4129
|
-
folderCount += response.CommonPrefixes.length;
|
|
4130
|
-
}
|
|
4131
|
-
continuationToken = response.NextContinuationToken;
|
|
4132
|
-
} while (continuationToken);
|
|
4133
|
-
return { category, count: folderCount, videos: [] };
|
|
4134
|
-
}
|
|
4135
|
-
} catch (error) {
|
|
4136
|
-
console.error(`Error ${buildIndex ? "building index for" : "counting folders for"} category ${category}:`, error);
|
|
4137
|
-
return { category, count: 0, videos: [] };
|
|
4138
|
-
}
|
|
4139
|
-
});
|
|
4140
|
-
const results = await Promise.all(countPromises);
|
|
4141
|
-
for (const { category, count, videos } of results) {
|
|
4142
|
-
counts[category] = count;
|
|
4143
|
-
counts.total += count;
|
|
4144
|
-
if (buildIndex && videoIndex && videos) {
|
|
4145
|
-
if (videos.length > 0) {
|
|
4146
|
-
const parsedType = videos[0].category;
|
|
4147
|
-
videoIndex.byCategory.set(parsedType, videos);
|
|
4148
|
-
console.log(`[S3ClipsService] Indexed ${videos.length} videos under parsed type '${parsedType}'`);
|
|
4149
|
-
if (category !== parsedType) {
|
|
4150
|
-
videoIndex.byCategory.set(category, videos);
|
|
4151
|
-
console.log(`[S3ClipsService] Created alias: S3 folder '${category}' -> parsed type '${parsedType}' (${videos.length} videos)`);
|
|
4152
|
-
}
|
|
4153
|
-
}
|
|
4154
|
-
videoIndex.allVideos.push(...videos);
|
|
4155
|
-
}
|
|
4156
|
-
}
|
|
4157
|
-
if (buildIndex && videoIndex) {
|
|
4158
|
-
videoIndex.allVideos.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
4159
|
-
videoIndex.counts = { ...counts };
|
|
4160
|
-
}
|
|
4161
|
-
const elapsed = performance.now() - startTime;
|
|
4162
|
-
console.log(`[S3ClipsService] ${buildIndex ? "Video index and counts" : "Clip counts"} completed in ${elapsed.toFixed(2)}ms - Total: ${counts.total}`);
|
|
4163
|
-
if (buildIndex && videoIndex) {
|
|
4164
|
-
console.log(`[S3ClipsService] Final video index summary:`);
|
|
4165
|
-
console.log(` - VideoIndex ID: ${videoIndex._debugId}`);
|
|
4166
|
-
console.log(` - Total videos in allVideos: ${videoIndex.allVideos.length}`);
|
|
4167
|
-
console.log(` - Categories in byCategory Map: ${Array.from(videoIndex.byCategory.keys()).join(", ")}`);
|
|
4168
|
-
for (const [cat, vids] of videoIndex.byCategory.entries()) {
|
|
4169
|
-
console.log(` - '${cat}': ${vids.length} videos`);
|
|
4170
|
-
}
|
|
4171
|
-
return { counts, videoIndex };
|
|
4172
|
-
}
|
|
4173
|
-
return counts;
|
|
4174
|
-
} finally {
|
|
4175
4286
|
if (buildIndex) {
|
|
4176
|
-
this.
|
|
4177
|
-
|
|
4287
|
+
const result = await this.apiClient.getClipCountsWithIndex(workspaceId, date, shiftId);
|
|
4288
|
+
const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
|
|
4289
|
+
await smartVideoCache.setClipCounts(cacheKey, result);
|
|
4290
|
+
return result;
|
|
4291
|
+
} else {
|
|
4292
|
+
const counts = await this.apiClient.getClipCounts(workspaceId, date, shiftId);
|
|
4293
|
+
return counts;
|
|
4178
4294
|
}
|
|
4295
|
+
} catch (error) {
|
|
4296
|
+
console.error("[S3ClipsService] Error fetching clip counts:", error);
|
|
4297
|
+
return buildIndex ? { counts: {}, videoIndex: this.createEmptyVideoIndex(workspaceId, date, shiftId.toString()) } : {};
|
|
4179
4298
|
}
|
|
4180
4299
|
}
|
|
4181
4300
|
async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex) {
|
|
4182
4301
|
const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
|
|
4183
4302
|
const cachedResult = await smartVideoCache.getClipCounts(cacheKey);
|
|
4184
4303
|
if (cachedResult) {
|
|
4185
|
-
console.log(
|
|
4186
|
-
|
|
4187
|
-
return cachedResult;
|
|
4188
|
-
} else {
|
|
4189
|
-
return cachedResult.counts;
|
|
4190
|
-
}
|
|
4304
|
+
console.log("[S3ClipsService] Using cached clip counts");
|
|
4305
|
+
return buildIndex ? cachedResult : cachedResult.counts;
|
|
4191
4306
|
}
|
|
4192
|
-
console.log(
|
|
4307
|
+
console.log("[S3ClipsService] Cache miss - fetching from API");
|
|
4193
4308
|
return buildIndex ? this.getClipCounts(workspaceId, date, shiftId, true) : this.getClipCounts(workspaceId, date, shiftId);
|
|
4194
4309
|
}
|
|
4195
4310
|
/**
|
|
4196
|
-
* Get first clip for
|
|
4311
|
+
* Get first clip for category
|
|
4197
4312
|
*/
|
|
4198
4313
|
async getFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4199
4314
|
if (!isValidShiftId(shiftId)) {
|
|
4200
|
-
console.error(`[S3ClipsService]
|
|
4315
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
4201
4316
|
return null;
|
|
4202
4317
|
}
|
|
4203
|
-
const deduplicationKey = `first-clip:${workspaceId}:${date}:${shiftId}:${category}`;
|
|
4204
|
-
return this.requestCache.deduplicate(
|
|
4205
|
-
deduplicationKey,
|
|
4206
|
-
() => this.executeGetFirstClipForCategory(workspaceId, date, shiftId, category),
|
|
4207
|
-
"FirstClip"
|
|
4208
|
-
);
|
|
4209
|
-
}
|
|
4210
|
-
/**
|
|
4211
|
-
* Internal implementation of first clip fetching
|
|
4212
|
-
*/
|
|
4213
|
-
async executeGetFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4214
|
-
const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
|
|
4215
4318
|
try {
|
|
4216
|
-
|
|
4217
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4218
|
-
Prefix: categoryPrefix,
|
|
4219
|
-
MaxKeys: 10
|
|
4220
|
-
// Small limit since we only need one
|
|
4221
|
-
});
|
|
4222
|
-
const response = await this.s3Client.send(command);
|
|
4223
|
-
if (response.Contents) {
|
|
4224
|
-
const playlistObj = response.Contents.find((obj) => obj.Key?.endsWith("playlist.m3u8"));
|
|
4225
|
-
if (playlistObj && playlistObj.Key) {
|
|
4226
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${playlistObj.Key}`;
|
|
4227
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4228
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4229
|
-
if (parsedInfo) {
|
|
4230
|
-
const cloudfrontUrl = this.s3UriToCloudfront(s3Uri);
|
|
4231
|
-
return {
|
|
4232
|
-
id: `${workspaceId}-${date}-${shiftId}-${category}-first`,
|
|
4233
|
-
src: cloudfrontUrl,
|
|
4234
|
-
...parsedInfo,
|
|
4235
|
-
originalUri: s3Uri
|
|
4236
|
-
};
|
|
4237
|
-
}
|
|
4238
|
-
}
|
|
4239
|
-
}
|
|
4319
|
+
return await this.apiClient.getFirstClipForCategory(workspaceId, date, shiftId, category);
|
|
4240
4320
|
} catch (error) {
|
|
4241
|
-
console.error(
|
|
4321
|
+
console.error("[S3ClipsService] Error fetching first clip:", error);
|
|
4322
|
+
return null;
|
|
4242
4323
|
}
|
|
4243
|
-
return null;
|
|
4244
4324
|
}
|
|
4245
4325
|
/**
|
|
4246
|
-
* Get
|
|
4326
|
+
* Get video from index (for compatibility)
|
|
4247
4327
|
*/
|
|
4248
4328
|
async getVideoFromIndex(videoIndex, category, index, includeCycleTime = true, includeMetadata = true) {
|
|
4329
|
+
const { workspaceId, date, shiftId } = videoIndex;
|
|
4330
|
+
return this.getClipByIndex(
|
|
4331
|
+
workspaceId,
|
|
4332
|
+
date,
|
|
4333
|
+
shiftId,
|
|
4334
|
+
category,
|
|
4335
|
+
index,
|
|
4336
|
+
includeCycleTime,
|
|
4337
|
+
includeMetadata
|
|
4338
|
+
);
|
|
4339
|
+
}
|
|
4340
|
+
/**
|
|
4341
|
+
* Get clip by index
|
|
4342
|
+
*/
|
|
4343
|
+
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4249
4344
|
try {
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
console.warn(`Video index total videos: ${videoIndex.allVideos.length}`);
|
|
4256
|
-
return null;
|
|
4257
|
-
}
|
|
4258
|
-
const videoEntry = categoryVideos[index];
|
|
4259
|
-
return this.processFullVideo(
|
|
4260
|
-
videoEntry.uri,
|
|
4345
|
+
return await this.apiClient.getClipByIndex(
|
|
4346
|
+
workspaceId,
|
|
4347
|
+
date,
|
|
4348
|
+
shiftId,
|
|
4349
|
+
category,
|
|
4261
4350
|
index,
|
|
4262
|
-
videoEntry.workspaceId,
|
|
4263
|
-
videoEntry.date,
|
|
4264
|
-
videoEntry.shiftId,
|
|
4265
4351
|
includeCycleTime,
|
|
4266
4352
|
includeMetadata
|
|
4267
4353
|
);
|
|
4268
4354
|
} catch (error) {
|
|
4269
|
-
console.error(
|
|
4355
|
+
console.error("[S3ClipsService] Error fetching clip by index:", error);
|
|
4270
4356
|
return null;
|
|
4271
4357
|
}
|
|
4272
4358
|
}
|
|
4273
4359
|
/**
|
|
4274
|
-
*
|
|
4275
|
-
* @deprecated Use getVideoFromIndex with a pre-built VideoIndex for better performance
|
|
4276
|
-
*/
|
|
4277
|
-
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4278
|
-
const deduplicationKey = `clip-by-index:${workspaceId}:${date}:${shiftId}:${category}:${index}:${includeCycleTime}:${includeMetadata}`;
|
|
4279
|
-
return this.requestCache.deduplicate(
|
|
4280
|
-
deduplicationKey,
|
|
4281
|
-
() => this.executeGetClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime, includeMetadata),
|
|
4282
|
-
"ClipByIndex"
|
|
4283
|
-
);
|
|
4284
|
-
}
|
|
4285
|
-
/**
|
|
4286
|
-
* Internal implementation of clip by index fetching
|
|
4287
|
-
*/
|
|
4288
|
-
async executeGetClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4289
|
-
const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
|
|
4290
|
-
try {
|
|
4291
|
-
const command = new ListObjectsV2Command({
|
|
4292
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4293
|
-
Prefix: categoryPrefix,
|
|
4294
|
-
MaxKeys: 1e3
|
|
4295
|
-
});
|
|
4296
|
-
let playlists = [];
|
|
4297
|
-
let continuationToken = void 0;
|
|
4298
|
-
do {
|
|
4299
|
-
if (continuationToken) {
|
|
4300
|
-
command.input.ContinuationToken = continuationToken;
|
|
4301
|
-
}
|
|
4302
|
-
const response = await this.s3Client.send(command);
|
|
4303
|
-
if (response.Contents) {
|
|
4304
|
-
for (const obj of response.Contents) {
|
|
4305
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
4306
|
-
playlists.push(`s3://${this.config.s3Config.bucketName}/${obj.Key}`);
|
|
4307
|
-
}
|
|
4308
|
-
}
|
|
4309
|
-
}
|
|
4310
|
-
continuationToken = response.NextContinuationToken;
|
|
4311
|
-
} while (continuationToken && playlists.length < index + 10);
|
|
4312
|
-
playlists.sort((a, b) => {
|
|
4313
|
-
const parsedA = parseS3Uri(a);
|
|
4314
|
-
const parsedB = parseS3Uri(b);
|
|
4315
|
-
if (!parsedA || !parsedB) return 0;
|
|
4316
|
-
return parsedB.timestamp.localeCompare(parsedA.timestamp);
|
|
4317
|
-
});
|
|
4318
|
-
if (index < playlists.length) {
|
|
4319
|
-
const uri = playlists[index];
|
|
4320
|
-
return this.processFullVideo(
|
|
4321
|
-
uri,
|
|
4322
|
-
index,
|
|
4323
|
-
workspaceId,
|
|
4324
|
-
date,
|
|
4325
|
-
shiftId.toString(),
|
|
4326
|
-
includeCycleTime,
|
|
4327
|
-
includeMetadata
|
|
4328
|
-
);
|
|
4329
|
-
}
|
|
4330
|
-
} catch (error) {
|
|
4331
|
-
console.error(`[getClipByIndex] Error getting clip at index ${index} for category ${category}:`, error);
|
|
4332
|
-
}
|
|
4333
|
-
return null;
|
|
4334
|
-
}
|
|
4335
|
-
/**
|
|
4336
|
-
* Get one sample video from each category for preloading
|
|
4337
|
-
*/
|
|
4338
|
-
async getSampleVideos(workspaceId, date, shiftId) {
|
|
4339
|
-
const basePrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
|
|
4340
|
-
const samples = {};
|
|
4341
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4342
|
-
const categoriesToSample = sopCategories ? sopCategories.map((cat) => cat.id) : ["low_value", "best_cycle_time", "worst_cycle_time", "long_cycle_time", "cycle_completion"];
|
|
4343
|
-
console.log(`[S3ClipsService] Getting sample videos for categories:`, categoriesToSample);
|
|
4344
|
-
const samplePromises = categoriesToSample.map(async (category) => {
|
|
4345
|
-
const categoryPrefix = `${basePrefix}${category}/`;
|
|
4346
|
-
try {
|
|
4347
|
-
const command = new ListObjectsV2Command({
|
|
4348
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4349
|
-
Prefix: categoryPrefix,
|
|
4350
|
-
MaxKeys: 20
|
|
4351
|
-
// Small limit since we only need one
|
|
4352
|
-
});
|
|
4353
|
-
const response = await this.s3Client.send(command);
|
|
4354
|
-
if (response.Contents) {
|
|
4355
|
-
const playlistObj = response.Contents.find((obj) => obj.Key?.endsWith("playlist.m3u8"));
|
|
4356
|
-
if (playlistObj && playlistObj.Key) {
|
|
4357
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${playlistObj.Key}`;
|
|
4358
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4359
|
-
if (parsedInfo) {
|
|
4360
|
-
return {
|
|
4361
|
-
category,
|
|
4362
|
-
sample: {
|
|
4363
|
-
id: `${workspaceId}-${date}-${shiftId}-${category}-sample`,
|
|
4364
|
-
src: this.s3UriToCloudfront(s3Uri),
|
|
4365
|
-
// Pre-convert to CloudFront URL
|
|
4366
|
-
...parsedInfo,
|
|
4367
|
-
originalUri: s3Uri
|
|
4368
|
-
}
|
|
4369
|
-
};
|
|
4370
|
-
}
|
|
4371
|
-
}
|
|
4372
|
-
}
|
|
4373
|
-
} catch (error) {
|
|
4374
|
-
console.error(`Error getting sample for category ${category}:`, error);
|
|
4375
|
-
}
|
|
4376
|
-
return { category, sample: null };
|
|
4377
|
-
});
|
|
4378
|
-
const sampleResults = await Promise.all(samplePromises);
|
|
4379
|
-
for (const { category, sample } of sampleResults) {
|
|
4380
|
-
if (sample) {
|
|
4381
|
-
samples[category] = sample;
|
|
4382
|
-
}
|
|
4383
|
-
}
|
|
4384
|
-
return samples;
|
|
4385
|
-
}
|
|
4386
|
-
/**
|
|
4387
|
-
* Processes a single video completely
|
|
4360
|
+
* Process full video (for compatibility)
|
|
4388
4361
|
*/
|
|
4389
4362
|
async processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime, includeMetadata = false) {
|
|
4390
4363
|
const sopCategories = this.getSOPCategories(workspaceId);
|
|
@@ -4393,35 +4366,35 @@ var S3ClipsService = class {
|
|
|
4393
4366
|
console.warn(`Skipping URI due to parsing failure: ${uri}`);
|
|
4394
4367
|
return null;
|
|
4395
4368
|
}
|
|
4396
|
-
|
|
4397
|
-
|
|
4369
|
+
const video = {
|
|
4370
|
+
id: `${workspaceId}-${date}-${shiftId}-${index}`,
|
|
4371
|
+
src: uri,
|
|
4372
|
+
// Already signed from API
|
|
4373
|
+
...parsedInfo,
|
|
4374
|
+
originalUri: uri
|
|
4375
|
+
};
|
|
4398
4376
|
if (includeMetadata) {
|
|
4399
4377
|
const metadata = await this.getFullMetadata(uri);
|
|
4400
4378
|
if (metadata) {
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
}
|
|
4404
|
-
creationTimestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp || metadata[""];
|
|
4379
|
+
video.cycle_time_seconds = metadata.original_task_metadata?.cycle_time;
|
|
4380
|
+
video.creation_timestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp;
|
|
4405
4381
|
}
|
|
4406
|
-
} else if (includeCycleTime && (parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time") || parsedInfo.type === "best_cycle_time" || parsedInfo.type === "worst_cycle_time" || parsedInfo.type === "cycle_completion")) {
|
|
4407
|
-
cycleTimeSeconds = null;
|
|
4408
4382
|
}
|
|
4409
|
-
|
|
4410
|
-
const { type: videoType, timestamp: videoTimestamp } = parsedInfo;
|
|
4411
|
-
return {
|
|
4412
|
-
id: `${workspaceId}-${date}-${shiftId}-${videoType}-${videoTimestamp.replace(/:/g, "")}-${index}`,
|
|
4413
|
-
src: cloudfrontPlaylistUrl,
|
|
4414
|
-
// Direct CloudFront playlist URL
|
|
4415
|
-
...parsedInfo,
|
|
4416
|
-
cycle_time_seconds: cycleTimeSeconds || void 0,
|
|
4417
|
-
creation_timestamp: creationTimestamp
|
|
4418
|
-
};
|
|
4383
|
+
return video;
|
|
4419
4384
|
}
|
|
4420
4385
|
/**
|
|
4421
|
-
*
|
|
4386
|
+
* Fetch clips (main method for compatibility)
|
|
4422
4387
|
*/
|
|
4423
4388
|
async fetchClips(params) {
|
|
4424
|
-
const {
|
|
4389
|
+
const {
|
|
4390
|
+
workspaceId,
|
|
4391
|
+
date: inputDate,
|
|
4392
|
+
shift,
|
|
4393
|
+
category,
|
|
4394
|
+
limit,
|
|
4395
|
+
offset = 0,
|
|
4396
|
+
mode
|
|
4397
|
+
} = params;
|
|
4425
4398
|
if (!workspaceId) {
|
|
4426
4399
|
throw new Error("Valid Workspace ID is required");
|
|
4427
4400
|
}
|
|
@@ -4439,88 +4412,66 @@ var S3ClipsService = class {
|
|
|
4439
4412
|
const { shiftId: currentShiftId } = getCurrentShift2(this.config.dateTimeConfig?.defaultTimezone);
|
|
4440
4413
|
shiftId = currentShiftId;
|
|
4441
4414
|
}
|
|
4442
|
-
console.log(`S3ClipsService
|
|
4415
|
+
console.log(`[S3ClipsService] Fetching clips for workspace ${workspaceId}`);
|
|
4443
4416
|
if (mode === "summary") {
|
|
4444
4417
|
const counts = await this.getClipCounts(workspaceId, date, shiftId.toString());
|
|
4445
4418
|
return { counts, samples: {} };
|
|
4446
4419
|
}
|
|
4447
4420
|
const effectiveLimit = limit || this.defaultLimitPerCategory;
|
|
4448
|
-
const
|
|
4421
|
+
const result = await this.getVideosPage(
|
|
4449
4422
|
workspaceId,
|
|
4450
4423
|
date,
|
|
4451
4424
|
shiftId,
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
if (!parsedInfo) return false;
|
|
4464
|
-
if (category === "long_cycle_time") {
|
|
4465
|
-
return parsedInfo.type === "long_cycle_time" || parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time");
|
|
4466
|
-
}
|
|
4467
|
-
return parsedInfo.type === category;
|
|
4468
|
-
});
|
|
4469
|
-
filteredUris = filteredUris.slice(offset, offset + effectiveLimit);
|
|
4470
|
-
}
|
|
4471
|
-
const videoPromises = filteredUris.map(async (uri, index) => {
|
|
4472
|
-
return this.processFullVideo(
|
|
4473
|
-
uri,
|
|
4474
|
-
index,
|
|
4425
|
+
category || "all",
|
|
4426
|
+
effectiveLimit
|
|
4427
|
+
);
|
|
4428
|
+
return result.videos;
|
|
4429
|
+
}
|
|
4430
|
+
/**
|
|
4431
|
+
* Get videos page using pagination API
|
|
4432
|
+
*/
|
|
4433
|
+
async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
|
|
4434
|
+
try {
|
|
4435
|
+
return await this.apiClient.getVideosPage(
|
|
4475
4436
|
workspaceId,
|
|
4476
4437
|
date,
|
|
4477
|
-
shiftId
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4438
|
+
shiftId,
|
|
4439
|
+
category,
|
|
4440
|
+
pageSize,
|
|
4441
|
+
startAfter
|
|
4481
4442
|
);
|
|
4482
|
-
})
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
if (timestampStart || timestampEnd) {
|
|
4486
|
-
videos = videos.filter((video) => {
|
|
4487
|
-
if (!video.creation_timestamp) return false;
|
|
4488
|
-
const videoTimestamp = new Date(video.creation_timestamp).getTime();
|
|
4489
|
-
if (timestampStart && timestampEnd) {
|
|
4490
|
-
const start = new Date(timestampStart).getTime();
|
|
4491
|
-
const end = new Date(timestampEnd).getTime();
|
|
4492
|
-
return videoTimestamp >= start && videoTimestamp <= end;
|
|
4493
|
-
} else if (timestampStart) {
|
|
4494
|
-
const start = new Date(timestampStart).getTime();
|
|
4495
|
-
return videoTimestamp >= start;
|
|
4496
|
-
} else if (timestampEnd) {
|
|
4497
|
-
const end = new Date(timestampEnd).getTime();
|
|
4498
|
-
return videoTimestamp <= end;
|
|
4499
|
-
}
|
|
4500
|
-
return true;
|
|
4501
|
-
});
|
|
4502
|
-
}
|
|
4503
|
-
videos.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
4504
|
-
const cacheKey = `videos:${workspaceId}:${date}:${shiftId}:${category || "all"}`;
|
|
4505
|
-
if (videos.length > 0) {
|
|
4506
|
-
smartVideoCache.setVideos(cacheKey, videos);
|
|
4443
|
+
} catch (error) {
|
|
4444
|
+
console.error("[S3ClipsService] Error fetching videos page:", error);
|
|
4445
|
+
return { videos: [], hasMore: false };
|
|
4507
4446
|
}
|
|
4508
|
-
return videos;
|
|
4509
4447
|
}
|
|
4510
4448
|
/**
|
|
4511
|
-
*
|
|
4449
|
+
* Create empty video index for compatibility
|
|
4450
|
+
*/
|
|
4451
|
+
createEmptyVideoIndex(workspaceId, date, shiftId) {
|
|
4452
|
+
return {
|
|
4453
|
+
byCategory: /* @__PURE__ */ new Map(),
|
|
4454
|
+
allVideos: [],
|
|
4455
|
+
counts: {},
|
|
4456
|
+
workspaceId,
|
|
4457
|
+
date,
|
|
4458
|
+
shiftId,
|
|
4459
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
4460
|
+
_debugId: `empty_${Date.now()}`
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4463
|
+
/**
|
|
4464
|
+
* Cleanup
|
|
4512
4465
|
*/
|
|
4513
4466
|
dispose() {
|
|
4514
|
-
console.log("[S3ClipsService] Disposing service
|
|
4515
|
-
this.
|
|
4467
|
+
console.log("[S3ClipsService] Disposing service");
|
|
4468
|
+
this.apiClient.dispose();
|
|
4516
4469
|
}
|
|
4517
4470
|
/**
|
|
4518
|
-
* Get
|
|
4471
|
+
* Get statistics
|
|
4519
4472
|
*/
|
|
4520
4473
|
getStats() {
|
|
4521
|
-
return
|
|
4522
|
-
requestCache: this.requestCache.getStats()
|
|
4523
|
-
};
|
|
4474
|
+
return this.apiClient.getStats();
|
|
4524
4475
|
}
|
|
4525
4476
|
};
|
|
4526
4477
|
|
|
@@ -8435,6 +8386,16 @@ function useTicketHistory(companyId) {
|
|
|
8435
8386
|
throw err;
|
|
8436
8387
|
}
|
|
8437
8388
|
}, [refreshTickets]);
|
|
8389
|
+
const updateTicketServicedStatus = useCallback(async (ticketId, serviced) => {
|
|
8390
|
+
setError(null);
|
|
8391
|
+
try {
|
|
8392
|
+
await TicketHistoryService.updateTicketServicedStatus(ticketId, serviced);
|
|
8393
|
+
await refreshTickets();
|
|
8394
|
+
} catch (err) {
|
|
8395
|
+
setError(err instanceof Error ? err.message : "Failed to update ticket serviced status");
|
|
8396
|
+
throw err;
|
|
8397
|
+
}
|
|
8398
|
+
}, [refreshTickets]);
|
|
8438
8399
|
useEffect(() => {
|
|
8439
8400
|
if (companyId) {
|
|
8440
8401
|
refreshTickets();
|
|
@@ -8446,7 +8407,8 @@ function useTicketHistory(companyId) {
|
|
|
8446
8407
|
error,
|
|
8447
8408
|
createTicket,
|
|
8448
8409
|
refreshTickets,
|
|
8449
|
-
updateTicketStatus
|
|
8410
|
+
updateTicketStatus,
|
|
8411
|
+
updateTicketServicedStatus
|
|
8450
8412
|
};
|
|
8451
8413
|
}
|
|
8452
8414
|
|
|
@@ -11598,7 +11560,8 @@ var usePrefetchClipCounts = ({
|
|
|
11598
11560
|
date,
|
|
11599
11561
|
shift,
|
|
11600
11562
|
enabled = true,
|
|
11601
|
-
buildIndex =
|
|
11563
|
+
buildIndex = false,
|
|
11564
|
+
// Default to false for cost efficiency
|
|
11602
11565
|
subscriberId
|
|
11603
11566
|
}) => {
|
|
11604
11567
|
const dashboardConfig = useDashboardConfig();
|
|
@@ -11686,12 +11649,12 @@ var usePrefetchClipCounts = ({
|
|
|
11686
11649
|
}
|
|
11687
11650
|
},
|
|
11688
11651
|
onRenderReady: (key, newData) => {
|
|
11689
|
-
console.log(`[usePrefetchClipCounts] Render ready with ${Object.values(newData.counts).reduce((sum, count) => sum + count, 0)} total clips`);
|
|
11652
|
+
console.log(`[usePrefetchClipCounts] Render ready with ${Object.values(newData.counts || {}).reduce((sum, count) => sum + count, 0)} total clips`);
|
|
11690
11653
|
setData(newData);
|
|
11691
11654
|
setError(null);
|
|
11692
11655
|
},
|
|
11693
11656
|
onFullyIndexed: (key, newData) => {
|
|
11694
|
-
console.log(`[usePrefetchClipCounts] Fully indexed with ${newData.videoIndex
|
|
11657
|
+
console.log(`[usePrefetchClipCounts] Fully indexed with ${newData.videoIndex?.allVideos?.length || 0} videos`);
|
|
11695
11658
|
setData(newData);
|
|
11696
11659
|
setError(null);
|
|
11697
11660
|
},
|
|
@@ -12097,6 +12060,140 @@ function useWorkspaceNavigation() {
|
|
|
12097
12060
|
getWorkspaceNavigationParams: getWorkspaceNavigationParams2
|
|
12098
12061
|
};
|
|
12099
12062
|
}
|
|
12063
|
+
function useWorkspaceHealth(options = {}) {
|
|
12064
|
+
const [workspaces, setWorkspaces] = useState([]);
|
|
12065
|
+
const [summary, setSummary] = useState(null);
|
|
12066
|
+
const [loading, setLoading] = useState(true);
|
|
12067
|
+
const [error, setError] = useState(null);
|
|
12068
|
+
const unsubscribeRef = useRef(null);
|
|
12069
|
+
const refreshIntervalRef = useRef(null);
|
|
12070
|
+
const {
|
|
12071
|
+
enableRealtime = true,
|
|
12072
|
+
refreshInterval = 3e4,
|
|
12073
|
+
// 30 seconds default
|
|
12074
|
+
...filterOptions
|
|
12075
|
+
} = options;
|
|
12076
|
+
const fetchData = useCallback(async () => {
|
|
12077
|
+
try {
|
|
12078
|
+
setError(null);
|
|
12079
|
+
workspaceHealthService.clearCache();
|
|
12080
|
+
const [workspacesData, summaryData] = await Promise.all([
|
|
12081
|
+
workspaceHealthService.getWorkspaceHealthStatus(filterOptions),
|
|
12082
|
+
workspaceHealthService.getHealthSummary(filterOptions.lineId, filterOptions.companyId)
|
|
12083
|
+
]);
|
|
12084
|
+
setWorkspaces(workspacesData);
|
|
12085
|
+
setSummary(summaryData);
|
|
12086
|
+
} catch (err) {
|
|
12087
|
+
console.error("Error fetching workspace health:", err);
|
|
12088
|
+
setError(err);
|
|
12089
|
+
} finally {
|
|
12090
|
+
setLoading(false);
|
|
12091
|
+
}
|
|
12092
|
+
}, [filterOptions.lineId, filterOptions.companyId, filterOptions.status, filterOptions.searchTerm, filterOptions.sortBy, filterOptions.sortOrder]);
|
|
12093
|
+
const handleRealtimeUpdate = useCallback(async (data) => {
|
|
12094
|
+
try {
|
|
12095
|
+
const [workspacesData, summaryData] = await Promise.all([
|
|
12096
|
+
workspaceHealthService.getWorkspaceHealthStatus(filterOptions),
|
|
12097
|
+
workspaceHealthService.getHealthSummary(filterOptions.lineId, filterOptions.companyId)
|
|
12098
|
+
]);
|
|
12099
|
+
setWorkspaces(workspacesData);
|
|
12100
|
+
setSummary(summaryData);
|
|
12101
|
+
} catch (err) {
|
|
12102
|
+
console.error("Error updating real-time health data:", err);
|
|
12103
|
+
}
|
|
12104
|
+
}, [filterOptions]);
|
|
12105
|
+
useEffect(() => {
|
|
12106
|
+
fetchData();
|
|
12107
|
+
if (refreshInterval > 0) {
|
|
12108
|
+
refreshIntervalRef.current = setInterval(fetchData, 1e4);
|
|
12109
|
+
}
|
|
12110
|
+
if (enableRealtime) {
|
|
12111
|
+
unsubscribeRef.current = workspaceHealthService.subscribeToHealthUpdates(
|
|
12112
|
+
handleRealtimeUpdate,
|
|
12113
|
+
{ lineId: filterOptions.lineId, companyId: filterOptions.companyId }
|
|
12114
|
+
);
|
|
12115
|
+
}
|
|
12116
|
+
return () => {
|
|
12117
|
+
if (refreshIntervalRef.current) {
|
|
12118
|
+
clearInterval(refreshIntervalRef.current);
|
|
12119
|
+
}
|
|
12120
|
+
if (unsubscribeRef.current) {
|
|
12121
|
+
unsubscribeRef.current();
|
|
12122
|
+
}
|
|
12123
|
+
};
|
|
12124
|
+
}, [fetchData, enableRealtime, refreshInterval, handleRealtimeUpdate, filterOptions.lineId, filterOptions.companyId]);
|
|
12125
|
+
return {
|
|
12126
|
+
workspaces,
|
|
12127
|
+
summary,
|
|
12128
|
+
loading,
|
|
12129
|
+
error,
|
|
12130
|
+
refetch: fetchData
|
|
12131
|
+
};
|
|
12132
|
+
}
|
|
12133
|
+
function useWorkspaceHealthById(workspaceId, options = {}) {
|
|
12134
|
+
const [workspace, setWorkspace] = useState(null);
|
|
12135
|
+
const [metrics2, setMetrics] = useState(null);
|
|
12136
|
+
const [loading, setLoading] = useState(true);
|
|
12137
|
+
const [error, setError] = useState(null);
|
|
12138
|
+
const unsubscribeRef = useRef(null);
|
|
12139
|
+
const refreshIntervalRef = useRef(null);
|
|
12140
|
+
const { enableRealtime = true, refreshInterval = 3e4 } = options;
|
|
12141
|
+
const fetchData = useCallback(async () => {
|
|
12142
|
+
if (!workspaceId) {
|
|
12143
|
+
setWorkspace(null);
|
|
12144
|
+
setMetrics(null);
|
|
12145
|
+
setLoading(false);
|
|
12146
|
+
return;
|
|
12147
|
+
}
|
|
12148
|
+
try {
|
|
12149
|
+
setLoading(true);
|
|
12150
|
+
setError(null);
|
|
12151
|
+
const [workspaceData, metricsData] = await Promise.all([
|
|
12152
|
+
workspaceHealthService.getWorkspaceHealthById(workspaceId),
|
|
12153
|
+
workspaceHealthService.getHealthMetrics(workspaceId)
|
|
12154
|
+
]);
|
|
12155
|
+
setWorkspace(workspaceData);
|
|
12156
|
+
setMetrics(metricsData);
|
|
12157
|
+
} catch (err) {
|
|
12158
|
+
console.error("Error fetching workspace health by ID:", err);
|
|
12159
|
+
setError(err);
|
|
12160
|
+
} finally {
|
|
12161
|
+
setLoading(false);
|
|
12162
|
+
}
|
|
12163
|
+
}, [workspaceId]);
|
|
12164
|
+
const handleRealtimeUpdate = useCallback((data) => {
|
|
12165
|
+
if (data.workspace_id === workspaceId) {
|
|
12166
|
+
const updatedWorkspace = workspaceHealthService["processHealthStatus"](data);
|
|
12167
|
+
setWorkspace(updatedWorkspace);
|
|
12168
|
+
}
|
|
12169
|
+
}, [workspaceId]);
|
|
12170
|
+
useEffect(() => {
|
|
12171
|
+
fetchData();
|
|
12172
|
+
if (refreshInterval > 0) {
|
|
12173
|
+
refreshIntervalRef.current = setInterval(fetchData, 1e4);
|
|
12174
|
+
}
|
|
12175
|
+
if (enableRealtime && workspaceId) {
|
|
12176
|
+
unsubscribeRef.current = workspaceHealthService.subscribeToHealthUpdates(
|
|
12177
|
+
handleRealtimeUpdate
|
|
12178
|
+
);
|
|
12179
|
+
}
|
|
12180
|
+
return () => {
|
|
12181
|
+
if (refreshIntervalRef.current) {
|
|
12182
|
+
clearInterval(refreshIntervalRef.current);
|
|
12183
|
+
}
|
|
12184
|
+
if (unsubscribeRef.current) {
|
|
12185
|
+
unsubscribeRef.current();
|
|
12186
|
+
}
|
|
12187
|
+
};
|
|
12188
|
+
}, [fetchData, enableRealtime, refreshInterval, handleRealtimeUpdate, workspaceId]);
|
|
12189
|
+
return {
|
|
12190
|
+
workspace,
|
|
12191
|
+
metrics: metrics2,
|
|
12192
|
+
loading,
|
|
12193
|
+
error,
|
|
12194
|
+
refetch: fetchData
|
|
12195
|
+
};
|
|
12196
|
+
}
|
|
12100
12197
|
function useDateFormatter() {
|
|
12101
12198
|
const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
|
|
12102
12199
|
const formatDate = useCallback(
|
|
@@ -23284,8 +23381,7 @@ var ISTTimer = memo(() => {
|
|
|
23284
23381
|
return /* @__PURE__ */ jsx(
|
|
23285
23382
|
TimeDisplay2,
|
|
23286
23383
|
{
|
|
23287
|
-
variant: "minimal"
|
|
23288
|
-
className: "text-sm font-medium text-gray-700"
|
|
23384
|
+
variant: "minimal"
|
|
23289
23385
|
}
|
|
23290
23386
|
);
|
|
23291
23387
|
});
|
|
@@ -23317,6 +23413,7 @@ var CardFooter2 = (props) => {
|
|
|
23317
23413
|
};
|
|
23318
23414
|
var TicketHistory = ({ companyId }) => {
|
|
23319
23415
|
const { tickets, loading, error } = useTicketHistory(companyId);
|
|
23416
|
+
const [expandedTickets, setExpandedTickets] = useState(/* @__PURE__ */ new Set());
|
|
23320
23417
|
const getCategoryIcon = (category) => {
|
|
23321
23418
|
switch (category) {
|
|
23322
23419
|
case "general":
|
|
@@ -23373,6 +23470,23 @@ var TicketHistory = ({ companyId }) => {
|
|
|
23373
23470
|
return "text-gray-600 bg-gray-50";
|
|
23374
23471
|
}
|
|
23375
23472
|
};
|
|
23473
|
+
const toggleTicketExpansion = (ticketId) => {
|
|
23474
|
+
setExpandedTickets((prev) => {
|
|
23475
|
+
const newSet = new Set(prev);
|
|
23476
|
+
if (newSet.has(ticketId)) {
|
|
23477
|
+
newSet.delete(ticketId);
|
|
23478
|
+
} else {
|
|
23479
|
+
newSet.add(ticketId);
|
|
23480
|
+
}
|
|
23481
|
+
return newSet;
|
|
23482
|
+
});
|
|
23483
|
+
};
|
|
23484
|
+
const isTicketExpanded = (ticketId) => {
|
|
23485
|
+
return expandedTickets.has(ticketId);
|
|
23486
|
+
};
|
|
23487
|
+
const shouldShowExpandButton = (description) => {
|
|
23488
|
+
return description.length > 100 || description.includes("\n");
|
|
23489
|
+
};
|
|
23376
23490
|
if (loading) {
|
|
23377
23491
|
return /* @__PURE__ */ jsxs(Card2, { className: "h-full", children: [
|
|
23378
23492
|
/* @__PURE__ */ jsx(CardHeader2, { children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg font-semibold text-gray-800", children: "History of Tickets" }) }),
|
|
@@ -23413,12 +23527,14 @@ var TicketHistory = ({ companyId }) => {
|
|
|
23413
23527
|
tickets.map((ticket, index) => {
|
|
23414
23528
|
const CategoryIcon = getCategoryIcon(ticket.category);
|
|
23415
23529
|
const StatusIcon = getStatusIcon(ticket.status);
|
|
23530
|
+
const isExpanded = isTicketExpanded(ticket.id);
|
|
23531
|
+
const showExpandButton = shouldShowExpandButton(ticket.description);
|
|
23416
23532
|
return /* @__PURE__ */ jsxs(
|
|
23417
23533
|
motion.div,
|
|
23418
23534
|
{
|
|
23419
23535
|
initial: { opacity: 0, y: 10 },
|
|
23420
23536
|
animate: { opacity: 1, y: 0 },
|
|
23421
|
-
className: `border-b border-gray-100 p-4 hover:bg-gray-50 transition-colors
|
|
23537
|
+
className: `border-b border-gray-100 p-4 hover:bg-gray-50 transition-colors ${index === 0 ? "border-t-0" : ""}`,
|
|
23422
23538
|
children: [
|
|
23423
23539
|
/* @__PURE__ */ jsx("div", { className: "flex items-start justify-between mb-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [
|
|
23424
23540
|
/* @__PURE__ */ jsx("div", { className: "p-1 bg-gray-100 rounded", children: /* @__PURE__ */ jsx(CategoryIcon, { className: "h-3 w-3 text-gray-600" }) }),
|
|
@@ -23429,9 +23545,42 @@ var TicketHistory = ({ companyId }) => {
|
|
|
23429
23545
|
/* @__PURE__ */ jsx(StatusIcon, { className: "h-2.5 w-2.5 mr-1" }),
|
|
23430
23546
|
ticket.status === "submitted" ? "New" : ticket.status.replace("_", " ")
|
|
23431
23547
|
] }),
|
|
23432
|
-
/* @__PURE__ */ jsx("span", { className: `inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${getPriorityColor(ticket.priority)}`, children: ticket.priority === "normal" ? "Standard" : ticket.priority })
|
|
23548
|
+
/* @__PURE__ */ jsx("span", { className: `inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${getPriorityColor(ticket.priority)}`, children: ticket.priority === "normal" ? "Standard" : ticket.priority }),
|
|
23549
|
+
ticket.serviced && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center px-2 py-1 rounded-full text-xs font-medium text-green-700 bg-green-100 border border-green-200", children: [
|
|
23550
|
+
/* @__PURE__ */ jsx(CheckIcon, { className: "h-2.5 w-2.5 mr-1" }),
|
|
23551
|
+
"Serviced"
|
|
23552
|
+
] })
|
|
23553
|
+
] }),
|
|
23554
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3", children: [
|
|
23555
|
+
/* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
|
|
23556
|
+
motion.div,
|
|
23557
|
+
{
|
|
23558
|
+
initial: { opacity: 0 },
|
|
23559
|
+
animate: { opacity: 1 },
|
|
23560
|
+
exit: { opacity: 0 },
|
|
23561
|
+
transition: { duration: 0.2 },
|
|
23562
|
+
children: /* @__PURE__ */ jsx("p", { className: `text-xs text-gray-600 leading-relaxed whitespace-pre-wrap ${!isExpanded && showExpandButton ? "line-clamp-2" : ""}`, children: ticket.description })
|
|
23563
|
+
},
|
|
23564
|
+
isExpanded ? "expanded" : "collapsed"
|
|
23565
|
+
) }),
|
|
23566
|
+
showExpandButton && /* @__PURE__ */ jsx(
|
|
23567
|
+
"button",
|
|
23568
|
+
{
|
|
23569
|
+
onClick: (e) => {
|
|
23570
|
+
e.stopPropagation();
|
|
23571
|
+
toggleTicketExpansion(ticket.id);
|
|
23572
|
+
},
|
|
23573
|
+
className: "mt-2 flex items-center gap-1 text-xs text-blue-600 hover:text-blue-700 font-medium transition-colors",
|
|
23574
|
+
children: isExpanded ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23575
|
+
/* @__PURE__ */ jsx(ChevronUpIcon, { className: "h-3 w-3" }),
|
|
23576
|
+
"Show less"
|
|
23577
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23578
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "h-3 w-3" }),
|
|
23579
|
+
"Show more"
|
|
23580
|
+
] })
|
|
23581
|
+
}
|
|
23582
|
+
)
|
|
23433
23583
|
] }),
|
|
23434
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-600 mb-3 line-clamp-2 leading-relaxed", children: ticket.description }),
|
|
23435
23584
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
|
|
23436
23585
|
/* @__PURE__ */ jsx("span", { className: "text-gray-500 capitalize font-medium", children: ticket.category }),
|
|
23437
23586
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-gray-500", children: [
|
|
@@ -23453,6 +23602,165 @@ var TicketHistory = ({ companyId }) => {
|
|
|
23453
23602
|
] });
|
|
23454
23603
|
};
|
|
23455
23604
|
var TicketHistory_default = TicketHistory;
|
|
23605
|
+
var HealthStatusIndicator = ({
|
|
23606
|
+
status,
|
|
23607
|
+
lastUpdated,
|
|
23608
|
+
showLabel = false,
|
|
23609
|
+
showTime = true,
|
|
23610
|
+
size = "md",
|
|
23611
|
+
className = "",
|
|
23612
|
+
inline = true,
|
|
23613
|
+
pulse = true
|
|
23614
|
+
}) => {
|
|
23615
|
+
const getStatusConfig = () => {
|
|
23616
|
+
switch (status) {
|
|
23617
|
+
case "healthy":
|
|
23618
|
+
return {
|
|
23619
|
+
color: "text-green-500",
|
|
23620
|
+
bgColor: "bg-green-500",
|
|
23621
|
+
borderColor: "border-green-500",
|
|
23622
|
+
label: "Healthy",
|
|
23623
|
+
icon: CheckCircle,
|
|
23624
|
+
shouldPulse: pulse
|
|
23625
|
+
};
|
|
23626
|
+
case "unhealthy":
|
|
23627
|
+
return {
|
|
23628
|
+
color: "text-red-500",
|
|
23629
|
+
bgColor: "bg-red-500",
|
|
23630
|
+
borderColor: "border-red-500",
|
|
23631
|
+
label: "Unhealthy",
|
|
23632
|
+
icon: XCircle,
|
|
23633
|
+
shouldPulse: false
|
|
23634
|
+
};
|
|
23635
|
+
case "warning":
|
|
23636
|
+
return {
|
|
23637
|
+
color: "text-yellow-500",
|
|
23638
|
+
bgColor: "bg-yellow-500",
|
|
23639
|
+
borderColor: "border-yellow-500",
|
|
23640
|
+
label: "Warning",
|
|
23641
|
+
icon: AlertTriangle,
|
|
23642
|
+
shouldPulse: true
|
|
23643
|
+
};
|
|
23644
|
+
case "unknown":
|
|
23645
|
+
default:
|
|
23646
|
+
return {
|
|
23647
|
+
color: "text-gray-400",
|
|
23648
|
+
bgColor: "bg-gray-400",
|
|
23649
|
+
borderColor: "border-gray-400",
|
|
23650
|
+
label: "Unknown",
|
|
23651
|
+
icon: Activity,
|
|
23652
|
+
shouldPulse: false
|
|
23653
|
+
};
|
|
23654
|
+
}
|
|
23655
|
+
};
|
|
23656
|
+
const config = getStatusConfig();
|
|
23657
|
+
config.icon;
|
|
23658
|
+
const sizeClasses = {
|
|
23659
|
+
sm: {
|
|
23660
|
+
dot: "h-2 w-2",
|
|
23661
|
+
icon: "h-3 w-3",
|
|
23662
|
+
text: "text-xs",
|
|
23663
|
+
spacing: "gap-1"
|
|
23664
|
+
},
|
|
23665
|
+
md: {
|
|
23666
|
+
dot: "h-3 w-3",
|
|
23667
|
+
icon: "h-4 w-4",
|
|
23668
|
+
text: "text-sm",
|
|
23669
|
+
spacing: "gap-1.5"
|
|
23670
|
+
},
|
|
23671
|
+
lg: {
|
|
23672
|
+
dot: "h-4 w-4",
|
|
23673
|
+
icon: "h-5 w-5",
|
|
23674
|
+
text: "text-base",
|
|
23675
|
+
spacing: "gap-2"
|
|
23676
|
+
}
|
|
23677
|
+
};
|
|
23678
|
+
const currentSize = sizeClasses[size];
|
|
23679
|
+
const containerClasses = clsx(
|
|
23680
|
+
"flex items-center",
|
|
23681
|
+
currentSize.spacing,
|
|
23682
|
+
inline ? "inline-flex" : "flex",
|
|
23683
|
+
className
|
|
23684
|
+
);
|
|
23685
|
+
const dotClasses = clsx(
|
|
23686
|
+
"rounded-full",
|
|
23687
|
+
currentSize.dot,
|
|
23688
|
+
config.bgColor,
|
|
23689
|
+
config.shouldPulse && "animate-pulse"
|
|
23690
|
+
);
|
|
23691
|
+
return /* @__PURE__ */ jsxs("div", { className: containerClasses, children: [
|
|
23692
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
|
|
23693
|
+
/* @__PURE__ */ jsx("div", { className: dotClasses }),
|
|
23694
|
+
config.shouldPulse && status === "healthy" && /* @__PURE__ */ jsx(
|
|
23695
|
+
"div",
|
|
23696
|
+
{
|
|
23697
|
+
className: clsx(
|
|
23698
|
+
"absolute rounded-full opacity-25",
|
|
23699
|
+
currentSize.dot,
|
|
23700
|
+
config.bgColor,
|
|
23701
|
+
"animate-ping"
|
|
23702
|
+
)
|
|
23703
|
+
}
|
|
23704
|
+
)
|
|
23705
|
+
] }),
|
|
23706
|
+
showLabel && /* @__PURE__ */ jsx("span", { className: clsx(currentSize.text, "font-medium", config.color), children: config.label }),
|
|
23707
|
+
showTime && lastUpdated && /* @__PURE__ */ jsx("span", { className: clsx(currentSize.text, "text-gray-500 dark:text-gray-400"), children: lastUpdated })
|
|
23708
|
+
] });
|
|
23709
|
+
};
|
|
23710
|
+
var DetailedHealthStatus = ({
|
|
23711
|
+
workspaceName,
|
|
23712
|
+
lineName,
|
|
23713
|
+
consecutiveMisses,
|
|
23714
|
+
showDetails = true,
|
|
23715
|
+
...indicatorProps
|
|
23716
|
+
}) => {
|
|
23717
|
+
const getStatusConfig = () => {
|
|
23718
|
+
switch (indicatorProps.status) {
|
|
23719
|
+
case "healthy":
|
|
23720
|
+
return {
|
|
23721
|
+
bgClass: "bg-green-50 dark:bg-green-900/20",
|
|
23722
|
+
borderClass: "border-green-200 dark:border-green-800"
|
|
23723
|
+
};
|
|
23724
|
+
case "unhealthy":
|
|
23725
|
+
return {
|
|
23726
|
+
bgClass: "bg-red-50 dark:bg-red-900/20",
|
|
23727
|
+
borderClass: "border-red-200 dark:border-red-800"
|
|
23728
|
+
};
|
|
23729
|
+
case "warning":
|
|
23730
|
+
return {
|
|
23731
|
+
bgClass: "bg-yellow-50 dark:bg-yellow-900/20",
|
|
23732
|
+
borderClass: "border-yellow-200 dark:border-yellow-800"
|
|
23733
|
+
};
|
|
23734
|
+
default:
|
|
23735
|
+
return {
|
|
23736
|
+
bgClass: "bg-gray-50 dark:bg-gray-900/20",
|
|
23737
|
+
borderClass: "border-gray-200 dark:border-gray-800"
|
|
23738
|
+
};
|
|
23739
|
+
}
|
|
23740
|
+
};
|
|
23741
|
+
const config = getStatusConfig();
|
|
23742
|
+
return /* @__PURE__ */ jsx(
|
|
23743
|
+
"div",
|
|
23744
|
+
{
|
|
23745
|
+
className: clsx(
|
|
23746
|
+
"rounded-lg border p-3",
|
|
23747
|
+
config.bgClass,
|
|
23748
|
+
config.borderClass
|
|
23749
|
+
),
|
|
23750
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
|
|
23751
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
23752
|
+
showDetails && workspaceName && /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: workspaceName }),
|
|
23753
|
+
showDetails && lineName && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5", children: lineName }),
|
|
23754
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(HealthStatusIndicator, { ...indicatorProps, showLabel: true }) })
|
|
23755
|
+
] }),
|
|
23756
|
+
showDetails && consecutiveMisses !== void 0 && consecutiveMisses > 0 && /* @__PURE__ */ jsxs("div", { className: "ml-3 text-right", children: [
|
|
23757
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: "Missed" }),
|
|
23758
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-bold text-gray-900 dark:text-gray-100", children: consecutiveMisses })
|
|
23759
|
+
] })
|
|
23760
|
+
] })
|
|
23761
|
+
}
|
|
23762
|
+
);
|
|
23763
|
+
};
|
|
23456
23764
|
var LinePdfExportButton = ({
|
|
23457
23765
|
targetElement,
|
|
23458
23766
|
fileName = "line-export",
|
|
@@ -24575,6 +24883,8 @@ var WorkspaceCard = ({
|
|
|
24575
24883
|
cycleTime,
|
|
24576
24884
|
operators,
|
|
24577
24885
|
status = "normal",
|
|
24886
|
+
healthStatus,
|
|
24887
|
+
healthLastUpdated,
|
|
24578
24888
|
onCardClick,
|
|
24579
24889
|
headerActions,
|
|
24580
24890
|
footerContent,
|
|
@@ -24667,6 +24977,19 @@ var WorkspaceCard = ({
|
|
|
24667
24977
|
] })
|
|
24668
24978
|
] })
|
|
24669
24979
|
] }),
|
|
24980
|
+
healthStatus && /* @__PURE__ */ jsx("div", { className: "pt-2 mt-auto border-t dark:border-gray-700", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
24981
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "System Health" }),
|
|
24982
|
+
/* @__PURE__ */ jsx(
|
|
24983
|
+
HealthStatusIndicator,
|
|
24984
|
+
{
|
|
24985
|
+
status: healthStatus,
|
|
24986
|
+
lastUpdated: healthLastUpdated,
|
|
24987
|
+
showTime: false,
|
|
24988
|
+
size: "sm",
|
|
24989
|
+
pulse: healthStatus === "healthy"
|
|
24990
|
+
}
|
|
24991
|
+
)
|
|
24992
|
+
] }) }),
|
|
24670
24993
|
chartData && chartData.data && chartData.data.length > 0 && /* @__PURE__ */ jsxs(
|
|
24671
24994
|
"div",
|
|
24672
24995
|
{
|
|
@@ -26204,6 +26527,315 @@ var VideoPlayer = React19__default.forwardRef(({
|
|
|
26204
26527
|
] });
|
|
26205
26528
|
});
|
|
26206
26529
|
VideoPlayer.displayName = "VideoPlayer";
|
|
26530
|
+
var BackButton = ({
|
|
26531
|
+
onClick,
|
|
26532
|
+
text = "Back",
|
|
26533
|
+
className,
|
|
26534
|
+
size = "default",
|
|
26535
|
+
disabled = false,
|
|
26536
|
+
"aria-label": ariaLabel
|
|
26537
|
+
}) => {
|
|
26538
|
+
const sizeClasses = {
|
|
26539
|
+
sm: {
|
|
26540
|
+
container: "gap-1 px-2 py-1.5",
|
|
26541
|
+
icon: "w-3.5 h-3.5",
|
|
26542
|
+
text: "text-xs"
|
|
26543
|
+
},
|
|
26544
|
+
default: {
|
|
26545
|
+
container: "gap-2 px-3 py-2",
|
|
26546
|
+
icon: "w-4 h-4",
|
|
26547
|
+
text: "text-sm"
|
|
26548
|
+
},
|
|
26549
|
+
lg: {
|
|
26550
|
+
container: "gap-2 px-4 py-2.5",
|
|
26551
|
+
icon: "w-5 h-5",
|
|
26552
|
+
text: "text-base"
|
|
26553
|
+
}
|
|
26554
|
+
};
|
|
26555
|
+
const currentSize = sizeClasses[size];
|
|
26556
|
+
return /* @__PURE__ */ jsxs(
|
|
26557
|
+
"button",
|
|
26558
|
+
{
|
|
26559
|
+
onClick,
|
|
26560
|
+
disabled,
|
|
26561
|
+
"aria-label": ariaLabel || `${text} button`,
|
|
26562
|
+
className: cn(
|
|
26563
|
+
// Base styles
|
|
26564
|
+
"flex items-center font-medium rounded-lg transition-all duration-200",
|
|
26565
|
+
// Size-specific styles
|
|
26566
|
+
currentSize.container,
|
|
26567
|
+
// Color and interaction styles
|
|
26568
|
+
disabled ? "text-gray-400 cursor-not-allowed" : "text-gray-600 hover:text-gray-900 hover:bg-gray-50 active:bg-gray-100",
|
|
26569
|
+
// Focus styles for accessibility
|
|
26570
|
+
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
|
|
26571
|
+
className
|
|
26572
|
+
),
|
|
26573
|
+
children: [
|
|
26574
|
+
/* @__PURE__ */ jsx(
|
|
26575
|
+
ArrowLeft,
|
|
26576
|
+
{
|
|
26577
|
+
className: cn(
|
|
26578
|
+
"flex-shrink-0",
|
|
26579
|
+
currentSize.icon,
|
|
26580
|
+
disabled && "opacity-50"
|
|
26581
|
+
)
|
|
26582
|
+
}
|
|
26583
|
+
),
|
|
26584
|
+
/* @__PURE__ */ jsx("span", { className: cn(
|
|
26585
|
+
"font-medium select-none",
|
|
26586
|
+
currentSize.text,
|
|
26587
|
+
disabled && "opacity-50"
|
|
26588
|
+
), children: text })
|
|
26589
|
+
]
|
|
26590
|
+
}
|
|
26591
|
+
);
|
|
26592
|
+
};
|
|
26593
|
+
var BackButtonMinimal = ({
|
|
26594
|
+
onClick,
|
|
26595
|
+
text = "Back",
|
|
26596
|
+
className,
|
|
26597
|
+
size = "default",
|
|
26598
|
+
disabled = false,
|
|
26599
|
+
"aria-label": ariaLabel
|
|
26600
|
+
}) => {
|
|
26601
|
+
const sizeClasses = {
|
|
26602
|
+
sm: {
|
|
26603
|
+
icon: "w-3.5 h-3.5",
|
|
26604
|
+
text: "text-xs ml-1"
|
|
26605
|
+
},
|
|
26606
|
+
default: {
|
|
26607
|
+
icon: "w-4 h-4",
|
|
26608
|
+
text: "text-sm ml-2"
|
|
26609
|
+
},
|
|
26610
|
+
lg: {
|
|
26611
|
+
icon: "w-5 h-5",
|
|
26612
|
+
text: "text-base ml-2"
|
|
26613
|
+
}
|
|
26614
|
+
};
|
|
26615
|
+
const currentSize = sizeClasses[size];
|
|
26616
|
+
return /* @__PURE__ */ jsxs(
|
|
26617
|
+
"button",
|
|
26618
|
+
{
|
|
26619
|
+
onClick,
|
|
26620
|
+
disabled,
|
|
26621
|
+
"aria-label": ariaLabel || `${text} button`,
|
|
26622
|
+
className: cn(
|
|
26623
|
+
// Base styles - minimal padding for tight spaces
|
|
26624
|
+
"flex items-center transition-colors duration-200",
|
|
26625
|
+
// Color and interaction styles
|
|
26626
|
+
disabled ? "text-gray-400 cursor-not-allowed" : "text-gray-600 hover:text-gray-900",
|
|
26627
|
+
// Focus styles for accessibility
|
|
26628
|
+
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded",
|
|
26629
|
+
className
|
|
26630
|
+
),
|
|
26631
|
+
children: [
|
|
26632
|
+
/* @__PURE__ */ jsx(
|
|
26633
|
+
ArrowLeft,
|
|
26634
|
+
{
|
|
26635
|
+
className: cn(
|
|
26636
|
+
"flex-shrink-0",
|
|
26637
|
+
currentSize.icon,
|
|
26638
|
+
disabled && "opacity-50"
|
|
26639
|
+
)
|
|
26640
|
+
}
|
|
26641
|
+
),
|
|
26642
|
+
/* @__PURE__ */ jsx("span", { className: cn(
|
|
26643
|
+
"font-medium select-none",
|
|
26644
|
+
currentSize.text,
|
|
26645
|
+
disabled && "opacity-50"
|
|
26646
|
+
), children: text })
|
|
26647
|
+
]
|
|
26648
|
+
}
|
|
26649
|
+
);
|
|
26650
|
+
};
|
|
26651
|
+
var InlineEditableText = ({
|
|
26652
|
+
value,
|
|
26653
|
+
onSave,
|
|
26654
|
+
placeholder = "Click to edit",
|
|
26655
|
+
className = "",
|
|
26656
|
+
editIconClassName = "",
|
|
26657
|
+
inputClassName = "",
|
|
26658
|
+
debounceDelay = 750,
|
|
26659
|
+
// 750ms for quick auto-save
|
|
26660
|
+
disabled = false
|
|
26661
|
+
}) => {
|
|
26662
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
26663
|
+
const [editValue, setEditValue] = useState(value);
|
|
26664
|
+
const [saveStatus, setSaveStatus] = useState("idle");
|
|
26665
|
+
const [lastSavedValue, setLastSavedValue] = useState(value);
|
|
26666
|
+
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
|
26667
|
+
const inputRef = useRef(null);
|
|
26668
|
+
const containerRef = useRef(null);
|
|
26669
|
+
const debounceTimeout = useRef(void 0);
|
|
26670
|
+
const saveStatusTimeout = useRef(void 0);
|
|
26671
|
+
useEffect(() => {
|
|
26672
|
+
if (!isEditing) {
|
|
26673
|
+
setEditValue(value);
|
|
26674
|
+
setLastSavedValue(value);
|
|
26675
|
+
}
|
|
26676
|
+
}, [value, isEditing]);
|
|
26677
|
+
useEffect(() => {
|
|
26678
|
+
if (isEditing && inputRef.current) {
|
|
26679
|
+
requestAnimationFrame(() => {
|
|
26680
|
+
inputRef.current?.focus();
|
|
26681
|
+
inputRef.current?.select();
|
|
26682
|
+
});
|
|
26683
|
+
}
|
|
26684
|
+
}, [isEditing]);
|
|
26685
|
+
useEffect(() => {
|
|
26686
|
+
return () => {
|
|
26687
|
+
if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
|
|
26688
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26689
|
+
};
|
|
26690
|
+
}, []);
|
|
26691
|
+
const performSave = useCallback(async (valueToSave, shouldClose = false) => {
|
|
26692
|
+
const trimmedValue = valueToSave.trim();
|
|
26693
|
+
if (trimmedValue === lastSavedValue.trim()) {
|
|
26694
|
+
setHasUnsavedChanges(false);
|
|
26695
|
+
if (shouldClose) {
|
|
26696
|
+
setIsEditing(false);
|
|
26697
|
+
setSaveStatus("idle");
|
|
26698
|
+
}
|
|
26699
|
+
return;
|
|
26700
|
+
}
|
|
26701
|
+
setSaveStatus("saving");
|
|
26702
|
+
setHasUnsavedChanges(false);
|
|
26703
|
+
try {
|
|
26704
|
+
await onSave(trimmedValue);
|
|
26705
|
+
setLastSavedValue(trimmedValue);
|
|
26706
|
+
setSaveStatus("saved");
|
|
26707
|
+
if (!shouldClose && inputRef.current) {
|
|
26708
|
+
requestAnimationFrame(() => {
|
|
26709
|
+
inputRef.current?.focus();
|
|
26710
|
+
});
|
|
26711
|
+
}
|
|
26712
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26713
|
+
saveStatusTimeout.current = setTimeout(() => {
|
|
26714
|
+
setSaveStatus("idle");
|
|
26715
|
+
}, 2e3);
|
|
26716
|
+
if (shouldClose) {
|
|
26717
|
+
setIsEditing(false);
|
|
26718
|
+
}
|
|
26719
|
+
} catch (error) {
|
|
26720
|
+
console.error("Failed to save:", error);
|
|
26721
|
+
setSaveStatus("error");
|
|
26722
|
+
setHasUnsavedChanges(true);
|
|
26723
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26724
|
+
saveStatusTimeout.current = setTimeout(() => {
|
|
26725
|
+
setSaveStatus("idle");
|
|
26726
|
+
}, 3e3);
|
|
26727
|
+
if (!shouldClose && inputRef.current) {
|
|
26728
|
+
requestAnimationFrame(() => {
|
|
26729
|
+
inputRef.current?.focus();
|
|
26730
|
+
});
|
|
26731
|
+
}
|
|
26732
|
+
}
|
|
26733
|
+
}, [lastSavedValue, onSave]);
|
|
26734
|
+
useEffect(() => {
|
|
26735
|
+
const handleClickOutside = (event) => {
|
|
26736
|
+
if (isEditing && containerRef.current && !containerRef.current.contains(event.target)) {
|
|
26737
|
+
if (debounceTimeout.current) {
|
|
26738
|
+
clearTimeout(debounceTimeout.current);
|
|
26739
|
+
}
|
|
26740
|
+
performSave(editValue, true);
|
|
26741
|
+
}
|
|
26742
|
+
};
|
|
26743
|
+
if (isEditing) {
|
|
26744
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
26745
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
26746
|
+
}
|
|
26747
|
+
}, [isEditing, editValue, performSave]);
|
|
26748
|
+
const handleInputChange = (e) => {
|
|
26749
|
+
const newValue = e.target.value;
|
|
26750
|
+
setEditValue(newValue);
|
|
26751
|
+
if (newValue.trim() !== lastSavedValue.trim()) {
|
|
26752
|
+
setHasUnsavedChanges(true);
|
|
26753
|
+
setSaveStatus("idle");
|
|
26754
|
+
} else {
|
|
26755
|
+
setHasUnsavedChanges(false);
|
|
26756
|
+
}
|
|
26757
|
+
if (debounceTimeout.current) {
|
|
26758
|
+
clearTimeout(debounceTimeout.current);
|
|
26759
|
+
}
|
|
26760
|
+
if (newValue.trim() !== lastSavedValue.trim()) {
|
|
26761
|
+
debounceTimeout.current = setTimeout(() => {
|
|
26762
|
+
performSave(newValue, false);
|
|
26763
|
+
}, debounceDelay);
|
|
26764
|
+
}
|
|
26765
|
+
};
|
|
26766
|
+
const handleKeyDown = (e) => {
|
|
26767
|
+
if (e.key === "Enter") {
|
|
26768
|
+
e.preventDefault();
|
|
26769
|
+
if (debounceTimeout.current) {
|
|
26770
|
+
clearTimeout(debounceTimeout.current);
|
|
26771
|
+
}
|
|
26772
|
+
performSave(editValue, true);
|
|
26773
|
+
} else if (e.key === "Escape") {
|
|
26774
|
+
e.preventDefault();
|
|
26775
|
+
if (debounceTimeout.current) {
|
|
26776
|
+
clearTimeout(debounceTimeout.current);
|
|
26777
|
+
}
|
|
26778
|
+
setEditValue(lastSavedValue);
|
|
26779
|
+
setHasUnsavedChanges(false);
|
|
26780
|
+
setSaveStatus("idle");
|
|
26781
|
+
setIsEditing(false);
|
|
26782
|
+
}
|
|
26783
|
+
};
|
|
26784
|
+
const handleClick = () => {
|
|
26785
|
+
if (!disabled && !isEditing) {
|
|
26786
|
+
setIsEditing(true);
|
|
26787
|
+
setSaveStatus("idle");
|
|
26788
|
+
}
|
|
26789
|
+
};
|
|
26790
|
+
if (isEditing) {
|
|
26791
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: "inline-flex items-center gap-1.5", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
26792
|
+
/* @__PURE__ */ jsx(
|
|
26793
|
+
"input",
|
|
26794
|
+
{
|
|
26795
|
+
ref: inputRef,
|
|
26796
|
+
type: "text",
|
|
26797
|
+
value: editValue,
|
|
26798
|
+
onChange: handleInputChange,
|
|
26799
|
+
onKeyDown: handleKeyDown,
|
|
26800
|
+
className: `px-2 py-1 pr-7 text-sm border rounded-md transition-colors duration-200
|
|
26801
|
+
${saveStatus === "error" ? "border-red-400 focus:ring-red-500 focus:border-red-500" : hasUnsavedChanges ? "border-yellow-400 focus:ring-yellow-500 focus:border-yellow-500" : saveStatus === "saved" ? "border-green-400 focus:ring-green-500 focus:border-green-500" : "border-blue-400 focus:ring-blue-500 focus:border-blue-500"}
|
|
26802
|
+
focus:outline-none focus:ring-2 bg-white
|
|
26803
|
+
${inputClassName}`,
|
|
26804
|
+
placeholder,
|
|
26805
|
+
autoComplete: "off"
|
|
26806
|
+
}
|
|
26807
|
+
),
|
|
26808
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute right-1.5 top-1/2 -translate-y-1/2", children: [
|
|
26809
|
+
saveStatus === "saving" && /* @__PURE__ */ jsx(Loader2, { className: "w-3.5 h-3.5 text-blue-500 animate-spin" }),
|
|
26810
|
+
saveStatus === "saved" && /* @__PURE__ */ jsx(Check, { className: "w-3.5 h-3.5 text-green-500" }),
|
|
26811
|
+
saveStatus === "error" && /* @__PURE__ */ jsx(AlertCircle, { className: "w-3.5 h-3.5 text-red-500" }),
|
|
26812
|
+
saveStatus === "idle" && hasUnsavedChanges && /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-yellow-400 rounded-full" })
|
|
26813
|
+
] })
|
|
26814
|
+
] }) });
|
|
26815
|
+
}
|
|
26816
|
+
return /* @__PURE__ */ jsxs(
|
|
26817
|
+
"div",
|
|
26818
|
+
{
|
|
26819
|
+
onClick: handleClick,
|
|
26820
|
+
className: `inline-flex items-center gap-1.5 cursor-pointer px-2 py-1 rounded-md
|
|
26821
|
+
transition-all duration-200 hover:bg-gray-50 group
|
|
26822
|
+
${disabled ? "cursor-not-allowed opacity-50" : ""}
|
|
26823
|
+
${className}`,
|
|
26824
|
+
children: [
|
|
26825
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-900", children: editValue || placeholder }),
|
|
26826
|
+
/* @__PURE__ */ jsx(
|
|
26827
|
+
Edit2,
|
|
26828
|
+
{
|
|
26829
|
+
className: `w-3.5 h-3.5 text-gray-400 transition-opacity duration-200
|
|
26830
|
+
opacity-0 group-hover:opacity-100
|
|
26831
|
+
${disabled ? "hidden" : ""}
|
|
26832
|
+
${editIconClassName}`
|
|
26833
|
+
}
|
|
26834
|
+
)
|
|
26835
|
+
]
|
|
26836
|
+
}
|
|
26837
|
+
);
|
|
26838
|
+
};
|
|
26207
26839
|
var BottlenecksContent = ({
|
|
26208
26840
|
workspaceId,
|
|
26209
26841
|
workspaceName,
|
|
@@ -26223,7 +26855,6 @@ var BottlenecksContent = ({
|
|
|
26223
26855
|
const videoRef = useRef(null);
|
|
26224
26856
|
const timestampFilterRef = useRef(null);
|
|
26225
26857
|
const initialFilter = sopCategories && sopCategories.length > 0 ? sopCategories[0].id : "low_value";
|
|
26226
|
-
const videoIndexRef = useRef(null);
|
|
26227
26858
|
const currentIndexRef = useRef(0);
|
|
26228
26859
|
const activeFilterRef = useRef(initialFilter);
|
|
26229
26860
|
const isMountedRef = useRef(true);
|
|
@@ -26241,15 +26872,6 @@ var BottlenecksContent = ({
|
|
|
26241
26872
|
const [isNavigating, setIsNavigating] = useState(false);
|
|
26242
26873
|
const [error, setError] = useState(null);
|
|
26243
26874
|
const [clipCounts, setClipCounts] = useState({});
|
|
26244
|
-
const [videoIndex, setVideoIndex] = useState(null);
|
|
26245
|
-
const updateVideoIndex = useCallback((newIndex) => {
|
|
26246
|
-
console.log(`[BottlenecksContent] Updating video index - ID: ${newIndex?._debugId || "NO_ID"}, total videos: ${newIndex?.allVideos.length || 0}, categories: ${newIndex?.byCategory.size || 0}`);
|
|
26247
|
-
if (newIndex) {
|
|
26248
|
-
console.log(`[BottlenecksContent] VideoIndex categories: [${Array.from(newIndex.byCategory.keys()).join(", ")}]`);
|
|
26249
|
-
}
|
|
26250
|
-
setVideoIndex(newIndex);
|
|
26251
|
-
videoIndexRef.current = newIndex;
|
|
26252
|
-
}, []);
|
|
26253
26875
|
const updateActiveFilter = useCallback((newFilter) => {
|
|
26254
26876
|
console.log(`[BottlenecksContent] Updating active filter: ${activeFilterRef.current} -> ${newFilter}`);
|
|
26255
26877
|
setActiveFilter(newFilter);
|
|
@@ -26310,7 +26932,8 @@ var BottlenecksContent = ({
|
|
|
26310
26932
|
date: date || getOperationalDate(),
|
|
26311
26933
|
shift: effectiveShift,
|
|
26312
26934
|
enabled: !!workspaceId && !!s3ClipsService,
|
|
26313
|
-
buildIndex:
|
|
26935
|
+
buildIndex: false
|
|
26936
|
+
// Disabled to reduce S3 costs - use pagination instead
|
|
26314
26937
|
});
|
|
26315
26938
|
const fetchClipCounts = useCallback(async () => {
|
|
26316
26939
|
if (!workspaceId || !s3ClipsService || !dashboardConfig?.s3Config || !isMountedRef.current) return;
|
|
@@ -26329,7 +26952,6 @@ var BottlenecksContent = ({
|
|
|
26329
26952
|
if (cachedResult) {
|
|
26330
26953
|
console.log(`[BottlenecksContent] Using cached clip counts`);
|
|
26331
26954
|
updateClipCounts(cachedResult.counts);
|
|
26332
|
-
updateVideoIndex(cachedResult.videoIndex);
|
|
26333
26955
|
setIsLoading(false);
|
|
26334
26956
|
setHasInitialLoad(true);
|
|
26335
26957
|
return;
|
|
@@ -26338,19 +26960,13 @@ var BottlenecksContent = ({
|
|
|
26338
26960
|
const fullResult = await s3ClipsService.getClipCounts(
|
|
26339
26961
|
workspaceId,
|
|
26340
26962
|
operationalDate,
|
|
26341
|
-
shiftStr
|
|
26342
|
-
|
|
26343
|
-
// Build index
|
|
26963
|
+
shiftStr
|
|
26964
|
+
// Don't build index - use pagination for cost efficiency
|
|
26344
26965
|
);
|
|
26345
|
-
if (fullResult
|
|
26346
|
-
updateClipCounts(fullResult.counts);
|
|
26347
|
-
updateVideoIndex(fullResult.videoIndex);
|
|
26348
|
-
await smartVideoCache.setClipCounts(cacheKey, fullResult, 5);
|
|
26349
|
-
console.log(`[BottlenecksContent] Fetched and cached clip counts with ${fullResult.videoIndex.allVideos.length} videos`);
|
|
26350
|
-
} else if (fullResult) {
|
|
26966
|
+
if (fullResult) {
|
|
26351
26967
|
const counts = fullResult;
|
|
26352
26968
|
updateClipCounts(counts);
|
|
26353
|
-
console.log(`[BottlenecksContent] Fetched clip counts
|
|
26969
|
+
console.log(`[BottlenecksContent] Fetched and cached clip counts`);
|
|
26354
26970
|
}
|
|
26355
26971
|
setIsLoading(false);
|
|
26356
26972
|
setHasInitialLoad(true);
|
|
@@ -26363,7 +26979,7 @@ var BottlenecksContent = ({
|
|
|
26363
26979
|
} finally {
|
|
26364
26980
|
fetchInProgressRef.current.delete(operationKey);
|
|
26365
26981
|
}
|
|
26366
|
-
}, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts
|
|
26982
|
+
}, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts]);
|
|
26367
26983
|
const loadingCategoryRef = useRef(null);
|
|
26368
26984
|
const videoRetryCountRef = useRef(0);
|
|
26369
26985
|
const loadingVideosRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -26371,7 +26987,6 @@ var BottlenecksContent = ({
|
|
|
26371
26987
|
const ensureVideosLoaded = useCallback(async (centerIndex) => {
|
|
26372
26988
|
if (!s3ClipsService || !workspaceId || !isMountedRef.current) return;
|
|
26373
26989
|
const currentFilter = activeFilterRef.current;
|
|
26374
|
-
const currentVideoIndex = videoIndexRef.current;
|
|
26375
26990
|
let effectiveFilter = currentFilter;
|
|
26376
26991
|
if (sopCategories && sopCategories.length > 0) {
|
|
26377
26992
|
const category = sopCategories.find((cat) => cat.id === currentFilter);
|
|
@@ -26398,17 +27013,6 @@ var BottlenecksContent = ({
|
|
|
26398
27013
|
const loadPromises = indicesToLoad.map(async (index) => {
|
|
26399
27014
|
try {
|
|
26400
27015
|
let video = null;
|
|
26401
|
-
if (currentVideoIndex && currentVideoIndex.byCategory && currentVideoIndex.allVideos.length > 0) {
|
|
26402
|
-
video = await s3ClipsService.getVideoFromIndex(
|
|
26403
|
-
currentVideoIndex,
|
|
26404
|
-
effectiveFilter,
|
|
26405
|
-
index,
|
|
26406
|
-
true,
|
|
26407
|
-
// includeCycleTime - OK for preloading
|
|
26408
|
-
false
|
|
26409
|
-
// includeMetadata - NO metadata during bulk preloading to prevent flooding
|
|
26410
|
-
);
|
|
26411
|
-
}
|
|
26412
27016
|
if (!video) {
|
|
26413
27017
|
const operationalDate = date || getOperationalDate();
|
|
26414
27018
|
const shiftStr = effectiveShift;
|
|
@@ -26461,12 +27065,11 @@ var BottlenecksContent = ({
|
|
|
26461
27065
|
try {
|
|
26462
27066
|
const operationalDate = date || getOperationalDate();
|
|
26463
27067
|
const shiftStr = effectiveShift;
|
|
26464
|
-
if (!clipCounts[targetCategory]
|
|
27068
|
+
if (!clipCounts[targetCategory]) {
|
|
26465
27069
|
const cacheKey = `clip-counts:${workspaceId}:${operationalDate}:${shiftStr}`;
|
|
26466
27070
|
const cachedResult = await smartVideoCache.getClipCounts(cacheKey);
|
|
26467
27071
|
if (cachedResult && cachedResult.counts[targetCategory] > 0) {
|
|
26468
27072
|
updateClipCounts(cachedResult.counts);
|
|
26469
|
-
updateVideoIndex(cachedResult.videoIndex);
|
|
26470
27073
|
setHasInitialLoad(true);
|
|
26471
27074
|
console.log(`[BottlenecksContent] Used cached data for loadFirstVideoForCategory - ${targetCategory}`);
|
|
26472
27075
|
}
|
|
@@ -26498,11 +27101,14 @@ var BottlenecksContent = ({
|
|
|
26498
27101
|
} catch (directError) {
|
|
26499
27102
|
console.warn(`[BottlenecksContent] Direct S3 loading failed, trying video index:`, directError);
|
|
26500
27103
|
}
|
|
26501
|
-
|
|
26502
|
-
if (clipCounts[targetCategory] > 0 && currentVideoIndex && currentVideoIndex.allVideos.length > 0) {
|
|
27104
|
+
if (clipCounts[targetCategory] > 0) {
|
|
26503
27105
|
try {
|
|
26504
|
-
const
|
|
26505
|
-
|
|
27106
|
+
const operationalDate2 = date || getOperationalDate();
|
|
27107
|
+
const shiftStr2 = effectiveShift;
|
|
27108
|
+
const firstVideo = await s3ClipsService.getClipByIndex(
|
|
27109
|
+
workspaceId,
|
|
27110
|
+
operationalDate2,
|
|
27111
|
+
shiftStr2,
|
|
26506
27112
|
targetCategory,
|
|
26507
27113
|
0,
|
|
26508
27114
|
// First video (index 0)
|
|
@@ -26542,25 +27148,22 @@ var BottlenecksContent = ({
|
|
|
26542
27148
|
loadingCategoryRef.current = null;
|
|
26543
27149
|
fetchInProgressRef.current.delete(operationKey);
|
|
26544
27150
|
}
|
|
26545
|
-
}, [workspaceId, date, s3ClipsService, clipCounts,
|
|
27151
|
+
}, [workspaceId, date, s3ClipsService, clipCounts, effectiveShift, updateClipCounts]);
|
|
26546
27152
|
useEffect(() => {
|
|
26547
27153
|
if (s3ClipsService && !prefetchData) {
|
|
26548
27154
|
fetchClipCounts();
|
|
26549
27155
|
}
|
|
26550
|
-
}, [workspaceId, date, effectiveShift, s3ClipsService, fetchClipCounts, updateClipCounts,
|
|
27156
|
+
}, [workspaceId, date, effectiveShift, s3ClipsService, fetchClipCounts, updateClipCounts, prefetchData]);
|
|
26551
27157
|
useEffect(() => {
|
|
26552
|
-
if (prefetchData) {
|
|
26553
|
-
console.log(`[BottlenecksContent] Received prefetch update - status: ${prefetchStatus}
|
|
27158
|
+
if (prefetchData && prefetchData.counts) {
|
|
27159
|
+
console.log(`[BottlenecksContent] Received prefetch update - status: ${prefetchStatus}`);
|
|
26554
27160
|
updateClipCounts(prefetchData.counts);
|
|
26555
|
-
if (
|
|
26556
|
-
updateVideoIndex(prefetchData.videoIndex);
|
|
26557
|
-
}
|
|
26558
|
-
if (!hasInitialLoad && prefetchData.videoIndex.allVideos.length > 0) {
|
|
27161
|
+
if (!hasInitialLoad) {
|
|
26559
27162
|
setIsLoading(false);
|
|
26560
27163
|
setHasInitialLoad(true);
|
|
26561
27164
|
}
|
|
26562
27165
|
}
|
|
26563
|
-
}, [prefetchData, prefetchStatus, updateClipCounts,
|
|
27166
|
+
}, [prefetchData, prefetchStatus, updateClipCounts, hasInitialLoad]);
|
|
26564
27167
|
useEffect(() => {
|
|
26565
27168
|
if (s3ClipsService && clipCounts[activeFilter] > 0) {
|
|
26566
27169
|
const hasVideosForCurrentFilter = allVideos.some((video) => {
|
|
@@ -26600,7 +27203,7 @@ var BottlenecksContent = ({
|
|
|
26600
27203
|
setIsCategoryLoading(false);
|
|
26601
27204
|
}
|
|
26602
27205
|
}
|
|
26603
|
-
}, [activeFilter, s3ClipsService,
|
|
27206
|
+
}, [activeFilter, s3ClipsService, clipCounts, loadFirstVideoForCategory, allVideos, sopCategories]);
|
|
26604
27207
|
useEffect(() => {
|
|
26605
27208
|
if (previousFilterRef.current !== activeFilter) {
|
|
26606
27209
|
console.log(`Filter changed from ${previousFilterRef.current} to ${activeFilter} - resetting to first video`);
|
|
@@ -26716,23 +27319,7 @@ var BottlenecksContent = ({
|
|
|
26716
27319
|
}
|
|
26717
27320
|
try {
|
|
26718
27321
|
let video = null;
|
|
26719
|
-
|
|
26720
|
-
if (currentVideoIndex && currentVideoIndex.byCategory && currentVideoIndex.allVideos.length > 0 && s3ClipsService) {
|
|
26721
|
-
console.log(`[BottlenecksContent] Using video index for navigation - ID: ${currentVideoIndex._debugId || "NO_ID"}, total categories: ${currentVideoIndex.byCategory.size}, total videos: ${currentVideoIndex.allVideos.length}, filter: ${currentFilter}`);
|
|
26722
|
-
console.log(`[BottlenecksContent] VideoIndex categories in handleNext: [${Array.from(currentVideoIndex.byCategory.keys()).join(", ")}]`);
|
|
26723
|
-
video = await s3ClipsService.getVideoFromIndex(
|
|
26724
|
-
currentVideoIndex,
|
|
26725
|
-
effectiveFilter,
|
|
26726
|
-
nextIndex,
|
|
26727
|
-
true,
|
|
26728
|
-
// includeCycleTime
|
|
26729
|
-
false
|
|
26730
|
-
// includeMetadata - DON'T fetch metadata during navigation to prevent flooding!
|
|
26731
|
-
);
|
|
26732
|
-
} else {
|
|
26733
|
-
console.warn(`[BottlenecksContent] Video index not ready for navigation: ID: ${currentVideoIndex?._debugId || "NO_ID"}, byCategory exists = ${!!currentVideoIndex?.byCategory}, allVideos = ${currentVideoIndex?.allVideos?.length || 0}`);
|
|
26734
|
-
}
|
|
26735
|
-
if (!video && s3ClipsService) {
|
|
27322
|
+
if (s3ClipsService) {
|
|
26736
27323
|
const operationalDate = date || getOperationalDate();
|
|
26737
27324
|
const shiftStr = effectiveShift;
|
|
26738
27325
|
video = await s3ClipsService.getClipByIndex(
|
|
@@ -27994,6 +28581,451 @@ var KPISection = memo(({
|
|
|
27994
28581
|
return true;
|
|
27995
28582
|
});
|
|
27996
28583
|
KPISection.displayName = "KPISection";
|
|
28584
|
+
var WorkspaceHealthCard = ({
|
|
28585
|
+
workspace,
|
|
28586
|
+
onClick,
|
|
28587
|
+
showDetails = true,
|
|
28588
|
+
className = ""
|
|
28589
|
+
}) => {
|
|
28590
|
+
const getStatusConfig = () => {
|
|
28591
|
+
switch (workspace.status) {
|
|
28592
|
+
case "healthy":
|
|
28593
|
+
return {
|
|
28594
|
+
gradient: "from-emerald-50 to-green-50 dark:from-emerald-950/30 dark:to-green-950/30",
|
|
28595
|
+
border: "border-emerald-200 dark:border-emerald-800",
|
|
28596
|
+
icon: CheckCircle2,
|
|
28597
|
+
iconColor: "text-emerald-600 dark:text-emerald-400",
|
|
28598
|
+
badge: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300",
|
|
28599
|
+
statusText: "Online",
|
|
28600
|
+
pulse: true
|
|
28601
|
+
};
|
|
28602
|
+
case "unhealthy":
|
|
28603
|
+
return {
|
|
28604
|
+
gradient: "from-rose-50 to-red-50 dark:from-rose-950/30 dark:to-red-950/30",
|
|
28605
|
+
border: "border-rose-200 dark:border-rose-800",
|
|
28606
|
+
icon: XCircle,
|
|
28607
|
+
iconColor: "text-rose-600 dark:text-rose-400",
|
|
28608
|
+
badge: "bg-rose-100 text-rose-700 dark:bg-rose-900/50 dark:text-rose-300",
|
|
28609
|
+
statusText: "Offline",
|
|
28610
|
+
pulse: false
|
|
28611
|
+
};
|
|
28612
|
+
case "warning":
|
|
28613
|
+
return {
|
|
28614
|
+
gradient: "from-amber-50 to-yellow-50 dark:from-amber-950/30 dark:to-yellow-950/30",
|
|
28615
|
+
border: "border-amber-200 dark:border-amber-800",
|
|
28616
|
+
icon: AlertTriangle,
|
|
28617
|
+
iconColor: "text-amber-600 dark:text-amber-400",
|
|
28618
|
+
badge: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
|
28619
|
+
statusText: "Degraded",
|
|
28620
|
+
pulse: true
|
|
28621
|
+
};
|
|
28622
|
+
default:
|
|
28623
|
+
return {
|
|
28624
|
+
gradient: "from-gray-50 to-slate-50 dark:from-gray-950/30 dark:to-slate-950/30",
|
|
28625
|
+
border: "border-gray-200 dark:border-gray-800",
|
|
28626
|
+
icon: Activity,
|
|
28627
|
+
iconColor: "text-gray-500 dark:text-gray-400",
|
|
28628
|
+
badge: "bg-gray-100 text-gray-700 dark:bg-gray-900/50 dark:text-gray-300",
|
|
28629
|
+
statusText: "Unknown",
|
|
28630
|
+
pulse: false
|
|
28631
|
+
};
|
|
28632
|
+
}
|
|
28633
|
+
};
|
|
28634
|
+
const config = getStatusConfig();
|
|
28635
|
+
const StatusIcon = config.icon;
|
|
28636
|
+
workspace.isStale ? WifiOff : Wifi;
|
|
28637
|
+
const handleClick = () => {
|
|
28638
|
+
if (onClick) {
|
|
28639
|
+
onClick(workspace);
|
|
28640
|
+
}
|
|
28641
|
+
};
|
|
28642
|
+
const handleKeyDown = (event) => {
|
|
28643
|
+
if (onClick && (event.key === "Enter" || event.key === " ")) {
|
|
28644
|
+
event.preventDefault();
|
|
28645
|
+
onClick(workspace);
|
|
28646
|
+
}
|
|
28647
|
+
};
|
|
28648
|
+
const formatTimeAgo = (timeString) => {
|
|
28649
|
+
return timeString.replace("about ", "").replace(" ago", "");
|
|
28650
|
+
};
|
|
28651
|
+
return /* @__PURE__ */ jsx(
|
|
28652
|
+
Card2,
|
|
28653
|
+
{
|
|
28654
|
+
className: clsx(
|
|
28655
|
+
"relative overflow-hidden transition-all duration-300",
|
|
28656
|
+
"bg-gradient-to-br",
|
|
28657
|
+
config.gradient,
|
|
28658
|
+
"border",
|
|
28659
|
+
config.border,
|
|
28660
|
+
"shadow-sm hover:shadow-md",
|
|
28661
|
+
onClick && "cursor-pointer hover:scale-[1.01]",
|
|
28662
|
+
workspace.isStale && "opacity-90",
|
|
28663
|
+
className
|
|
28664
|
+
),
|
|
28665
|
+
onClick: handleClick,
|
|
28666
|
+
onKeyDown: handleKeyDown,
|
|
28667
|
+
tabIndex: onClick ? 0 : void 0,
|
|
28668
|
+
role: onClick ? "button" : void 0,
|
|
28669
|
+
"aria-label": `Workspace ${workspace.workspace_display_name} status: ${workspace.status}`,
|
|
28670
|
+
children: /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
|
|
28671
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between mb-3", children: [
|
|
28672
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
28673
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1", children: workspace.workspace_display_name || `Workspace ${workspace.workspace_id.slice(0, 8)}` }),
|
|
28674
|
+
showDetails && workspace.line_name && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: workspace.line_name })
|
|
28675
|
+
] }),
|
|
28676
|
+
/* @__PURE__ */ jsxs("div", { className: clsx(
|
|
28677
|
+
"flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium",
|
|
28678
|
+
config.badge
|
|
28679
|
+
), children: [
|
|
28680
|
+
/* @__PURE__ */ jsx(StatusIcon, { className: "h-3.5 w-3.5" }),
|
|
28681
|
+
/* @__PURE__ */ jsx("span", { children: config.statusText })
|
|
28682
|
+
] })
|
|
28683
|
+
] }),
|
|
28684
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
28685
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
28686
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
28687
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-3.5 w-3.5 text-gray-400" }),
|
|
28688
|
+
/* @__PURE__ */ jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap", children: [
|
|
28689
|
+
"Last seen: ",
|
|
28690
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: formatTimeAgo(workspace.timeSinceLastUpdate) })
|
|
28691
|
+
] })
|
|
28692
|
+
] }),
|
|
28693
|
+
workspace.isStale && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
28694
|
+
/* @__PURE__ */ jsx(WifiOff, { className: "h-3.5 w-3.5 text-amber-500" }),
|
|
28695
|
+
/* @__PURE__ */ jsx("span", { className: "text-amber-600 dark:text-amber-400 text-xs", children: "No recent updates" })
|
|
28696
|
+
] })
|
|
28697
|
+
] }),
|
|
28698
|
+
config.pulse && !workspace.isStale && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
28699
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex h-2 w-2", children: [
|
|
28700
|
+
/* @__PURE__ */ jsx("span", { className: clsx(
|
|
28701
|
+
"animate-ping absolute inline-flex h-full w-full rounded-full opacity-75",
|
|
28702
|
+
workspace.status === "healthy" ? "bg-emerald-400" : "bg-amber-400"
|
|
28703
|
+
) }),
|
|
28704
|
+
/* @__PURE__ */ jsx("span", { className: clsx(
|
|
28705
|
+
"relative inline-flex rounded-full h-2 w-2",
|
|
28706
|
+
workspace.status === "healthy" ? "bg-emerald-500" : "bg-amber-500"
|
|
28707
|
+
) })
|
|
28708
|
+
] }),
|
|
28709
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Live" })
|
|
28710
|
+
] })
|
|
28711
|
+
] })
|
|
28712
|
+
] })
|
|
28713
|
+
}
|
|
28714
|
+
);
|
|
28715
|
+
};
|
|
28716
|
+
var CompactWorkspaceHealthCard = ({
|
|
28717
|
+
workspace,
|
|
28718
|
+
onClick,
|
|
28719
|
+
className = ""
|
|
28720
|
+
}) => {
|
|
28721
|
+
const getStatusConfig = () => {
|
|
28722
|
+
switch (workspace.status) {
|
|
28723
|
+
case "healthy":
|
|
28724
|
+
return {
|
|
28725
|
+
dot: "bg-emerald-500",
|
|
28726
|
+
icon: CheckCircle2,
|
|
28727
|
+
iconColor: "text-emerald-600 dark:text-emerald-400",
|
|
28728
|
+
bg: "hover:bg-emerald-50 dark:hover:bg-emerald-950/20"
|
|
28729
|
+
};
|
|
28730
|
+
case "unhealthy":
|
|
28731
|
+
return {
|
|
28732
|
+
dot: "bg-rose-500",
|
|
28733
|
+
icon: XCircle,
|
|
28734
|
+
iconColor: "text-rose-600 dark:text-rose-400",
|
|
28735
|
+
bg: "hover:bg-rose-50 dark:hover:bg-rose-950/20"
|
|
28736
|
+
};
|
|
28737
|
+
case "warning":
|
|
28738
|
+
return {
|
|
28739
|
+
dot: "bg-amber-500",
|
|
28740
|
+
icon: AlertTriangle,
|
|
28741
|
+
iconColor: "text-amber-600 dark:text-amber-400",
|
|
28742
|
+
bg: "hover:bg-amber-50 dark:hover:bg-amber-950/20"
|
|
28743
|
+
};
|
|
28744
|
+
default:
|
|
28745
|
+
return {
|
|
28746
|
+
dot: "bg-gray-400",
|
|
28747
|
+
icon: Activity,
|
|
28748
|
+
iconColor: "text-gray-500 dark:text-gray-400",
|
|
28749
|
+
bg: "hover:bg-gray-50 dark:hover:bg-gray-950/20"
|
|
28750
|
+
};
|
|
28751
|
+
}
|
|
28752
|
+
};
|
|
28753
|
+
const config = getStatusConfig();
|
|
28754
|
+
const StatusIcon = config.icon;
|
|
28755
|
+
const handleClick = () => {
|
|
28756
|
+
if (onClick) {
|
|
28757
|
+
onClick(workspace);
|
|
28758
|
+
}
|
|
28759
|
+
};
|
|
28760
|
+
return /* @__PURE__ */ jsxs(
|
|
28761
|
+
"div",
|
|
28762
|
+
{
|
|
28763
|
+
className: clsx(
|
|
28764
|
+
"flex items-center justify-between px-4 py-3 rounded-lg border",
|
|
28765
|
+
"bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700",
|
|
28766
|
+
"transition-all duration-200",
|
|
28767
|
+
onClick && `cursor-pointer ${config.bg}`,
|
|
28768
|
+
className
|
|
28769
|
+
),
|
|
28770
|
+
onClick: handleClick,
|
|
28771
|
+
role: onClick ? "button" : void 0,
|
|
28772
|
+
tabIndex: onClick ? 0 : void 0,
|
|
28773
|
+
children: [
|
|
28774
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
28775
|
+
/* @__PURE__ */ jsx(StatusIcon, { className: clsx("h-5 w-5", config.iconColor) }),
|
|
28776
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
28777
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: workspace.workspace_display_name || `WS-${workspace.workspace_id.slice(0, 6)}` }),
|
|
28778
|
+
workspace.line_name && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.line_name })
|
|
28779
|
+
] })
|
|
28780
|
+
] }),
|
|
28781
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
28782
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.timeSinceLastUpdate }),
|
|
28783
|
+
/* @__PURE__ */ jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) })
|
|
28784
|
+
] })
|
|
28785
|
+
]
|
|
28786
|
+
}
|
|
28787
|
+
);
|
|
28788
|
+
};
|
|
28789
|
+
var HealthStatusGrid = ({
|
|
28790
|
+
workspaces,
|
|
28791
|
+
onWorkspaceClick,
|
|
28792
|
+
viewMode: initialViewMode = "grid",
|
|
28793
|
+
showFilters = true,
|
|
28794
|
+
groupBy: initialGroupBy = "none",
|
|
28795
|
+
className = ""
|
|
28796
|
+
}) => {
|
|
28797
|
+
const [viewMode, setViewMode] = useState(initialViewMode);
|
|
28798
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
28799
|
+
const [statusFilter, setStatusFilter] = useState("all");
|
|
28800
|
+
const [groupBy, setGroupBy] = useState(initialGroupBy);
|
|
28801
|
+
const [expandedGroups, setExpandedGroups] = useState(/* @__PURE__ */ new Set());
|
|
28802
|
+
const filteredWorkspaces = useMemo(() => {
|
|
28803
|
+
let filtered = [...workspaces];
|
|
28804
|
+
if (searchTerm) {
|
|
28805
|
+
const search = searchTerm.toLowerCase();
|
|
28806
|
+
filtered = filtered.filter(
|
|
28807
|
+
(w) => w.workspace_display_name?.toLowerCase().includes(search) || w.line_name?.toLowerCase().includes(search) || w.company_name?.toLowerCase().includes(search)
|
|
28808
|
+
);
|
|
28809
|
+
}
|
|
28810
|
+
if (statusFilter !== "all") {
|
|
28811
|
+
filtered = filtered.filter((w) => w.status === statusFilter);
|
|
28812
|
+
}
|
|
28813
|
+
return filtered;
|
|
28814
|
+
}, [workspaces, searchTerm, statusFilter]);
|
|
28815
|
+
const groupedWorkspaces = useMemo(() => {
|
|
28816
|
+
if (groupBy === "none") {
|
|
28817
|
+
return { "All Workspaces": filteredWorkspaces };
|
|
28818
|
+
}
|
|
28819
|
+
const groups = {};
|
|
28820
|
+
filteredWorkspaces.forEach((workspace) => {
|
|
28821
|
+
let key = "Unknown";
|
|
28822
|
+
switch (groupBy) {
|
|
28823
|
+
case "line":
|
|
28824
|
+
key = workspace.line_name || "Unknown Line";
|
|
28825
|
+
break;
|
|
28826
|
+
case "status":
|
|
28827
|
+
key = workspace.status;
|
|
28828
|
+
break;
|
|
28829
|
+
}
|
|
28830
|
+
if (!groups[key]) {
|
|
28831
|
+
groups[key] = [];
|
|
28832
|
+
}
|
|
28833
|
+
groups[key].push(workspace);
|
|
28834
|
+
});
|
|
28835
|
+
const sortedGroups = {};
|
|
28836
|
+
Object.keys(groups).sort().forEach((key) => {
|
|
28837
|
+
sortedGroups[key] = groups[key];
|
|
28838
|
+
});
|
|
28839
|
+
return sortedGroups;
|
|
28840
|
+
}, [filteredWorkspaces, groupBy]);
|
|
28841
|
+
useEffect(() => {
|
|
28842
|
+
if (groupBy !== "none") {
|
|
28843
|
+
setExpandedGroups(new Set(Object.keys(groupedWorkspaces)));
|
|
28844
|
+
}
|
|
28845
|
+
}, [groupBy, groupedWorkspaces]);
|
|
28846
|
+
const toggleGroup = (groupName) => {
|
|
28847
|
+
const newExpanded = new Set(expandedGroups);
|
|
28848
|
+
if (newExpanded.has(groupName)) {
|
|
28849
|
+
newExpanded.delete(groupName);
|
|
28850
|
+
} else {
|
|
28851
|
+
newExpanded.add(groupName);
|
|
28852
|
+
}
|
|
28853
|
+
setExpandedGroups(newExpanded);
|
|
28854
|
+
};
|
|
28855
|
+
const getStatusCounts = () => {
|
|
28856
|
+
const counts = {
|
|
28857
|
+
healthy: 0,
|
|
28858
|
+
unhealthy: 0,
|
|
28859
|
+
warning: 0,
|
|
28860
|
+
unknown: 0
|
|
28861
|
+
};
|
|
28862
|
+
workspaces.forEach((w) => {
|
|
28863
|
+
counts[w.status]++;
|
|
28864
|
+
});
|
|
28865
|
+
return counts;
|
|
28866
|
+
};
|
|
28867
|
+
const statusCounts = getStatusCounts();
|
|
28868
|
+
return /* @__PURE__ */ jsxs("div", { className: clsx("space-y-4", className), children: [
|
|
28869
|
+
showFilters && /* @__PURE__ */ jsxs("div", { className: "bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4", children: [
|
|
28870
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-3 flex-wrap", children: [
|
|
28871
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-w-[200px]", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
28872
|
+
/* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" }),
|
|
28873
|
+
/* @__PURE__ */ jsx(
|
|
28874
|
+
"input",
|
|
28875
|
+
{
|
|
28876
|
+
type: "text",
|
|
28877
|
+
placeholder: "Search workspaces...",
|
|
28878
|
+
value: searchTerm,
|
|
28879
|
+
onChange: (e) => setSearchTerm(e.target.value),
|
|
28880
|
+
className: "w-full pl-10 pr-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
|
28881
|
+
}
|
|
28882
|
+
)
|
|
28883
|
+
] }) }),
|
|
28884
|
+
/* @__PURE__ */ jsxs(Select, { value: statusFilter, onValueChange: (value) => setStatusFilter(value), children: [
|
|
28885
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-full sm:w-[180px] bg-white border-gray-200", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "All statuses" }) }),
|
|
28886
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
28887
|
+
/* @__PURE__ */ jsxs(SelectItem, { value: "all", children: [
|
|
28888
|
+
"All (",
|
|
28889
|
+
workspaces.length,
|
|
28890
|
+
")"
|
|
28891
|
+
] }),
|
|
28892
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "healthy", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28893
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500" }),
|
|
28894
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
28895
|
+
"Healthy (",
|
|
28896
|
+
statusCounts.healthy,
|
|
28897
|
+
")"
|
|
28898
|
+
] })
|
|
28899
|
+
] }) }),
|
|
28900
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "unhealthy", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28901
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-red-500" }),
|
|
28902
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
28903
|
+
"Unhealthy (",
|
|
28904
|
+
statusCounts.unhealthy,
|
|
28905
|
+
")"
|
|
28906
|
+
] })
|
|
28907
|
+
] }) }),
|
|
28908
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "warning", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28909
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-500" }),
|
|
28910
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
28911
|
+
"Warning (",
|
|
28912
|
+
statusCounts.warning,
|
|
28913
|
+
")"
|
|
28914
|
+
] })
|
|
28915
|
+
] }) }),
|
|
28916
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "unknown", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28917
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-gray-400" }),
|
|
28918
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
28919
|
+
"Unknown (",
|
|
28920
|
+
statusCounts.unknown,
|
|
28921
|
+
")"
|
|
28922
|
+
] })
|
|
28923
|
+
] }) })
|
|
28924
|
+
] })
|
|
28925
|
+
] }),
|
|
28926
|
+
/* @__PURE__ */ jsxs(Select, { value: groupBy, onValueChange: (value) => setGroupBy(value), children: [
|
|
28927
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-full sm:w-[160px] bg-white border-gray-200", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Group by" }) }),
|
|
28928
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
28929
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "none", children: "No grouping" }),
|
|
28930
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "line", children: "Group by Line" }),
|
|
28931
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "status", children: "Group by Status" })
|
|
28932
|
+
] })
|
|
28933
|
+
] }),
|
|
28934
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
28935
|
+
/* @__PURE__ */ jsx(
|
|
28936
|
+
"button",
|
|
28937
|
+
{
|
|
28938
|
+
onClick: () => setViewMode("grid"),
|
|
28939
|
+
className: clsx(
|
|
28940
|
+
"p-2 rounded-lg transition-colors",
|
|
28941
|
+
viewMode === "grid" ? "bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
28942
|
+
),
|
|
28943
|
+
"aria-label": "Grid view",
|
|
28944
|
+
children: /* @__PURE__ */ jsx(Grid3x3, { className: "h-5 w-5" })
|
|
28945
|
+
}
|
|
28946
|
+
),
|
|
28947
|
+
/* @__PURE__ */ jsx(
|
|
28948
|
+
"button",
|
|
28949
|
+
{
|
|
28950
|
+
onClick: () => setViewMode("list"),
|
|
28951
|
+
className: clsx(
|
|
28952
|
+
"p-2 rounded-lg transition-colors",
|
|
28953
|
+
viewMode === "list" ? "bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
28954
|
+
),
|
|
28955
|
+
"aria-label": "List view",
|
|
28956
|
+
children: /* @__PURE__ */ jsx(List, { className: "h-5 w-5" })
|
|
28957
|
+
}
|
|
28958
|
+
)
|
|
28959
|
+
] })
|
|
28960
|
+
] }),
|
|
28961
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 text-sm text-gray-500 dark:text-gray-400", children: [
|
|
28962
|
+
"Showing ",
|
|
28963
|
+
filteredWorkspaces.length,
|
|
28964
|
+
" of ",
|
|
28965
|
+
workspaces.length,
|
|
28966
|
+
" workspaces"
|
|
28967
|
+
] })
|
|
28968
|
+
] }),
|
|
28969
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-6", children: Object.entries(groupedWorkspaces).map(([groupName, groupWorkspaces]) => {
|
|
28970
|
+
const isExpanded = groupBy === "none" || expandedGroups.has(groupName);
|
|
28971
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
28972
|
+
groupBy !== "none" && /* @__PURE__ */ jsxs(
|
|
28973
|
+
"div",
|
|
28974
|
+
{
|
|
28975
|
+
className: "flex items-center justify-between cursor-pointer group",
|
|
28976
|
+
onClick: () => toggleGroup(groupName),
|
|
28977
|
+
children: [
|
|
28978
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
28979
|
+
groupName,
|
|
28980
|
+
/* @__PURE__ */ jsxs("span", { className: "text-sm font-normal text-gray-500 dark:text-gray-400", children: [
|
|
28981
|
+
"(",
|
|
28982
|
+
groupWorkspaces.length,
|
|
28983
|
+
")"
|
|
28984
|
+
] })
|
|
28985
|
+
] }),
|
|
28986
|
+
/* @__PURE__ */ jsx(
|
|
28987
|
+
ChevronDown,
|
|
28988
|
+
{
|
|
28989
|
+
className: clsx(
|
|
28990
|
+
"h-5 w-5 text-gray-400 transition-transform",
|
|
28991
|
+
isExpanded && "rotate-180"
|
|
28992
|
+
)
|
|
28993
|
+
}
|
|
28994
|
+
)
|
|
28995
|
+
]
|
|
28996
|
+
}
|
|
28997
|
+
),
|
|
28998
|
+
isExpanded && /* @__PURE__ */ jsx(
|
|
28999
|
+
"div",
|
|
29000
|
+
{
|
|
29001
|
+
className: clsx(
|
|
29002
|
+
viewMode === "grid" ? "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4" : "space-y-2"
|
|
29003
|
+
),
|
|
29004
|
+
children: groupWorkspaces.map(
|
|
29005
|
+
(workspace) => viewMode === "grid" ? /* @__PURE__ */ jsx(
|
|
29006
|
+
WorkspaceHealthCard,
|
|
29007
|
+
{
|
|
29008
|
+
workspace,
|
|
29009
|
+
onClick: onWorkspaceClick,
|
|
29010
|
+
showDetails: true
|
|
29011
|
+
},
|
|
29012
|
+
workspace.workspace_id
|
|
29013
|
+
) : /* @__PURE__ */ jsx(
|
|
29014
|
+
CompactWorkspaceHealthCard,
|
|
29015
|
+
{
|
|
29016
|
+
workspace,
|
|
29017
|
+
onClick: onWorkspaceClick
|
|
29018
|
+
},
|
|
29019
|
+
workspace.workspace_id
|
|
29020
|
+
)
|
|
29021
|
+
)
|
|
29022
|
+
}
|
|
29023
|
+
)
|
|
29024
|
+
] }, groupName);
|
|
29025
|
+
}) }),
|
|
29026
|
+
filteredWorkspaces.length === 0 && /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-gray-500 dark:text-gray-400", children: searchTerm || statusFilter !== "all" ? "No workspaces found matching your filters." : "No workspaces available." }) })
|
|
29027
|
+
] });
|
|
29028
|
+
};
|
|
27997
29029
|
var ISTTimer2 = ISTTimer_default;
|
|
27998
29030
|
var DashboardHeader = memo(({ lineTitle, className = "", headerControls }) => {
|
|
27999
29031
|
const getShiftName = () => {
|
|
@@ -28491,6 +29523,17 @@ var SideNavBar = memo(({
|
|
|
28491
29523
|
});
|
|
28492
29524
|
onMobileMenuClose?.();
|
|
28493
29525
|
}, [navigate, onMobileMenuClose]);
|
|
29526
|
+
const handleHealthClick = useCallback(() => {
|
|
29527
|
+
navigate("/health", {
|
|
29528
|
+
trackingEvent: {
|
|
29529
|
+
name: "Health Status Page Clicked",
|
|
29530
|
+
properties: {
|
|
29531
|
+
source: "side_nav"
|
|
29532
|
+
}
|
|
29533
|
+
}
|
|
29534
|
+
});
|
|
29535
|
+
onMobileMenuClose?.();
|
|
29536
|
+
}, [navigate, onMobileMenuClose]);
|
|
28494
29537
|
const handleLogoClick = useCallback(() => {
|
|
28495
29538
|
navigate("/");
|
|
28496
29539
|
onMobileMenuClose?.();
|
|
@@ -28504,6 +29547,7 @@ var SideNavBar = memo(({
|
|
|
28504
29547
|
const profileButtonClasses = useMemo(() => getButtonClasses("/profile"), [getButtonClasses, pathname]);
|
|
28505
29548
|
const helpButtonClasses = useMemo(() => getButtonClasses("/help"), [getButtonClasses, pathname]);
|
|
28506
29549
|
const skusButtonClasses = useMemo(() => getButtonClasses("/skus"), [getButtonClasses, pathname]);
|
|
29550
|
+
const healthButtonClasses = useMemo(() => getButtonClasses("/health"), [getButtonClasses, pathname]);
|
|
28507
29551
|
const NavigationContent = () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
28508
29552
|
/* @__PURE__ */ jsx("div", { className: "w-full py-6 px-4 flex-shrink-0", children: /* @__PURE__ */ jsx(
|
|
28509
29553
|
"button",
|
|
@@ -28648,6 +29692,21 @@ var SideNavBar = memo(({
|
|
|
28648
29692
|
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Help" })
|
|
28649
29693
|
]
|
|
28650
29694
|
}
|
|
29695
|
+
),
|
|
29696
|
+
/* @__PURE__ */ jsxs(
|
|
29697
|
+
"button",
|
|
29698
|
+
{
|
|
29699
|
+
onClick: handleHealthClick,
|
|
29700
|
+
className: healthButtonClasses,
|
|
29701
|
+
"aria-label": "System Health",
|
|
29702
|
+
tabIndex: 0,
|
|
29703
|
+
role: "tab",
|
|
29704
|
+
"aria-selected": pathname === "/health" || pathname.startsWith("/health/"),
|
|
29705
|
+
children: [
|
|
29706
|
+
/* @__PURE__ */ jsx(HeartIcon, { className: "w-5 h-5 mb-1" }),
|
|
29707
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Health" })
|
|
29708
|
+
]
|
|
29709
|
+
}
|
|
28651
29710
|
)
|
|
28652
29711
|
] })
|
|
28653
29712
|
] }),
|
|
@@ -30790,16 +31849,13 @@ var AIAgentView = () => {
|
|
|
30790
31849
|
} }),
|
|
30791
31850
|
/* @__PURE__ */ jsxs("div", { className: `flex-1 flex flex-col h-screen transition-all duration-300 ${isSidebarOpen ? "mr-80" : "mr-0"}`, children: [
|
|
30792
31851
|
/* @__PURE__ */ jsx("header", { className: "flex-shrink-0 bg-white px-8 py-6 shadow-sm border-b border-gray-200/80 sticky top-0 z-10", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between relative", children: [
|
|
30793
|
-
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */
|
|
30794
|
-
|
|
31852
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsx(
|
|
31853
|
+
BackButtonMinimal,
|
|
30795
31854
|
{
|
|
30796
31855
|
onClick: () => navigate("/"),
|
|
30797
|
-
|
|
30798
|
-
|
|
30799
|
-
|
|
30800
|
-
/* @__PURE__ */ jsx(ArrowLeft, { className: "h-5 w-5" }),
|
|
30801
|
-
/* @__PURE__ */ jsx("span", { className: "ml-2", children: "Back" })
|
|
30802
|
-
]
|
|
31856
|
+
text: "Back",
|
|
31857
|
+
size: "default",
|
|
31858
|
+
"aria-label": "Navigate back to dashboard"
|
|
30803
31859
|
}
|
|
30804
31860
|
) }),
|
|
30805
31861
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 text-center mx-auto", children: [
|
|
@@ -31468,15 +32524,13 @@ var HelpView = ({
|
|
|
31468
32524
|
transition: { duration: 0.3 },
|
|
31469
32525
|
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
31470
32526
|
/* @__PURE__ */ jsx("div", { className: "bg-white px-8 py-6 shadow-sm border-b border-gray-200/80", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between relative", children: [
|
|
31471
|
-
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */
|
|
31472
|
-
|
|
32527
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsx(
|
|
32528
|
+
BackButtonMinimal,
|
|
31473
32529
|
{
|
|
31474
32530
|
onClick: handleBackClick,
|
|
31475
|
-
|
|
31476
|
-
|
|
31477
|
-
|
|
31478
|
-
/* @__PURE__ */ jsx("span", { className: "ml-2", children: "Back" })
|
|
31479
|
-
]
|
|
32531
|
+
text: "Back",
|
|
32532
|
+
size: "default",
|
|
32533
|
+
"aria-label": "Navigate back to dashboard"
|
|
31480
32534
|
}
|
|
31481
32535
|
) }),
|
|
31482
32536
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 text-center mx-auto", children: [
|
|
@@ -31506,10 +32560,7 @@ var HelpView = ({
|
|
|
31506
32560
|
/* @__PURE__ */ jsx("div", { className: "xl:col-span-3 order-1", children: /* @__PURE__ */ jsxs(Card2, { className: "shadow-lg border-gray-200 bg-white", children: [
|
|
31507
32561
|
/* @__PURE__ */ jsx(CardHeader2, { className: "bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-100 p-4 sm:p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 sm:gap-3", children: [
|
|
31508
32562
|
/* @__PURE__ */ jsx("div", { className: "p-1.5 sm:p-2 bg-blue-100 rounded-lg", children: /* @__PURE__ */ jsx(DocumentTextIcon, { className: "h-4 w-4 sm:h-5 sm:w-5 text-blue-600" }) }),
|
|
31509
|
-
/* @__PURE__ */
|
|
31510
|
-
/* @__PURE__ */ jsx(CardTitle2, { className: "text-lg sm:text-xl font-bold text-gray-900", children: "Submit Support Request" }),
|
|
31511
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs sm:text-sm text-gray-600 mt-1", children: "Direct line to our engineering team \u2022 Avg. response time: <30 minutes" })
|
|
31512
|
-
] })
|
|
32563
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-lg sm:text-xl font-bold text-gray-900", children: "Submit Support Request" }) })
|
|
31513
32564
|
] }) }),
|
|
31514
32565
|
/* @__PURE__ */ jsx(CardContent2, { className: "p-4 sm:p-6", children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4 sm:space-y-5", children: [
|
|
31515
32566
|
/* @__PURE__ */ jsxs("div", { className: "space-y-4 sm:space-y-5", children: [
|
|
@@ -32603,17 +33654,15 @@ var KPIDetailView = ({
|
|
|
32603
33654
|
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
|
|
32604
33655
|
/* @__PURE__ */ jsx("header", { className: "sticky top-0 z-10 bg-white border-b flex-shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "px-4 py-3", children: [
|
|
32605
33656
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center relative", children: [
|
|
32606
|
-
/* @__PURE__ */
|
|
32607
|
-
|
|
33657
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsx(
|
|
33658
|
+
BackButtonMinimal,
|
|
32608
33659
|
{
|
|
32609
33660
|
onClick: handleBackClick,
|
|
32610
|
-
|
|
32611
|
-
|
|
32612
|
-
|
|
32613
|
-
/* @__PURE__ */ jsx("span", { className: "ml-2", children: "Back" })
|
|
32614
|
-
]
|
|
33661
|
+
text: "Back",
|
|
33662
|
+
size: "default",
|
|
33663
|
+
"aria-label": "Navigate back to previous page"
|
|
32615
33664
|
}
|
|
32616
|
-
),
|
|
33665
|
+
) }),
|
|
32617
33666
|
/* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
32618
33667
|
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: lineInfo?.line_name || "Line" }),
|
|
32619
33668
|
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
@@ -32975,17 +34024,15 @@ var KPIsOverviewView = ({
|
|
|
32975
34024
|
if (error) {
|
|
32976
34025
|
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
|
|
32977
34026
|
/* @__PURE__ */ jsx("header", { className: "sticky top-0 z-10 bg-white border-b flex-shrink-0", children: /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center relative", children: [
|
|
32978
|
-
/* @__PURE__ */
|
|
32979
|
-
|
|
34027
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsx(
|
|
34028
|
+
BackButtonMinimal,
|
|
32980
34029
|
{
|
|
32981
34030
|
onClick: handleBackClick,
|
|
32982
|
-
|
|
32983
|
-
|
|
32984
|
-
|
|
32985
|
-
/* @__PURE__ */ jsx("span", { className: "ml-2", children: "Back" })
|
|
32986
|
-
]
|
|
34031
|
+
text: "Back",
|
|
34032
|
+
size: "default",
|
|
34033
|
+
"aria-label": "Navigate back to previous page"
|
|
32987
34034
|
}
|
|
32988
|
-
),
|
|
34035
|
+
) }),
|
|
32989
34036
|
/* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
32990
34037
|
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "Shop-floor overview" }),
|
|
32991
34038
|
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
@@ -32997,17 +34044,15 @@ var KPIsOverviewView = ({
|
|
|
32997
34044
|
if (lines.length === 0) {
|
|
32998
34045
|
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
|
|
32999
34046
|
/* @__PURE__ */ jsx("header", { className: "sticky top-0 z-10 bg-white border-b flex-shrink-0", children: /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center relative", children: [
|
|
33000
|
-
/* @__PURE__ */
|
|
33001
|
-
|
|
34047
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsx(
|
|
34048
|
+
BackButtonMinimal,
|
|
33002
34049
|
{
|
|
33003
34050
|
onClick: handleBackClick,
|
|
33004
|
-
|
|
33005
|
-
|
|
33006
|
-
|
|
33007
|
-
/* @__PURE__ */ jsx("span", { className: "ml-2", children: "Back" })
|
|
33008
|
-
]
|
|
34051
|
+
text: "Back",
|
|
34052
|
+
size: "default",
|
|
34053
|
+
"aria-label": "Navigate back to previous page"
|
|
33009
34054
|
}
|
|
33010
|
-
),
|
|
34055
|
+
) }),
|
|
33011
34056
|
/* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
33012
34057
|
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "Shop-floor overview" }),
|
|
33013
34058
|
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
@@ -33022,17 +34067,15 @@ var KPIsOverviewView = ({
|
|
|
33022
34067
|
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
|
|
33023
34068
|
/* @__PURE__ */ jsx("header", { className: "sticky top-0 z-10 bg-white border-b flex-shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "px-4 py-3", children: [
|
|
33024
34069
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center relative", children: [
|
|
33025
|
-
/* @__PURE__ */
|
|
33026
|
-
|
|
34070
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsx(
|
|
34071
|
+
BackButtonMinimal,
|
|
33027
34072
|
{
|
|
33028
34073
|
onClick: handleBackClick,
|
|
33029
|
-
|
|
33030
|
-
|
|
33031
|
-
|
|
33032
|
-
/* @__PURE__ */ jsx("span", { className: "ml-2", children: "Back" })
|
|
33033
|
-
]
|
|
34074
|
+
text: "Back",
|
|
34075
|
+
size: "default",
|
|
34076
|
+
"aria-label": "Navigate back to previous page"
|
|
33034
34077
|
}
|
|
33035
|
-
),
|
|
34078
|
+
) }),
|
|
33036
34079
|
/* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
33037
34080
|
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "Shop-floor overview" }),
|
|
33038
34081
|
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
@@ -33316,18 +34359,13 @@ var LeaderboardDetailView = memo(({
|
|
|
33316
34359
|
return /* @__PURE__ */ jsxs("div", { className: `min-h-screen bg-slate-50 flex flex-col ${className}`, children: [
|
|
33317
34360
|
/* @__PURE__ */ jsx("div", { className: "sticky top-0 z-20 bg-white shadow-sm border-b border-gray-200/80", children: /* @__PURE__ */ jsxs("div", { className: "px-3 sm:px-8 py-2 sm:py-2.5", children: [
|
|
33318
34361
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
33319
|
-
/* @__PURE__ */ jsx("div", { className: "w-auto sm:w-32", children: /* @__PURE__ */
|
|
33320
|
-
|
|
34362
|
+
/* @__PURE__ */ jsx("div", { className: "w-auto sm:w-32", children: /* @__PURE__ */ jsx(
|
|
34363
|
+
BackButtonMinimal,
|
|
33321
34364
|
{
|
|
33322
34365
|
onClick: handleBackClick,
|
|
33323
|
-
|
|
33324
|
-
|
|
33325
|
-
|
|
33326
|
-
/* @__PURE__ */ jsx("path", { d: "M19 12H5" }),
|
|
33327
|
-
/* @__PURE__ */ jsx("polyline", { points: "12 19 5 12 12 5" })
|
|
33328
|
-
] }),
|
|
33329
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs sm:text-sm font-medium", children: "Back" })
|
|
33330
|
-
]
|
|
34366
|
+
text: "Back",
|
|
34367
|
+
size: "default",
|
|
34368
|
+
"aria-label": "Navigate back to previous page"
|
|
33331
34369
|
}
|
|
33332
34370
|
) }),
|
|
33333
34371
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 sm:gap-3", children: [
|
|
@@ -34388,18 +35426,15 @@ var ShiftsView = ({
|
|
|
34388
35426
|
}, [lineConfigs, supabase, showToast]);
|
|
34389
35427
|
return /* @__PURE__ */ jsxs("div", { className: `min-h-screen bg-slate-50 ${className}`, children: [
|
|
34390
35428
|
/* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200/80 shadow-sm", children: /* @__PURE__ */ jsx("div", { className: "px-4 sm:px-8 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center relative", children: [
|
|
34391
|
-
/* @__PURE__ */
|
|
34392
|
-
|
|
35429
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsx(
|
|
35430
|
+
BackButtonMinimal,
|
|
34393
35431
|
{
|
|
34394
35432
|
onClick: () => onBackClick ? onBackClick() : window.history.back(),
|
|
34395
|
-
|
|
34396
|
-
|
|
34397
|
-
|
|
34398
|
-
/* @__PURE__ */ jsx(ArrowLeft, { className: "w-5 h-5" }),
|
|
34399
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Back" })
|
|
34400
|
-
]
|
|
35433
|
+
text: "Back",
|
|
35434
|
+
size: "default",
|
|
35435
|
+
"aria-label": "Navigate back to previous page"
|
|
34401
35436
|
}
|
|
34402
|
-
),
|
|
35437
|
+
) }),
|
|
34403
35438
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
|
|
34404
35439
|
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "Shift Management" }),
|
|
34405
35440
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-1", children: "Configure day and night shift timings and breaks for each production line" })
|
|
@@ -35297,6 +36332,7 @@ var TargetsViewUI = ({
|
|
|
35297
36332
|
onSaveLine,
|
|
35298
36333
|
onToggleBulkConfigure,
|
|
35299
36334
|
onBulkConfigure,
|
|
36335
|
+
onUpdateWorkspaceDisplayName,
|
|
35300
36336
|
// SKU props
|
|
35301
36337
|
skuEnabled = false,
|
|
35302
36338
|
skus = [],
|
|
@@ -35308,15 +36344,13 @@ var TargetsViewUI = ({
|
|
|
35308
36344
|
}
|
|
35309
36345
|
return /* @__PURE__ */ jsxs("main", { className: "min-h-screen flex-1 bg-gray-50", children: [
|
|
35310
36346
|
/* @__PURE__ */ jsx("div", { className: "bg-white px-8 py-6 shadow-sm border-b border-gray-200/80", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between relative", children: [
|
|
35311
|
-
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */
|
|
35312
|
-
|
|
36347
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsx(
|
|
36348
|
+
BackButtonMinimal,
|
|
35313
36349
|
{
|
|
35314
36350
|
onClick: onBack,
|
|
35315
|
-
|
|
35316
|
-
|
|
35317
|
-
|
|
35318
|
-
/* @__PURE__ */ jsx("span", { className: "ml-2", children: "Back" })
|
|
35319
|
-
]
|
|
36351
|
+
text: "Back",
|
|
36352
|
+
size: "default",
|
|
36353
|
+
"aria-label": "Navigate back to previous page"
|
|
35320
36354
|
}
|
|
35321
36355
|
) }),
|
|
35322
36356
|
/* @__PURE__ */ jsx("div", { className: "absolute right-0", children: /* @__PURE__ */ jsxs(
|
|
@@ -35464,7 +36498,18 @@ var TargetsViewUI = ({
|
|
|
35464
36498
|
{
|
|
35465
36499
|
className: "px-6 py-4 hover:bg-gray-50 transition-all duration-200",
|
|
35466
36500
|
children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-6 items-center", children: [
|
|
35467
|
-
/* @__PURE__ */ jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsx(
|
|
36501
|
+
/* @__PURE__ */ jsx("div", { className: "col-span-2", children: onUpdateWorkspaceDisplayName ? /* @__PURE__ */ jsx(
|
|
36502
|
+
InlineEditableText,
|
|
36503
|
+
{
|
|
36504
|
+
value: formattedName,
|
|
36505
|
+
onSave: async (newName) => {
|
|
36506
|
+
await onUpdateWorkspaceDisplayName(workspace.id, newName);
|
|
36507
|
+
},
|
|
36508
|
+
placeholder: "Workspace name",
|
|
36509
|
+
className: "font-medium text-gray-900",
|
|
36510
|
+
inputClassName: "min-w-[120px]"
|
|
36511
|
+
}
|
|
36512
|
+
) : /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: formattedName }) }),
|
|
35468
36513
|
/* @__PURE__ */ jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsxs(
|
|
35469
36514
|
"select",
|
|
35470
36515
|
{
|
|
@@ -36205,6 +37250,17 @@ var TargetsView = ({
|
|
|
36205
37250
|
router.push("/");
|
|
36206
37251
|
}
|
|
36207
37252
|
};
|
|
37253
|
+
const handleUpdateWorkspaceDisplayName = useCallback(async (workspaceId, displayName) => {
|
|
37254
|
+
try {
|
|
37255
|
+
await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
37256
|
+
await forceRefreshWorkspaceDisplayNames();
|
|
37257
|
+
toast.success("Workspace name updated successfully");
|
|
37258
|
+
} catch (error) {
|
|
37259
|
+
console.error("Error updating workspace display name:", error);
|
|
37260
|
+
toast.error("Failed to update workspace name");
|
|
37261
|
+
throw error;
|
|
37262
|
+
}
|
|
37263
|
+
}, []);
|
|
36208
37264
|
return /* @__PURE__ */ jsx(
|
|
36209
37265
|
TargetsViewUI_default,
|
|
36210
37266
|
{
|
|
@@ -36227,6 +37283,7 @@ var TargetsView = ({
|
|
|
36227
37283
|
onSaveLine: handleSaveLine,
|
|
36228
37284
|
onToggleBulkConfigure: handleToggleBulkConfigure,
|
|
36229
37285
|
onBulkConfigure: handleBulkConfigure,
|
|
37286
|
+
onUpdateWorkspaceDisplayName: handleUpdateWorkspaceDisplayName,
|
|
36230
37287
|
skuEnabled,
|
|
36231
37288
|
skus,
|
|
36232
37289
|
onUpdateSelectedSKU: updateSelectedSKU,
|
|
@@ -36325,6 +37382,14 @@ var WorkspaceDetailView = ({
|
|
|
36325
37382
|
const [usingFallbackData, setUsingFallbackData] = useState(false);
|
|
36326
37383
|
const [showIdleTime, setShowIdleTime] = useState(false);
|
|
36327
37384
|
const dashboardConfig = useDashboardConfig();
|
|
37385
|
+
const {
|
|
37386
|
+
workspace: workspaceHealth,
|
|
37387
|
+
loading: healthLoading,
|
|
37388
|
+
error: healthError
|
|
37389
|
+
} = useWorkspaceHealthById(workspaceId, {
|
|
37390
|
+
enableRealtime: true,
|
|
37391
|
+
refreshInterval: 3e4
|
|
37392
|
+
});
|
|
36328
37393
|
const {
|
|
36329
37394
|
status: prefetchStatus,
|
|
36330
37395
|
data: prefetchData,
|
|
@@ -36633,15 +37698,13 @@ var WorkspaceDetailView = ({
|
|
|
36633
37698
|
"Error: ",
|
|
36634
37699
|
error.message
|
|
36635
37700
|
] }),
|
|
36636
|
-
/* @__PURE__ */
|
|
36637
|
-
|
|
37701
|
+
/* @__PURE__ */ jsx(
|
|
37702
|
+
BackButton,
|
|
36638
37703
|
{
|
|
36639
37704
|
onClick: () => onNavigate && onNavigate("/"),
|
|
36640
|
-
|
|
36641
|
-
|
|
36642
|
-
|
|
36643
|
-
"Return to Dashboard"
|
|
36644
|
-
]
|
|
37705
|
+
text: "Return to Dashboard",
|
|
37706
|
+
size: "default",
|
|
37707
|
+
"aria-label": "Return to dashboard"
|
|
36645
37708
|
}
|
|
36646
37709
|
)
|
|
36647
37710
|
] });
|
|
@@ -36649,15 +37712,13 @@ var WorkspaceDetailView = ({
|
|
|
36649
37712
|
if (!workspace) {
|
|
36650
37713
|
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen p-8 bg-slate-50", children: [
|
|
36651
37714
|
/* @__PURE__ */ jsx("div", { className: "mb-4 text-xl text-gray-600", children: "Workspace not found" }),
|
|
36652
|
-
/* @__PURE__ */
|
|
36653
|
-
|
|
37715
|
+
/* @__PURE__ */ jsx(
|
|
37716
|
+
BackButton,
|
|
36654
37717
|
{
|
|
36655
37718
|
onClick: () => onNavigate && onNavigate("/"),
|
|
36656
|
-
|
|
36657
|
-
|
|
36658
|
-
|
|
36659
|
-
"Return to Dashboard"
|
|
36660
|
-
]
|
|
37719
|
+
text: "Return to Dashboard",
|
|
37720
|
+
size: "default",
|
|
37721
|
+
"aria-label": "Return to dashboard"
|
|
36661
37722
|
}
|
|
36662
37723
|
)
|
|
36663
37724
|
] });
|
|
@@ -36672,21 +37733,16 @@ var WorkspaceDetailView = ({
|
|
|
36672
37733
|
/* @__PURE__ */ jsxs("div", { className: "min-h-screen w-full flex flex-col bg-slate-50", children: [
|
|
36673
37734
|
/* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 px-2 sm:px-2.5 lg:px-3 py-1.5 sm:py-2 lg:py-3 flex flex-col shadow-sm bg-white", children: [
|
|
36674
37735
|
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
|
|
36675
|
-
/* @__PURE__ */
|
|
36676
|
-
|
|
37736
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsx(
|
|
37737
|
+
BackButtonMinimal,
|
|
36677
37738
|
{
|
|
36678
37739
|
onClick: handleBackNavigation,
|
|
36679
|
-
|
|
36680
|
-
|
|
36681
|
-
|
|
36682
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm sm:text-sm lg:text-base", children: previousView === "line_monthly_history" ? "Back to Line History" : returnUrl && returnUrl.includes("monthly_history") ? "Back to Line History" : returnUrl && returnUrl.includes("/kpis/") ? "Back to KPIs" : returnUrl && returnUrl.includes("/leaderboard/") ? "Back to Leaderboard" : date || shift ? "Back to Monthly History" : "Back" })
|
|
36683
|
-
]
|
|
37740
|
+
text: previousView === "line_monthly_history" ? "Back to Line History" : returnUrl && returnUrl.includes("monthly_history") ? "Back to Line History" : returnUrl && returnUrl.includes("/kpis/") ? "Back to KPIs" : returnUrl && returnUrl.includes("/leaderboard/") ? "Back to Leaderboard" : date || shift ? "Back to Monthly History" : "Back",
|
|
37741
|
+
size: "default",
|
|
37742
|
+
"aria-label": "Navigate back to previous page"
|
|
36684
37743
|
}
|
|
36685
|
-
),
|
|
36686
|
-
/* @__PURE__ */
|
|
36687
|
-
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: formattedWorkspaceName }),
|
|
36688
|
-
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
36689
|
-
] }),
|
|
37744
|
+
) }),
|
|
37745
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2", children: /* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: formattedWorkspaceName }) }),
|
|
36690
37746
|
/* @__PURE__ */ jsx("div", { className: "w-full h-8" })
|
|
36691
37747
|
] }),
|
|
36692
37748
|
activeTab !== "monthly_history" && /* @__PURE__ */ jsx("div", { className: "mt-3 bg-blue-50 px-3 py-2 rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4", children: [
|
|
@@ -36714,6 +37770,19 @@ var WorkspaceDetailView = ({
|
|
|
36714
37770
|
workspace.shift_type,
|
|
36715
37771
|
" Shift"
|
|
36716
37772
|
] })
|
|
37773
|
+
] }),
|
|
37774
|
+
workspaceHealth && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
37775
|
+
/* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
37776
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
37777
|
+
/* @__PURE__ */ jsx("div", { className: clsx(
|
|
37778
|
+
"h-1.5 w-1.5 rounded-full",
|
|
37779
|
+
workspaceHealth.status === "healthy" ? "bg-green-600" : workspaceHealth.status === "unhealthy" ? "bg-red-600" : workspaceHealth.status === "warning" ? "bg-amber-600" : "bg-gray-500"
|
|
37780
|
+
) }),
|
|
37781
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-blue-700", children: [
|
|
37782
|
+
"Last update: ",
|
|
37783
|
+
workspaceHealth.timeSinceLastUpdate
|
|
37784
|
+
] })
|
|
37785
|
+
] })
|
|
36717
37786
|
] })
|
|
36718
37787
|
] }) }),
|
|
36719
37788
|
/* @__PURE__ */ jsxs("div", { className: "mt-1 sm:mt-1.5 lg:mt-2 flex items-center justify-between", children: [
|
|
@@ -37190,17 +38259,15 @@ var SKUManagementView = () => {
|
|
|
37190
38259
|
}
|
|
37191
38260
|
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-slate-50", children: [
|
|
37192
38261
|
/* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200/80 shadow-sm", children: /* @__PURE__ */ jsx("div", { className: "px-4 sm:px-8 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center relative", children: [
|
|
37193
|
-
/* @__PURE__ */
|
|
37194
|
-
|
|
38262
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsx(
|
|
38263
|
+
BackButtonMinimal,
|
|
37195
38264
|
{
|
|
37196
38265
|
onClick: handleBack,
|
|
37197
|
-
|
|
37198
|
-
|
|
37199
|
-
|
|
37200
|
-
/* @__PURE__ */ jsx("span", { children: "Back" })
|
|
37201
|
-
]
|
|
38266
|
+
text: "Back",
|
|
38267
|
+
size: "default",
|
|
38268
|
+
"aria-label": "Navigate back to previous page"
|
|
37202
38269
|
}
|
|
37203
|
-
),
|
|
38270
|
+
) }),
|
|
37204
38271
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 text-center mx-auto", children: [
|
|
37205
38272
|
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "SKU Management" }),
|
|
37206
38273
|
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Manage Stock Keeping Units (SKUs) for production planning" })
|
|
@@ -37252,6 +38319,255 @@ var SKUManagementView = () => {
|
|
|
37252
38319
|
)
|
|
37253
38320
|
] });
|
|
37254
38321
|
};
|
|
38322
|
+
var WorkspaceHealthView = ({
|
|
38323
|
+
lineId,
|
|
38324
|
+
companyId,
|
|
38325
|
+
onNavigate,
|
|
38326
|
+
className = ""
|
|
38327
|
+
}) => {
|
|
38328
|
+
const router = useRouter();
|
|
38329
|
+
const [viewMode, setViewMode] = useState("grid");
|
|
38330
|
+
const [groupBy, setGroupBy] = useState("line");
|
|
38331
|
+
const operationalDate = getOperationalDate();
|
|
38332
|
+
const currentHour = (/* @__PURE__ */ new Date()).getHours();
|
|
38333
|
+
const isNightShift = currentHour >= 18 || currentHour < 6;
|
|
38334
|
+
const shiftType = isNightShift ? "Night" : "Day";
|
|
38335
|
+
const formatDate = (date) => {
|
|
38336
|
+
const d = new Date(date);
|
|
38337
|
+
return d.toLocaleDateString("en-IN", {
|
|
38338
|
+
month: "long",
|
|
38339
|
+
day: "numeric",
|
|
38340
|
+
year: "numeric",
|
|
38341
|
+
timeZone: "Asia/Kolkata"
|
|
38342
|
+
});
|
|
38343
|
+
};
|
|
38344
|
+
const getShiftIcon = (shift) => {
|
|
38345
|
+
return shift === "Night" ? /* @__PURE__ */ jsx(Moon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Sun, { className: "h-4 w-4" });
|
|
38346
|
+
};
|
|
38347
|
+
const {
|
|
38348
|
+
workspaces,
|
|
38349
|
+
summary,
|
|
38350
|
+
loading,
|
|
38351
|
+
error,
|
|
38352
|
+
refetch
|
|
38353
|
+
} = useWorkspaceHealth({
|
|
38354
|
+
lineId,
|
|
38355
|
+
companyId,
|
|
38356
|
+
enableRealtime: true,
|
|
38357
|
+
refreshInterval: 1e4
|
|
38358
|
+
// Refresh every 10 seconds for more responsive updates
|
|
38359
|
+
});
|
|
38360
|
+
const handleWorkspaceClick = useCallback(
|
|
38361
|
+
(workspace) => {
|
|
38362
|
+
const url = `/workspace/${workspace.workspace_id}`;
|
|
38363
|
+
if (onNavigate) {
|
|
38364
|
+
onNavigate(url);
|
|
38365
|
+
} else {
|
|
38366
|
+
router.push(url);
|
|
38367
|
+
}
|
|
38368
|
+
},
|
|
38369
|
+
[router, onNavigate]
|
|
38370
|
+
);
|
|
38371
|
+
const handleExport = useCallback(() => {
|
|
38372
|
+
const csv = [
|
|
38373
|
+
["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
|
|
38374
|
+
...workspaces.map((w) => [
|
|
38375
|
+
w.workspace_display_name || "",
|
|
38376
|
+
w.line_name || "",
|
|
38377
|
+
w.company_name || "",
|
|
38378
|
+
w.status,
|
|
38379
|
+
w.last_heartbeat,
|
|
38380
|
+
w.consecutive_misses?.toString() || "0"
|
|
38381
|
+
])
|
|
38382
|
+
].map((row) => row.join(",")).join("\n");
|
|
38383
|
+
const blob = new Blob([csv], { type: "text/csv" });
|
|
38384
|
+
const url = window.URL.createObjectURL(blob);
|
|
38385
|
+
const a = document.createElement("a");
|
|
38386
|
+
a.href = url;
|
|
38387
|
+
a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
|
|
38388
|
+
document.body.appendChild(a);
|
|
38389
|
+
a.click();
|
|
38390
|
+
document.body.removeChild(a);
|
|
38391
|
+
window.URL.revokeObjectURL(url);
|
|
38392
|
+
}, [workspaces]);
|
|
38393
|
+
const getStatusIcon = (status) => {
|
|
38394
|
+
switch (status) {
|
|
38395
|
+
case "healthy":
|
|
38396
|
+
return /* @__PURE__ */ jsx(CheckCircle, { className: "h-5 w-5 text-green-500" });
|
|
38397
|
+
case "unhealthy":
|
|
38398
|
+
return /* @__PURE__ */ jsx(XCircle, { className: "h-5 w-5 text-red-500" });
|
|
38399
|
+
case "warning":
|
|
38400
|
+
return /* @__PURE__ */ jsx(AlertTriangle, { className: "h-5 w-5 text-yellow-500" });
|
|
38401
|
+
default:
|
|
38402
|
+
return /* @__PURE__ */ jsx(Activity, { className: "h-5 w-5 text-gray-400" });
|
|
38403
|
+
}
|
|
38404
|
+
};
|
|
38405
|
+
const getUptimeColor = (percentage) => {
|
|
38406
|
+
if (percentage >= 99) return "text-green-600 dark:text-green-400";
|
|
38407
|
+
if (percentage >= 95) return "text-yellow-600 dark:text-yellow-400";
|
|
38408
|
+
return "text-red-600 dark:text-red-400";
|
|
38409
|
+
};
|
|
38410
|
+
if (loading && !summary) {
|
|
38411
|
+
return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 p-4", children: /* @__PURE__ */ jsx(LoadingState, {}) });
|
|
38412
|
+
}
|
|
38413
|
+
if (error) {
|
|
38414
|
+
return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 p-4", children: /* @__PURE__ */ jsx("div", { className: "max-w-7xl mx-auto", children: /* @__PURE__ */ jsx(Card2, { className: "border-red-200 dark:border-red-800", children: /* @__PURE__ */ jsxs(CardContent2, { className: "p-8 text-center", children: [
|
|
38415
|
+
/* @__PURE__ */ jsx(XCircle, { className: "h-12 w-12 text-red-500 mx-auto mb-4" }),
|
|
38416
|
+
/* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2", children: "Error Loading Health Status" }),
|
|
38417
|
+
/* @__PURE__ */ jsx("p", { className: "text-gray-600 dark:text-gray-400 mb-4", children: error.message || "Unable to load workspace health status" }),
|
|
38418
|
+
/* @__PURE__ */ jsx(
|
|
38419
|
+
"button",
|
|
38420
|
+
{
|
|
38421
|
+
onClick: () => refetch(),
|
|
38422
|
+
className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors",
|
|
38423
|
+
children: "Try Again"
|
|
38424
|
+
}
|
|
38425
|
+
)
|
|
38426
|
+
] }) }) }) });
|
|
38427
|
+
}
|
|
38428
|
+
return /* @__PURE__ */ jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
|
|
38429
|
+
/* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 px-2 sm:px-2.5 lg:px-3 py-1.5 sm:py-2 lg:py-3 flex flex-col shadow-sm bg-white", children: [
|
|
38430
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
|
|
38431
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsx(
|
|
38432
|
+
BackButtonMinimal,
|
|
38433
|
+
{
|
|
38434
|
+
onClick: () => router.push("/"),
|
|
38435
|
+
text: "Back",
|
|
38436
|
+
size: "default",
|
|
38437
|
+
"aria-label": "Navigate back to dashboard"
|
|
38438
|
+
}
|
|
38439
|
+
) }),
|
|
38440
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
38441
|
+
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "System Health" }),
|
|
38442
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
|
|
38443
|
+
/* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
|
|
38444
|
+
/* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
|
|
38445
|
+
] })
|
|
38446
|
+
] }) }),
|
|
38447
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute right-0 flex gap-2", children: [
|
|
38448
|
+
/* @__PURE__ */ jsx(
|
|
38449
|
+
"button",
|
|
38450
|
+
{
|
|
38451
|
+
onClick: () => {
|
|
38452
|
+
refetch();
|
|
38453
|
+
},
|
|
38454
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
38455
|
+
"aria-label": "Refresh",
|
|
38456
|
+
children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-5 w-5" })
|
|
38457
|
+
}
|
|
38458
|
+
),
|
|
38459
|
+
/* @__PURE__ */ jsx(
|
|
38460
|
+
"button",
|
|
38461
|
+
{
|
|
38462
|
+
onClick: handleExport,
|
|
38463
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
38464
|
+
"aria-label": "Export CSV",
|
|
38465
|
+
children: /* @__PURE__ */ jsx(Download, { className: "h-5 w-5" })
|
|
38466
|
+
}
|
|
38467
|
+
)
|
|
38468
|
+
] }),
|
|
38469
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-8" })
|
|
38470
|
+
] }),
|
|
38471
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3 bg-blue-50 px-3 py-2 rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4", children: [
|
|
38472
|
+
/* @__PURE__ */ jsx("div", { className: "text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsx(LiveTimer, {}) }),
|
|
38473
|
+
/* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
38474
|
+
/* @__PURE__ */ jsx("span", { className: "text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
|
|
38475
|
+
/* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
38476
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
38477
|
+
/* @__PURE__ */ jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
|
|
38478
|
+
/* @__PURE__ */ jsxs("span", { className: "text-base font-medium text-blue-600", children: [
|
|
38479
|
+
shiftType,
|
|
38480
|
+
" Shift"
|
|
38481
|
+
] })
|
|
38482
|
+
] })
|
|
38483
|
+
] }) })
|
|
38484
|
+
] }),
|
|
38485
|
+
/* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
38486
|
+
summary && /* @__PURE__ */ jsxs(
|
|
38487
|
+
motion.div,
|
|
38488
|
+
{
|
|
38489
|
+
initial: { opacity: 0, y: 20 },
|
|
38490
|
+
animate: { opacity: 1, y: 0 },
|
|
38491
|
+
transition: { duration: 0.3, delay: 0.1 },
|
|
38492
|
+
className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
|
|
38493
|
+
children: [
|
|
38494
|
+
/* @__PURE__ */ jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2", children: [
|
|
38495
|
+
/* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "Overall System Status" }) }),
|
|
38496
|
+
/* @__PURE__ */ jsxs(CardContent2, { children: [
|
|
38497
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
|
|
38498
|
+
/* @__PURE__ */ jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
|
|
38499
|
+
summary.uptimePercentage.toFixed(1),
|
|
38500
|
+
"%"
|
|
38501
|
+
] }),
|
|
38502
|
+
summary.uptimePercentage >= 99 ? /* @__PURE__ */ jsx(TrendingUp, { className: "h-5 w-5 text-green-500" }) : /* @__PURE__ */ jsx(TrendingDown, { className: "h-5 w-5 text-red-500" })
|
|
38503
|
+
] }),
|
|
38504
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: [
|
|
38505
|
+
summary.healthyWorkspaces,
|
|
38506
|
+
" of ",
|
|
38507
|
+
summary.totalWorkspaces,
|
|
38508
|
+
" workspaces healthy"
|
|
38509
|
+
] })
|
|
38510
|
+
] })
|
|
38511
|
+
] }),
|
|
38512
|
+
/* @__PURE__ */ jsxs(Card2, { children: [
|
|
38513
|
+
/* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38514
|
+
getStatusIcon("healthy"),
|
|
38515
|
+
"Healthy"
|
|
38516
|
+
] }) }),
|
|
38517
|
+
/* @__PURE__ */ jsxs(CardContent2, { children: [
|
|
38518
|
+
/* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-green-600 dark:text-green-400", children: summary.healthyWorkspaces }),
|
|
38519
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Operating normally" })
|
|
38520
|
+
] })
|
|
38521
|
+
] }),
|
|
38522
|
+
/* @__PURE__ */ jsxs(Card2, { children: [
|
|
38523
|
+
/* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38524
|
+
getStatusIcon("warning"),
|
|
38525
|
+
"Warning"
|
|
38526
|
+
] }) }),
|
|
38527
|
+
/* @__PURE__ */ jsxs(CardContent2, { children: [
|
|
38528
|
+
/* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-yellow-600 dark:text-yellow-400", children: summary.warningWorkspaces }),
|
|
38529
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Delayed updates" })
|
|
38530
|
+
] })
|
|
38531
|
+
] }),
|
|
38532
|
+
/* @__PURE__ */ jsxs(Card2, { children: [
|
|
38533
|
+
/* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38534
|
+
getStatusIcon("unhealthy"),
|
|
38535
|
+
"Unhealthy"
|
|
38536
|
+
] }) }),
|
|
38537
|
+
/* @__PURE__ */ jsxs(CardContent2, { children: [
|
|
38538
|
+
/* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-red-600 dark:text-red-400", children: summary.unhealthyWorkspaces }),
|
|
38539
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
|
|
38540
|
+
] })
|
|
38541
|
+
] })
|
|
38542
|
+
]
|
|
38543
|
+
}
|
|
38544
|
+
),
|
|
38545
|
+
/* @__PURE__ */ jsx(
|
|
38546
|
+
motion.div,
|
|
38547
|
+
{
|
|
38548
|
+
initial: { opacity: 0, y: 20 },
|
|
38549
|
+
animate: { opacity: 1, y: 0 },
|
|
38550
|
+
transition: { duration: 0.3, delay: 0.2 },
|
|
38551
|
+
children: /* @__PURE__ */ jsx(
|
|
38552
|
+
HealthStatusGrid,
|
|
38553
|
+
{
|
|
38554
|
+
workspaces,
|
|
38555
|
+
onWorkspaceClick: handleWorkspaceClick,
|
|
38556
|
+
viewMode,
|
|
38557
|
+
showFilters: true,
|
|
38558
|
+
groupBy
|
|
38559
|
+
}
|
|
38560
|
+
)
|
|
38561
|
+
}
|
|
38562
|
+
)
|
|
38563
|
+
] })
|
|
38564
|
+
] });
|
|
38565
|
+
};
|
|
38566
|
+
var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
|
|
38567
|
+
redirectTo: "/login",
|
|
38568
|
+
requireAuth: true
|
|
38569
|
+
});
|
|
38570
|
+
var AuthenticatedWorkspaceHealthView = WorkspaceHealthView;
|
|
37255
38571
|
var S3Service = class {
|
|
37256
38572
|
constructor(config) {
|
|
37257
38573
|
this.s3Client = null;
|
|
@@ -37707,4 +39023,4 @@ var streamProxyConfig = {
|
|
|
37707
39023
|
}
|
|
37708
39024
|
};
|
|
37709
39025
|
|
|
37710
|
-
export { ACTION_NAMES, AIAgentView_default as AIAgentView, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, BarChart, BaseHistoryCalendar, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, CongratulationsOverlay, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_SHIFT_CONFIG, 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, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HelpView_default as HelpView, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, 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, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, MainLayout, MetricCard_default as MetricCard, NoWorkspaceData, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, TargetWorkspaceGrid, TargetsView_default as TargetsView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TimeDisplay_default as TimeDisplay, TimePickerDropdown, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createStreamProxyHandler, createSupabaseClient, createThrottledReload, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useActiveBreaks, useAllWorkspaceMetrics, useAnalyticsConfig, useAudioService, useAuth, useAuthConfig, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTheme, useThemeConfig, useThreads, useTicketHistory, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, videoPrefetchManager, videoPreloader, whatsappService, withAuth, withRegistry, workspaceService };
|
|
39026
|
+
export { ACTION_NAMES, AIAgentView_default as AIAgentView, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedWorkspaceHealthView, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, CompactWorkspaceHealthCard, CongratulationsOverlay, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_SHIFT_CONFIG, 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, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, InlineEditableText, 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, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, MainLayout, MetricCard_default as MetricCard, NoWorkspaceData, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, TargetWorkspaceGrid, TargetsView_default as TargetsView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TimeDisplay_default as TimeDisplay, TimePickerDropdown, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createStreamProxyHandler, createSupabaseClient, createThrottledReload, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useActiveBreaks, useAllWorkspaceMetrics, useAnalyticsConfig, useAudioService, useAuth, useAuthConfig, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTheme, useThemeConfig, useThreads, useTicketHistory, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealth, useWorkspaceHealthById, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, videoPrefetchManager, videoPreloader, whatsappService, withAuth, withRegistry, workspaceHealthService, workspaceService };
|