@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.js
CHANGED
|
@@ -7,7 +7,6 @@ var dateFnsTz = require('date-fns-tz');
|
|
|
7
7
|
var dateFns = require('date-fns');
|
|
8
8
|
var mixpanel = require('mixpanel-browser');
|
|
9
9
|
var events = require('events');
|
|
10
|
-
var clientS3 = require('@aws-sdk/client-s3');
|
|
11
10
|
var supabaseJs = require('@supabase/supabase-js');
|
|
12
11
|
var Hls2 = require('hls.js');
|
|
13
12
|
var useSWR = require('swr');
|
|
@@ -18,12 +17,14 @@ var reactSlot = require('@radix-ui/react-slot');
|
|
|
18
17
|
var lucideReact = require('lucide-react');
|
|
19
18
|
var reactDayPicker = require('react-day-picker');
|
|
20
19
|
var outline = require('@heroicons/react/24/outline');
|
|
20
|
+
var solid = require('@heroicons/react/24/solid');
|
|
21
21
|
var html2canvas = require('html2canvas');
|
|
22
22
|
var jsPDF = require('jspdf');
|
|
23
23
|
var SelectPrimitive = require('@radix-ui/react-select');
|
|
24
24
|
var videojs = require('video.js');
|
|
25
25
|
require('video.js/dist/video-js.css');
|
|
26
26
|
var sonner = require('sonner');
|
|
27
|
+
var clientS3 = require('@aws-sdk/client-s3');
|
|
27
28
|
var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
|
|
28
29
|
var stream = require('stream');
|
|
29
30
|
|
|
@@ -1703,6 +1704,25 @@ var workspaceService = {
|
|
|
1703
1704
|
this._workspaceDisplayNamesCache.clear();
|
|
1704
1705
|
this._cacheTimestamp = 0;
|
|
1705
1706
|
},
|
|
1707
|
+
/**
|
|
1708
|
+
* Updates the display name for a workspace
|
|
1709
|
+
* @param workspaceId - The workspace UUID
|
|
1710
|
+
* @param displayName - The new display name
|
|
1711
|
+
* @returns Promise<void>
|
|
1712
|
+
*/
|
|
1713
|
+
async updateWorkspaceDisplayName(workspaceId, displayName) {
|
|
1714
|
+
const supabase = _getSupabaseInstance();
|
|
1715
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1716
|
+
const config = _getDashboardConfigInstance();
|
|
1717
|
+
const dbConfig = config?.databaseConfig;
|
|
1718
|
+
const workspacesTable = getTable3(dbConfig, "workspaces");
|
|
1719
|
+
const { error } = await supabase.from(workspacesTable).update({ display_name: displayName.trim() }).eq("id", workspaceId);
|
|
1720
|
+
if (error) {
|
|
1721
|
+
console.error(`Error updating workspace display name for ${workspaceId}:`, error);
|
|
1722
|
+
throw error;
|
|
1723
|
+
}
|
|
1724
|
+
this.clearWorkspaceDisplayNamesCache();
|
|
1725
|
+
},
|
|
1706
1726
|
async updateWorkspaceAction(updates) {
|
|
1707
1727
|
const supabase = _getSupabaseInstance();
|
|
1708
1728
|
if (!supabase) throw new Error("Supabase client not initialized");
|
|
@@ -1846,6 +1866,209 @@ var workspaceService = {
|
|
|
1846
1866
|
}
|
|
1847
1867
|
}
|
|
1848
1868
|
};
|
|
1869
|
+
var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
1870
|
+
constructor() {
|
|
1871
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1872
|
+
this.cacheExpiryMs = 30 * 1e3;
|
|
1873
|
+
}
|
|
1874
|
+
// 30 seconds cache
|
|
1875
|
+
static getInstance() {
|
|
1876
|
+
if (!_WorkspaceHealthService.instance) {
|
|
1877
|
+
_WorkspaceHealthService.instance = new _WorkspaceHealthService();
|
|
1878
|
+
}
|
|
1879
|
+
return _WorkspaceHealthService.instance;
|
|
1880
|
+
}
|
|
1881
|
+
getFromCache(key) {
|
|
1882
|
+
const cached = this.cache.get(key);
|
|
1883
|
+
if (cached && Date.now() - cached.timestamp < this.cacheExpiryMs) {
|
|
1884
|
+
return cached.data;
|
|
1885
|
+
}
|
|
1886
|
+
this.cache.delete(key);
|
|
1887
|
+
return null;
|
|
1888
|
+
}
|
|
1889
|
+
setCache(key, data) {
|
|
1890
|
+
this.cache.set(key, { data, timestamp: Date.now() });
|
|
1891
|
+
}
|
|
1892
|
+
async getWorkspaceHealthStatus(options = {}) {
|
|
1893
|
+
const supabase = _getSupabaseInstance();
|
|
1894
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1895
|
+
let query = supabase.from("workspace_health_status").select("*").order("workspace_display_name", { ascending: true });
|
|
1896
|
+
if (options.lineId) {
|
|
1897
|
+
query = query.eq("line_id", options.lineId);
|
|
1898
|
+
}
|
|
1899
|
+
if (options.companyId) {
|
|
1900
|
+
query = query.eq("company_id", options.companyId);
|
|
1901
|
+
}
|
|
1902
|
+
const { data, error } = await query;
|
|
1903
|
+
if (error) {
|
|
1904
|
+
console.error("Error fetching workspace health status:", error);
|
|
1905
|
+
throw error;
|
|
1906
|
+
}
|
|
1907
|
+
const processedData = (data || []).map((item) => this.processHealthStatus(item));
|
|
1908
|
+
let filteredData = processedData;
|
|
1909
|
+
try {
|
|
1910
|
+
const { data: enabledWorkspaces, error: workspaceError } = await supabase.from("workspaces").select("workspace_id, display_name").eq("enable", true);
|
|
1911
|
+
if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length > 0) {
|
|
1912
|
+
const enabledWorkspaceNames = /* @__PURE__ */ new Set();
|
|
1913
|
+
enabledWorkspaces.forEach((w) => {
|
|
1914
|
+
if (w.workspace_id) enabledWorkspaceNames.add(w.workspace_id);
|
|
1915
|
+
if (w.display_name) enabledWorkspaceNames.add(w.display_name);
|
|
1916
|
+
});
|
|
1917
|
+
filteredData = filteredData.filter((item) => {
|
|
1918
|
+
const displayName = item.workspace_display_name || "";
|
|
1919
|
+
return enabledWorkspaceNames.has(displayName);
|
|
1920
|
+
});
|
|
1921
|
+
} else if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length === 0) {
|
|
1922
|
+
return [];
|
|
1923
|
+
} else if (workspaceError) {
|
|
1924
|
+
console.error("Error fetching enabled workspaces:", workspaceError);
|
|
1925
|
+
}
|
|
1926
|
+
} catch (e) {
|
|
1927
|
+
console.error("Error filtering workspaces:", e);
|
|
1928
|
+
}
|
|
1929
|
+
if (options.status) {
|
|
1930
|
+
filteredData = filteredData.filter((item) => item.status === options.status);
|
|
1931
|
+
}
|
|
1932
|
+
if (options.searchTerm) {
|
|
1933
|
+
const searchLower = options.searchTerm.toLowerCase();
|
|
1934
|
+
filteredData = filteredData.filter(
|
|
1935
|
+
(item) => item.workspace_display_name?.toLowerCase().includes(searchLower) || item.line_name?.toLowerCase().includes(searchLower) || item.company_name?.toLowerCase().includes(searchLower)
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
if (options.sortBy) {
|
|
1939
|
+
filteredData.sort((a, b) => {
|
|
1940
|
+
let compareValue = 0;
|
|
1941
|
+
switch (options.sortBy) {
|
|
1942
|
+
case "name":
|
|
1943
|
+
compareValue = (a.workspace_display_name || "").localeCompare(b.workspace_display_name || "");
|
|
1944
|
+
break;
|
|
1945
|
+
case "status":
|
|
1946
|
+
compareValue = this.getStatusPriority(a.status) - this.getStatusPriority(b.status);
|
|
1947
|
+
break;
|
|
1948
|
+
case "lastUpdate":
|
|
1949
|
+
compareValue = new Date(b.last_heartbeat).getTime() - new Date(a.last_heartbeat).getTime();
|
|
1950
|
+
break;
|
|
1951
|
+
}
|
|
1952
|
+
return options.sortOrder === "desc" ? -compareValue : compareValue;
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
return filteredData;
|
|
1956
|
+
}
|
|
1957
|
+
async getWorkspaceHealthById(workspaceId) {
|
|
1958
|
+
const cacheKey = `health-${workspaceId}`;
|
|
1959
|
+
const cached = this.getFromCache(cacheKey);
|
|
1960
|
+
if (cached) return cached;
|
|
1961
|
+
const supabase = _getSupabaseInstance();
|
|
1962
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1963
|
+
const { data, error } = await supabase.from("workspace_health_status").select("*").eq("workspace_id", workspaceId).single();
|
|
1964
|
+
if (error) {
|
|
1965
|
+
if (error.code === "PGRST116") {
|
|
1966
|
+
return null;
|
|
1967
|
+
}
|
|
1968
|
+
console.error("Error fetching workspace health:", error);
|
|
1969
|
+
throw error;
|
|
1970
|
+
}
|
|
1971
|
+
const processedData = data ? this.processHealthStatus(data) : null;
|
|
1972
|
+
if (processedData) {
|
|
1973
|
+
this.setCache(cacheKey, processedData);
|
|
1974
|
+
}
|
|
1975
|
+
return processedData;
|
|
1976
|
+
}
|
|
1977
|
+
async getHealthSummary(lineId, companyId) {
|
|
1978
|
+
this.clearCache();
|
|
1979
|
+
const workspaces = await this.getWorkspaceHealthStatus({ lineId, companyId });
|
|
1980
|
+
const totalWorkspaces = workspaces.length;
|
|
1981
|
+
const healthyWorkspaces = workspaces.filter((w) => w.status === "healthy").length;
|
|
1982
|
+
const unhealthyWorkspaces = workspaces.filter((w) => w.status === "unhealthy").length;
|
|
1983
|
+
const warningWorkspaces = workspaces.filter((w) => w.status === "warning").length;
|
|
1984
|
+
const uptimePercentage = totalWorkspaces > 0 ? healthyWorkspaces / totalWorkspaces * 100 : 0;
|
|
1985
|
+
return {
|
|
1986
|
+
totalWorkspaces,
|
|
1987
|
+
healthyWorkspaces,
|
|
1988
|
+
unhealthyWorkspaces,
|
|
1989
|
+
warningWorkspaces,
|
|
1990
|
+
uptimePercentage,
|
|
1991
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
async getHealthMetrics(workspaceId, startDate, endDate) {
|
|
1995
|
+
const supabase = _getSupabaseInstance();
|
|
1996
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1997
|
+
return {
|
|
1998
|
+
avgResponseTime: 250,
|
|
1999
|
+
totalDowntime: 0,
|
|
2000
|
+
incidentCount: 0,
|
|
2001
|
+
mttr: 0
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
processHealthStatus(data) {
|
|
2005
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
2006
|
+
const lastHeartbeat = new Date(data.last_heartbeat);
|
|
2007
|
+
const minutesSinceUpdate = Math.floor((now2.getTime() - lastHeartbeat.getTime()) / (1e3 * 60));
|
|
2008
|
+
let status = "unknown";
|
|
2009
|
+
let isStale = false;
|
|
2010
|
+
if (data.is_healthy) {
|
|
2011
|
+
if (minutesSinceUpdate < 3) {
|
|
2012
|
+
status = "healthy";
|
|
2013
|
+
} else if (minutesSinceUpdate < 5) {
|
|
2014
|
+
status = "warning";
|
|
2015
|
+
isStale = true;
|
|
2016
|
+
} else {
|
|
2017
|
+
status = "unhealthy";
|
|
2018
|
+
isStale = true;
|
|
2019
|
+
}
|
|
2020
|
+
} else {
|
|
2021
|
+
status = "unhealthy";
|
|
2022
|
+
if (minutesSinceUpdate > 5) {
|
|
2023
|
+
isStale = true;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
const timeSinceLastUpdate = dateFns.formatDistanceToNow(lastHeartbeat, { addSuffix: true });
|
|
2027
|
+
return {
|
|
2028
|
+
...data,
|
|
2029
|
+
status,
|
|
2030
|
+
timeSinceLastUpdate,
|
|
2031
|
+
isStale
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
getStatusPriority(status) {
|
|
2035
|
+
const priorities = {
|
|
2036
|
+
unhealthy: 0,
|
|
2037
|
+
warning: 1,
|
|
2038
|
+
unknown: 2,
|
|
2039
|
+
healthy: 3
|
|
2040
|
+
};
|
|
2041
|
+
return priorities[status];
|
|
2042
|
+
}
|
|
2043
|
+
subscribeToHealthUpdates(callback, filters) {
|
|
2044
|
+
const supabase = _getSupabaseInstance();
|
|
2045
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
2046
|
+
let subscription = supabase.channel("workspace-health-updates").on(
|
|
2047
|
+
"postgres_changes",
|
|
2048
|
+
{
|
|
2049
|
+
event: "*",
|
|
2050
|
+
schema: "public",
|
|
2051
|
+
table: "workspace_health_status"
|
|
2052
|
+
},
|
|
2053
|
+
(payload) => {
|
|
2054
|
+
if (payload.new) {
|
|
2055
|
+
const newData = payload.new;
|
|
2056
|
+
if (filters?.lineId && newData.line_id !== filters.lineId) return;
|
|
2057
|
+
if (filters?.companyId && newData.company_id !== filters.companyId) return;
|
|
2058
|
+
this.clearCache();
|
|
2059
|
+
callback(newData);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
).subscribe();
|
|
2063
|
+
return () => {
|
|
2064
|
+
subscription.unsubscribe();
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
clearCache() {
|
|
2068
|
+
this.cache.clear();
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
var workspaceHealthService = WorkspaceHealthService.getInstance();
|
|
1849
2072
|
|
|
1850
2073
|
// src/lib/services/skuService.ts
|
|
1851
2074
|
var getTable4 = (dbConfig, tableName) => {
|
|
@@ -2675,6 +2898,17 @@ var TicketHistoryService = class {
|
|
|
2675
2898
|
}
|
|
2676
2899
|
return data;
|
|
2677
2900
|
}
|
|
2901
|
+
/**
|
|
2902
|
+
* Update ticket serviced status
|
|
2903
|
+
*/
|
|
2904
|
+
static async updateTicketServicedStatus(ticketId, serviced) {
|
|
2905
|
+
const supabase = _getSupabaseInstance();
|
|
2906
|
+
const { data, error } = await supabase.from("support_ticket_history").update({ serviced, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", ticketId).select().single();
|
|
2907
|
+
if (error) {
|
|
2908
|
+
throw new Error(`Failed to update ticket serviced status: ${error.message}`);
|
|
2909
|
+
}
|
|
2910
|
+
return data;
|
|
2911
|
+
}
|
|
2678
2912
|
};
|
|
2679
2913
|
|
|
2680
2914
|
// src/lib/services/audioService.ts
|
|
@@ -3229,6 +3463,14 @@ function parseS3Uri(s3Uri, sopCategories) {
|
|
|
3229
3463
|
return null;
|
|
3230
3464
|
}
|
|
3231
3465
|
}
|
|
3466
|
+
function shuffleArray(array) {
|
|
3467
|
+
const shuffled = [...array];
|
|
3468
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
3469
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
3470
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
3471
|
+
}
|
|
3472
|
+
return shuffled;
|
|
3473
|
+
}
|
|
3232
3474
|
|
|
3233
3475
|
// src/lib/cache/clipsCache.ts
|
|
3234
3476
|
var LRUCache = class _LRUCache {
|
|
@@ -3741,300 +3983,321 @@ if (typeof window !== "undefined") {
|
|
|
3741
3983
|
});
|
|
3742
3984
|
});
|
|
3743
3985
|
}
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3986
|
+
var getSupabaseClient = () => {
|
|
3987
|
+
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
3988
|
+
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
3989
|
+
if (!url || !key) {
|
|
3990
|
+
throw new Error("Supabase configuration missing");
|
|
3991
|
+
}
|
|
3992
|
+
return supabaseJs.createClient(url, key);
|
|
3993
|
+
};
|
|
3994
|
+
var getAuthToken = async () => {
|
|
3995
|
+
try {
|
|
3996
|
+
const supabase = getSupabaseClient();
|
|
3997
|
+
const { data: { session } } = await supabase.auth.getSession();
|
|
3998
|
+
return session?.access_token || null;
|
|
3999
|
+
} catch (error) {
|
|
4000
|
+
console.error("[S3ClipsAPIClient] Error getting auth token:", error);
|
|
4001
|
+
return null;
|
|
4002
|
+
}
|
|
4003
|
+
};
|
|
4004
|
+
var S3ClipsAPIClient = class {
|
|
4005
|
+
constructor(sopCategories) {
|
|
4006
|
+
this.baseUrl = "/api/clips";
|
|
4007
|
+
this.requestCache = /* @__PURE__ */ new Map();
|
|
4008
|
+
this.sopCategories = sopCategories;
|
|
4009
|
+
console.log("[S3ClipsAPIClient] \u2705 Initialized - Using secure API routes (no direct S3 access)");
|
|
3753
4010
|
}
|
|
3754
4011
|
/**
|
|
3755
|
-
*
|
|
4012
|
+
* Fetch with authentication and error handling
|
|
3756
4013
|
*/
|
|
3757
|
-
async
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
4014
|
+
async fetchWithAuth(endpoint, body) {
|
|
4015
|
+
const token = await getAuthToken();
|
|
4016
|
+
if (!token) {
|
|
4017
|
+
throw new Error("Authentication required");
|
|
4018
|
+
}
|
|
4019
|
+
const response = await fetch(endpoint, {
|
|
4020
|
+
method: "POST",
|
|
4021
|
+
headers: {
|
|
4022
|
+
"Authorization": `Bearer ${token}`,
|
|
4023
|
+
"Content-Type": "application/json"
|
|
4024
|
+
},
|
|
4025
|
+
body: JSON.stringify(body)
|
|
4026
|
+
});
|
|
4027
|
+
if (!response.ok) {
|
|
4028
|
+
const error = await response.json().catch(() => ({ error: "Request failed" }));
|
|
4029
|
+
throw new Error(error.error || `API error: ${response.status}`);
|
|
4030
|
+
}
|
|
4031
|
+
return response.json();
|
|
4032
|
+
}
|
|
4033
|
+
/**
|
|
4034
|
+
* Deduplicate requests to prevent multiple API calls
|
|
4035
|
+
*/
|
|
4036
|
+
async deduplicate(key, factory) {
|
|
4037
|
+
if (this.requestCache.has(key)) {
|
|
4038
|
+
console.log(`[S3ClipsAPIClient] Deduplicating request: ${key}`);
|
|
4039
|
+
return this.requestCache.get(key);
|
|
3763
4040
|
}
|
|
3764
|
-
console.log(`[${logPrefix}] Creating new request for key: ${key}`);
|
|
3765
4041
|
const promise = factory().finally(() => {
|
|
3766
|
-
this.
|
|
3767
|
-
console.log(`[${logPrefix}] Completed and cleaned up request: ${key}`);
|
|
4042
|
+
this.requestCache.delete(key);
|
|
3768
4043
|
});
|
|
3769
|
-
this.
|
|
4044
|
+
this.requestCache.set(key, promise);
|
|
3770
4045
|
return promise;
|
|
3771
4046
|
}
|
|
3772
4047
|
/**
|
|
3773
|
-
*
|
|
4048
|
+
* List S3 clips
|
|
3774
4049
|
*/
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
this.
|
|
4050
|
+
async listS3Clips(params) {
|
|
4051
|
+
const cacheKey = `list:${JSON.stringify(params)}`;
|
|
4052
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4053
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4054
|
+
action: "list",
|
|
4055
|
+
workspaceId: params.workspaceId,
|
|
4056
|
+
date: params.date,
|
|
4057
|
+
shift: params.shiftId,
|
|
4058
|
+
maxKeys: params.maxKeys
|
|
4059
|
+
});
|
|
4060
|
+
return response.clips.map((clip) => clip.originalUri);
|
|
4061
|
+
});
|
|
3778
4062
|
}
|
|
3779
4063
|
/**
|
|
3780
|
-
* Get
|
|
4064
|
+
* Get clip counts
|
|
3781
4065
|
*/
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
4066
|
+
async getClipCounts(workspaceId, date, shiftId) {
|
|
4067
|
+
const cacheKey = `counts:${workspaceId}:${date}:${shiftId}`;
|
|
4068
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4069
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4070
|
+
action: "count",
|
|
4071
|
+
workspaceId,
|
|
4072
|
+
date,
|
|
4073
|
+
shift: shiftId.toString()
|
|
4074
|
+
});
|
|
4075
|
+
return response.counts;
|
|
4076
|
+
});
|
|
4077
|
+
}
|
|
4078
|
+
/**
|
|
4079
|
+
* Get clip counts with index (for compatibility)
|
|
4080
|
+
*/
|
|
4081
|
+
async getClipCountsWithIndex(workspaceId, date, shiftId) {
|
|
4082
|
+
const counts = await this.getClipCounts(workspaceId, date, shiftId.toString());
|
|
4083
|
+
const videoIndex = {
|
|
4084
|
+
byCategory: /* @__PURE__ */ new Map(),
|
|
4085
|
+
allVideos: [],
|
|
4086
|
+
counts,
|
|
4087
|
+
workspaceId,
|
|
4088
|
+
date,
|
|
4089
|
+
shiftId: shiftId.toString(),
|
|
4090
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
3786
4091
|
};
|
|
4092
|
+
return { counts, videoIndex };
|
|
3787
4093
|
}
|
|
3788
4094
|
/**
|
|
3789
|
-
*
|
|
4095
|
+
* Get metadata for a video
|
|
3790
4096
|
*/
|
|
3791
|
-
|
|
3792
|
-
const
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
4097
|
+
async getMetadata(playlistUri) {
|
|
4098
|
+
const cacheKey = `metadata:${playlistUri}`;
|
|
4099
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4100
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4101
|
+
action: "metadata",
|
|
4102
|
+
playlistUri
|
|
4103
|
+
});
|
|
4104
|
+
return response.metadata;
|
|
4105
|
+
});
|
|
4106
|
+
}
|
|
4107
|
+
/**
|
|
4108
|
+
* Get metadata cycle time
|
|
4109
|
+
*/
|
|
4110
|
+
async getMetadataCycleTime(playlistUri) {
|
|
4111
|
+
const metadata = await this.getMetadata(playlistUri);
|
|
4112
|
+
return metadata?.cycle_time_seconds || null;
|
|
4113
|
+
}
|
|
4114
|
+
/**
|
|
4115
|
+
* Get first clip for category
|
|
4116
|
+
*/
|
|
4117
|
+
async getFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4118
|
+
const cacheKey = `first:${workspaceId}:${date}:${shiftId}:${category}`;
|
|
4119
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4120
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4121
|
+
action: "first",
|
|
4122
|
+
workspaceId,
|
|
4123
|
+
date,
|
|
4124
|
+
shift: shiftId.toString(),
|
|
4125
|
+
category,
|
|
4126
|
+
sopCategories: this.sopCategories
|
|
4127
|
+
});
|
|
4128
|
+
return response.video;
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
4131
|
+
/**
|
|
4132
|
+
* Get clip by index
|
|
4133
|
+
*/
|
|
4134
|
+
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4135
|
+
const cacheKey = `by-index:${workspaceId}:${date}:${shiftId}:${category}:${index}`;
|
|
4136
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4137
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4138
|
+
action: "by-index",
|
|
4139
|
+
workspaceId,
|
|
4140
|
+
date,
|
|
4141
|
+
shift: shiftId.toString(),
|
|
4142
|
+
category,
|
|
4143
|
+
index,
|
|
4144
|
+
sopCategories: this.sopCategories
|
|
4145
|
+
});
|
|
4146
|
+
const video = response.video;
|
|
4147
|
+
if (video && includeMetadata && video.originalUri) {
|
|
4148
|
+
try {
|
|
4149
|
+
const metadata = await this.getMetadata(video.originalUri);
|
|
4150
|
+
if (metadata) {
|
|
4151
|
+
video.cycle_time_seconds = metadata.cycle_time_seconds;
|
|
4152
|
+
video.creation_timestamp = metadata.creation_timestamp;
|
|
4153
|
+
}
|
|
4154
|
+
} catch (error) {
|
|
4155
|
+
console.warn("[S3ClipsAPIClient] Failed to fetch metadata:", error);
|
|
4156
|
+
}
|
|
3801
4157
|
}
|
|
3802
|
-
|
|
3803
|
-
}
|
|
4158
|
+
return video;
|
|
4159
|
+
});
|
|
4160
|
+
}
|
|
4161
|
+
/**
|
|
4162
|
+
* Get videos page with pagination
|
|
4163
|
+
*/
|
|
4164
|
+
async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
|
|
4165
|
+
const cacheKey = `page:${workspaceId}:${date}:${shiftId}:${category}:${pageSize}:${startAfter || "first"}`;
|
|
4166
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4167
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4168
|
+
action: "page",
|
|
4169
|
+
workspaceId,
|
|
4170
|
+
date,
|
|
4171
|
+
shift: shiftId.toString(),
|
|
4172
|
+
category,
|
|
4173
|
+
pageSize,
|
|
4174
|
+
startAfter,
|
|
4175
|
+
sopCategories: this.sopCategories
|
|
4176
|
+
});
|
|
4177
|
+
return {
|
|
4178
|
+
videos: response.videos,
|
|
4179
|
+
nextToken: response.nextToken,
|
|
4180
|
+
hasMore: response.hasMore
|
|
4181
|
+
};
|
|
4182
|
+
});
|
|
4183
|
+
}
|
|
4184
|
+
/**
|
|
4185
|
+
* Convert S3 URI to CloudFront URL
|
|
4186
|
+
* In the API client, URLs are already signed from the server
|
|
4187
|
+
*/
|
|
4188
|
+
s3UriToCloudfront(s3Uri) {
|
|
4189
|
+
return s3Uri;
|
|
4190
|
+
}
|
|
4191
|
+
/**
|
|
4192
|
+
* Clean up resources
|
|
4193
|
+
*/
|
|
4194
|
+
dispose() {
|
|
4195
|
+
this.requestCache.clear();
|
|
4196
|
+
}
|
|
4197
|
+
/**
|
|
4198
|
+
* Get service statistics
|
|
4199
|
+
*/
|
|
4200
|
+
getStats() {
|
|
4201
|
+
return {
|
|
4202
|
+
requestCache: {
|
|
4203
|
+
pendingCount: this.requestCache.size,
|
|
4204
|
+
maxSize: 1e3
|
|
4205
|
+
}
|
|
4206
|
+
};
|
|
3804
4207
|
}
|
|
3805
4208
|
};
|
|
4209
|
+
|
|
4210
|
+
// src/lib/api/s3-clips-secure.ts
|
|
3806
4211
|
var S3ClipsService = class {
|
|
3807
4212
|
constructor(config) {
|
|
3808
|
-
//
|
|
3809
|
-
this.requestCache = new RequestDeduplicationCache();
|
|
3810
|
-
// Flag to prevent metadata fetching during index building
|
|
4213
|
+
// Flags for compatibility
|
|
3811
4214
|
this.isIndexBuilding = false;
|
|
3812
|
-
// Flag to prevent metadata fetching during entire prefetch operation
|
|
3813
4215
|
this.isPrefetching = false;
|
|
3814
|
-
// Global safeguard: limit concurrent metadata fetches to prevent flooding
|
|
3815
4216
|
this.currentMetadataFetches = 0;
|
|
3816
4217
|
this.MAX_CONCURRENT_METADATA = 3;
|
|
3817
4218
|
this.config = config;
|
|
3818
4219
|
if (!config.s3Config) {
|
|
3819
4220
|
throw new Error("S3 configuration is required");
|
|
3820
4221
|
}
|
|
4222
|
+
const sopCategories = config.s3Config.sopCategories?.default;
|
|
4223
|
+
this.apiClient = new S3ClipsAPIClient(sopCategories);
|
|
3821
4224
|
const processing = config.s3Config.processing || {};
|
|
3822
4225
|
this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
|
|
3823
4226
|
this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
|
|
3824
4227
|
this.concurrencyLimit = processing.concurrencyLimit || 10;
|
|
3825
4228
|
this.maxInitialFetch = processing.maxInitialFetch || 60;
|
|
3826
|
-
|
|
3827
|
-
console.log(`S3ClipsService: Using AWS region: ${region}`);
|
|
3828
|
-
this.s3Client = new clientS3.S3Client({
|
|
3829
|
-
region,
|
|
3830
|
-
credentials: config.s3Config.credentials ? {
|
|
3831
|
-
accessKeyId: config.s3Config.credentials.accessKeyId,
|
|
3832
|
-
secretAccessKey: config.s3Config.credentials.secretAccessKey
|
|
3833
|
-
} : void 0
|
|
3834
|
-
});
|
|
3835
|
-
}
|
|
3836
|
-
/**
|
|
3837
|
-
* Validates and sanitizes the AWS region
|
|
3838
|
-
*/
|
|
3839
|
-
validateAndSanitizeRegion(region) {
|
|
3840
|
-
const defaultRegion = "us-east-1";
|
|
3841
|
-
if (!region || typeof region !== "string") {
|
|
3842
|
-
console.warn(`S3ClipsService: Invalid region provided (${region}), using default: ${defaultRegion}`);
|
|
3843
|
-
return defaultRegion;
|
|
3844
|
-
}
|
|
3845
|
-
const sanitizedRegion = region.trim().toLowerCase();
|
|
3846
|
-
if (!sanitizedRegion) {
|
|
3847
|
-
console.warn(`S3ClipsService: Empty region provided, using default: ${defaultRegion}`);
|
|
3848
|
-
return defaultRegion;
|
|
3849
|
-
}
|
|
3850
|
-
const regionPattern = /^[a-z]{2,3}-[a-z]+-\d+$/;
|
|
3851
|
-
if (!regionPattern.test(sanitizedRegion)) {
|
|
3852
|
-
console.warn(`S3ClipsService: Invalid region format (${sanitizedRegion}), using default: ${defaultRegion}`);
|
|
3853
|
-
return defaultRegion;
|
|
3854
|
-
}
|
|
3855
|
-
return sanitizedRegion;
|
|
4229
|
+
console.log("[S3ClipsService] \u2705 Initialized with secure API client - AWS credentials are now server-side only!");
|
|
3856
4230
|
}
|
|
3857
4231
|
/**
|
|
3858
|
-
* Lists S3 clips
|
|
4232
|
+
* Lists S3 clips using API
|
|
3859
4233
|
*/
|
|
3860
4234
|
async listS3Clips(params) {
|
|
3861
|
-
const { workspaceId, date, shiftId
|
|
4235
|
+
const { workspaceId, date, shiftId } = params;
|
|
3862
4236
|
if (!isValidShiftId(shiftId)) {
|
|
3863
|
-
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}
|
|
4237
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
3864
4238
|
return [];
|
|
3865
4239
|
}
|
|
3866
|
-
|
|
3867
|
-
console.log(`[S3ClipsService] Listing clips for workspace: ${workspaceId}, date: ${date}, shift: ${shiftId}`);
|
|
3868
|
-
const deduplicationKey = `list-s3-clips:${prefix}:${maxKeys || "all"}`;
|
|
3869
|
-
return this.requestCache.deduplicate(
|
|
3870
|
-
deduplicationKey,
|
|
3871
|
-
() => this.executeListS3Clips(params),
|
|
3872
|
-
"ListS3Clips"
|
|
3873
|
-
);
|
|
3874
|
-
}
|
|
3875
|
-
/**
|
|
3876
|
-
* Internal implementation of S3 listing (called through deduplication)
|
|
3877
|
-
*/
|
|
3878
|
-
async executeListS3Clips(params) {
|
|
3879
|
-
const { workspaceId, date, shiftId, maxKeys } = params;
|
|
3880
|
-
const prefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
|
|
3881
|
-
console.log(`[S3ClipsService] Executing S3 list for prefix: ${prefix}`);
|
|
4240
|
+
console.log(`[S3ClipsService] Listing clips via API for workspace: ${workspaceId}`);
|
|
3882
4241
|
try {
|
|
3883
|
-
|
|
3884
|
-
let continuationToken = void 0;
|
|
3885
|
-
do {
|
|
3886
|
-
const command = new clientS3.ListObjectsV2Command({
|
|
3887
|
-
Bucket: this.config.s3Config.bucketName,
|
|
3888
|
-
Prefix: prefix,
|
|
3889
|
-
ContinuationToken: continuationToken,
|
|
3890
|
-
MaxKeys: maxKeys ?? 1e3
|
|
3891
|
-
});
|
|
3892
|
-
const response = await this.s3Client.send(command);
|
|
3893
|
-
console.log(`Got S3 response for ${prefix}:`, {
|
|
3894
|
-
keyCount: response.KeyCount,
|
|
3895
|
-
contentsLength: response.Contents?.length || 0,
|
|
3896
|
-
hasContinuation: !!response.NextContinuationToken
|
|
3897
|
-
});
|
|
3898
|
-
if (response.Contents) {
|
|
3899
|
-
if (response.Contents.length > 0) {
|
|
3900
|
-
console.log("Sample Keys:", response.Contents.slice(0, 3).map((obj) => obj.Key));
|
|
3901
|
-
}
|
|
3902
|
-
for (const obj of response.Contents) {
|
|
3903
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
3904
|
-
if (obj.Key.includes("missed_qchecks")) {
|
|
3905
|
-
console.log(`Skipping missed_qchecks path: ${obj.Key}`);
|
|
3906
|
-
continue;
|
|
3907
|
-
}
|
|
3908
|
-
playlists.push(`s3://${this.config.s3Config.bucketName}/${obj.Key}`);
|
|
3909
|
-
}
|
|
3910
|
-
}
|
|
3911
|
-
}
|
|
3912
|
-
continuationToken = response.NextContinuationToken;
|
|
3913
|
-
if (maxKeys && playlists.length >= maxKeys) {
|
|
3914
|
-
break;
|
|
3915
|
-
}
|
|
3916
|
-
} while (continuationToken && (!maxKeys || playlists.length < maxKeys));
|
|
3917
|
-
console.log(`Found ${playlists.length} HLS playlists in ${prefix}`);
|
|
3918
|
-
if (playlists.length > 0) {
|
|
3919
|
-
console.log("First playlist URI:", playlists[0]);
|
|
3920
|
-
}
|
|
3921
|
-
return playlists;
|
|
4242
|
+
return await this.apiClient.listS3Clips(params);
|
|
3922
4243
|
} catch (error) {
|
|
3923
|
-
console.error(
|
|
4244
|
+
console.error("[S3ClipsService] Error listing clips:", error);
|
|
3924
4245
|
return [];
|
|
3925
4246
|
}
|
|
3926
4247
|
}
|
|
3927
4248
|
/**
|
|
3928
|
-
*
|
|
4249
|
+
* Get metadata cycle time
|
|
3929
4250
|
*/
|
|
3930
4251
|
async getMetadataCycleTime(playlistUri) {
|
|
3931
|
-
const deduplicationKey = `metadata-cycle-time:${playlistUri}`;
|
|
3932
|
-
return this.requestCache.deduplicate(
|
|
3933
|
-
deduplicationKey,
|
|
3934
|
-
() => this.executeGetMetadataCycleTime(playlistUri),
|
|
3935
|
-
"MetadataCycleTime"
|
|
3936
|
-
);
|
|
3937
|
-
}
|
|
3938
|
-
/**
|
|
3939
|
-
* Internal implementation of metadata cycle time fetching
|
|
3940
|
-
*/
|
|
3941
|
-
async executeGetMetadataCycleTime(playlistUri) {
|
|
3942
4252
|
try {
|
|
3943
|
-
|
|
3944
|
-
const url = new URL(metadataUri);
|
|
3945
|
-
const bucket = url.hostname;
|
|
3946
|
-
const key = url.pathname.substring(1);
|
|
3947
|
-
console.log(`[S3ClipsService] Fetching metadata cycle time for: ${key}`);
|
|
3948
|
-
const command = new clientS3.GetObjectCommand({
|
|
3949
|
-
Bucket: bucket,
|
|
3950
|
-
Key: key
|
|
3951
|
-
});
|
|
3952
|
-
const response = await this.s3Client.send(command);
|
|
3953
|
-
if (!response.Body) {
|
|
3954
|
-
console.warn(`Empty response body for metadata file: ${key}`);
|
|
3955
|
-
return null;
|
|
3956
|
-
}
|
|
3957
|
-
const metadataContent = await response.Body.transformToString();
|
|
3958
|
-
const metadata = JSON.parse(metadataContent);
|
|
3959
|
-
const cycleTimeFrames = metadata?.original_task_metadata?.cycle_time;
|
|
3960
|
-
if (typeof cycleTimeFrames === "number") {
|
|
3961
|
-
return cycleTimeFrames;
|
|
3962
|
-
}
|
|
3963
|
-
return null;
|
|
4253
|
+
return await this.apiClient.getMetadataCycleTime(playlistUri);
|
|
3964
4254
|
} catch (error) {
|
|
3965
|
-
console.error(
|
|
4255
|
+
console.error("[S3ClipsService] Error fetching metadata cycle time:", error);
|
|
3966
4256
|
return null;
|
|
3967
4257
|
}
|
|
3968
4258
|
}
|
|
3969
4259
|
/**
|
|
3970
|
-
* Control prefetch mode
|
|
4260
|
+
* Control prefetch mode
|
|
3971
4261
|
*/
|
|
3972
4262
|
setPrefetchMode(enabled) {
|
|
3973
4263
|
this.isPrefetching = enabled;
|
|
3974
|
-
console.log(`[S3ClipsService] Prefetch mode ${enabled ? "enabled" : "disabled"}
|
|
4264
|
+
console.log(`[S3ClipsService] Prefetch mode ${enabled ? "enabled" : "disabled"}`);
|
|
3975
4265
|
}
|
|
3976
4266
|
/**
|
|
3977
|
-
*
|
|
4267
|
+
* Get full metadata
|
|
3978
4268
|
*/
|
|
3979
4269
|
async getFullMetadata(playlistUri) {
|
|
3980
4270
|
if (this.isIndexBuilding || this.isPrefetching) {
|
|
3981
|
-
console.warn(
|
|
4271
|
+
console.warn("[S3ClipsService] Skipping metadata - operation in progress");
|
|
3982
4272
|
return null;
|
|
3983
4273
|
}
|
|
3984
4274
|
if (this.currentMetadataFetches >= this.MAX_CONCURRENT_METADATA) {
|
|
3985
|
-
console.warn(
|
|
4275
|
+
console.warn("[S3ClipsService] Skipping metadata - max concurrent fetches reached");
|
|
3986
4276
|
return null;
|
|
3987
4277
|
}
|
|
3988
4278
|
this.currentMetadataFetches++;
|
|
3989
4279
|
try {
|
|
3990
|
-
|
|
3991
|
-
return await this.requestCache.deduplicate(
|
|
3992
|
-
deduplicationKey,
|
|
3993
|
-
() => this.executeGetFullMetadata(playlistUri),
|
|
3994
|
-
"FullMetadata"
|
|
3995
|
-
);
|
|
3996
|
-
} finally {
|
|
3997
|
-
this.currentMetadataFetches--;
|
|
3998
|
-
}
|
|
3999
|
-
}
|
|
4000
|
-
/**
|
|
4001
|
-
* Internal implementation of full metadata fetching
|
|
4002
|
-
*/
|
|
4003
|
-
async executeGetFullMetadata(playlistUri) {
|
|
4004
|
-
try {
|
|
4005
|
-
const metadataUri = playlistUri.replace(/playlist\.m3u8$/, "metadata.json");
|
|
4006
|
-
const url = new URL(metadataUri);
|
|
4007
|
-
const bucket = url.hostname;
|
|
4008
|
-
const key = url.pathname.substring(1);
|
|
4009
|
-
console.log(`[S3ClipsService] Fetching full metadata for: ${key}`);
|
|
4010
|
-
const command = new clientS3.GetObjectCommand({
|
|
4011
|
-
Bucket: bucket,
|
|
4012
|
-
Key: key
|
|
4013
|
-
});
|
|
4014
|
-
const response = await this.s3Client.send(command);
|
|
4015
|
-
if (!response.Body) {
|
|
4016
|
-
console.warn(`Empty response body for metadata file: ${key}`);
|
|
4017
|
-
return null;
|
|
4018
|
-
}
|
|
4019
|
-
const metadataContent = await response.Body.transformToString();
|
|
4020
|
-
const metadata = JSON.parse(metadataContent);
|
|
4021
|
-
return metadata;
|
|
4280
|
+
return await this.apiClient.getMetadata(playlistUri);
|
|
4022
4281
|
} catch (error) {
|
|
4023
|
-
console.error(
|
|
4282
|
+
console.error("[S3ClipsService] Error fetching metadata:", error);
|
|
4024
4283
|
return null;
|
|
4284
|
+
} finally {
|
|
4285
|
+
this.currentMetadataFetches--;
|
|
4025
4286
|
}
|
|
4026
4287
|
}
|
|
4027
4288
|
/**
|
|
4028
|
-
*
|
|
4289
|
+
* Convert S3 URI to CloudFront URL
|
|
4290
|
+
* URLs from API are already signed
|
|
4029
4291
|
*/
|
|
4030
4292
|
s3UriToCloudfront(s3Uri) {
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4293
|
+
if (s3Uri.startsWith("http")) {
|
|
4294
|
+
return s3Uri;
|
|
4295
|
+
}
|
|
4296
|
+
console.warn("[S3ClipsService] Unexpected S3 URI in secure mode:", s3Uri);
|
|
4297
|
+
return s3Uri;
|
|
4035
4298
|
}
|
|
4036
4299
|
/**
|
|
4037
|
-
*
|
|
4300
|
+
* Get SOP categories for workspace
|
|
4038
4301
|
*/
|
|
4039
4302
|
getSOPCategories(workspaceId) {
|
|
4040
4303
|
const sopConfig = this.config.s3Config?.sopCategories;
|
|
@@ -4046,375 +4309,85 @@ var S3ClipsService = class {
|
|
|
4046
4309
|
}
|
|
4047
4310
|
async getClipCounts(workspaceId, date, shiftId, buildIndex) {
|
|
4048
4311
|
if (!isValidShiftId(shiftId)) {
|
|
4049
|
-
console.error(`[S3ClipsService]
|
|
4050
|
-
return buildIndex ? { counts: {}, videoIndex:
|
|
4051
|
-
}
|
|
4052
|
-
const deduplicationKey = `clip-counts:${workspaceId}:${date}:${shiftId}:${buildIndex ? "with-index" : "counts-only"}`;
|
|
4053
|
-
return this.requestCache.deduplicate(
|
|
4054
|
-
deduplicationKey,
|
|
4055
|
-
() => this.executeGetClipCounts(workspaceId, date, shiftId, buildIndex),
|
|
4056
|
-
"ClipCounts"
|
|
4057
|
-
);
|
|
4058
|
-
}
|
|
4059
|
-
/**
|
|
4060
|
-
* Internal implementation of clip counts fetching
|
|
4061
|
-
*/
|
|
4062
|
-
async executeGetClipCounts(workspaceId, date, shiftId, buildIndex) {
|
|
4063
|
-
if (buildIndex) {
|
|
4064
|
-
this.isIndexBuilding = true;
|
|
4065
|
-
console.log(`[S3ClipsService] Starting index building - metadata fetching disabled`);
|
|
4312
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
4313
|
+
return buildIndex ? { counts: {}, videoIndex: this.createEmptyVideoIndex(workspaceId, date, shiftId.toString()) } : {};
|
|
4066
4314
|
}
|
|
4067
4315
|
try {
|
|
4068
|
-
const basePrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
|
|
4069
|
-
const counts = { total: 0 };
|
|
4070
|
-
const categoryFolders = [
|
|
4071
|
-
"idle_time",
|
|
4072
|
-
"low_value",
|
|
4073
|
-
"sop_deviation",
|
|
4074
|
-
"missing_quality_check",
|
|
4075
|
-
"best_cycle_time",
|
|
4076
|
-
"worst_cycle_time",
|
|
4077
|
-
"long_cycle_time",
|
|
4078
|
-
"cycle_completion",
|
|
4079
|
-
"bottleneck"
|
|
4080
|
-
];
|
|
4081
|
-
const shiftName = shiftId === 0 || shiftId === "0" ? "Day" : "Night";
|
|
4082
|
-
console.log(`[S3ClipsService] ${buildIndex ? "Building video index and counting" : "Fast counting"} clips for ${workspaceId} on ${date}, shift ${shiftId} (${shiftName} Shift)`);
|
|
4083
|
-
const startTime = performance.now();
|
|
4084
|
-
const videoIndex = buildIndex ? {
|
|
4085
|
-
byCategory: /* @__PURE__ */ new Map(),
|
|
4086
|
-
allVideos: [],
|
|
4087
|
-
counts: {},
|
|
4088
|
-
workspaceId,
|
|
4089
|
-
date,
|
|
4090
|
-
shiftId: shiftId.toString(),
|
|
4091
|
-
lastUpdated: /* @__PURE__ */ new Date(),
|
|
4092
|
-
_debugId: `vid_${Date.now()}_${Math.random().toString(36).substring(7)}`
|
|
4093
|
-
} : null;
|
|
4094
|
-
const countPromises = categoryFolders.map(async (category) => {
|
|
4095
|
-
const categoryPrefix = `${basePrefix}${category}/videos/`;
|
|
4096
|
-
const categoryVideos = [];
|
|
4097
|
-
try {
|
|
4098
|
-
if (buildIndex) {
|
|
4099
|
-
const command = new clientS3.ListObjectsV2Command({
|
|
4100
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4101
|
-
Prefix: categoryPrefix,
|
|
4102
|
-
MaxKeys: 1e3
|
|
4103
|
-
});
|
|
4104
|
-
let continuationToken;
|
|
4105
|
-
do {
|
|
4106
|
-
if (continuationToken) {
|
|
4107
|
-
command.input.ContinuationToken = continuationToken;
|
|
4108
|
-
}
|
|
4109
|
-
const response = await this.s3Client.send(command);
|
|
4110
|
-
if (response.Contents) {
|
|
4111
|
-
for (const obj of response.Contents) {
|
|
4112
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
4113
|
-
if (obj.Key.includes("missed_qchecks")) {
|
|
4114
|
-
continue;
|
|
4115
|
-
}
|
|
4116
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${obj.Key}`;
|
|
4117
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4118
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4119
|
-
const belongsToCategory = parsedInfo && (parsedInfo.type === category || // Handle specific mismatches between folder names and parsed types
|
|
4120
|
-
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");
|
|
4121
|
-
if (belongsToCategory) {
|
|
4122
|
-
const videoEntry = {
|
|
4123
|
-
uri: s3Uri,
|
|
4124
|
-
category: parsedInfo.type,
|
|
4125
|
-
// Use the parsed type, not the folder name
|
|
4126
|
-
timestamp: parsedInfo.timestamp,
|
|
4127
|
-
videoId: `${workspaceId}-${parsedInfo.timestamp}`,
|
|
4128
|
-
workspaceId,
|
|
4129
|
-
date,
|
|
4130
|
-
shiftId: shiftId.toString()
|
|
4131
|
-
};
|
|
4132
|
-
categoryVideos.push(videoEntry);
|
|
4133
|
-
}
|
|
4134
|
-
}
|
|
4135
|
-
}
|
|
4136
|
-
}
|
|
4137
|
-
continuationToken = response.NextContinuationToken;
|
|
4138
|
-
} while (continuationToken);
|
|
4139
|
-
categoryVideos.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
4140
|
-
if (categoryVideos.length > 0) {
|
|
4141
|
-
console.log(`[S3ClipsService] Found ${categoryVideos.length} videos for category '${category}' (parsed types: ${[...new Set(categoryVideos.map((v) => v.category))].join(", ")})`);
|
|
4142
|
-
}
|
|
4143
|
-
return { category, count: categoryVideos.length, videos: categoryVideos };
|
|
4144
|
-
} else {
|
|
4145
|
-
const command = new clientS3.ListObjectsV2Command({
|
|
4146
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4147
|
-
Prefix: categoryPrefix,
|
|
4148
|
-
Delimiter: "/",
|
|
4149
|
-
MaxKeys: 1e3
|
|
4150
|
-
});
|
|
4151
|
-
let folderCount = 0;
|
|
4152
|
-
let continuationToken;
|
|
4153
|
-
do {
|
|
4154
|
-
if (continuationToken) {
|
|
4155
|
-
command.input.ContinuationToken = continuationToken;
|
|
4156
|
-
}
|
|
4157
|
-
const response = await this.s3Client.send(command);
|
|
4158
|
-
if (response.CommonPrefixes) {
|
|
4159
|
-
folderCount += response.CommonPrefixes.length;
|
|
4160
|
-
}
|
|
4161
|
-
continuationToken = response.NextContinuationToken;
|
|
4162
|
-
} while (continuationToken);
|
|
4163
|
-
return { category, count: folderCount, videos: [] };
|
|
4164
|
-
}
|
|
4165
|
-
} catch (error) {
|
|
4166
|
-
console.error(`Error ${buildIndex ? "building index for" : "counting folders for"} category ${category}:`, error);
|
|
4167
|
-
return { category, count: 0, videos: [] };
|
|
4168
|
-
}
|
|
4169
|
-
});
|
|
4170
|
-
const results = await Promise.all(countPromises);
|
|
4171
|
-
for (const { category, count, videos } of results) {
|
|
4172
|
-
counts[category] = count;
|
|
4173
|
-
counts.total += count;
|
|
4174
|
-
if (buildIndex && videoIndex && videos) {
|
|
4175
|
-
if (videos.length > 0) {
|
|
4176
|
-
const parsedType = videos[0].category;
|
|
4177
|
-
videoIndex.byCategory.set(parsedType, videos);
|
|
4178
|
-
console.log(`[S3ClipsService] Indexed ${videos.length} videos under parsed type '${parsedType}'`);
|
|
4179
|
-
if (category !== parsedType) {
|
|
4180
|
-
videoIndex.byCategory.set(category, videos);
|
|
4181
|
-
console.log(`[S3ClipsService] Created alias: S3 folder '${category}' -> parsed type '${parsedType}' (${videos.length} videos)`);
|
|
4182
|
-
}
|
|
4183
|
-
}
|
|
4184
|
-
videoIndex.allVideos.push(...videos);
|
|
4185
|
-
}
|
|
4186
|
-
}
|
|
4187
|
-
if (buildIndex && videoIndex) {
|
|
4188
|
-
videoIndex.allVideos.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
4189
|
-
videoIndex.counts = { ...counts };
|
|
4190
|
-
}
|
|
4191
|
-
const elapsed = performance.now() - startTime;
|
|
4192
|
-
console.log(`[S3ClipsService] ${buildIndex ? "Video index and counts" : "Clip counts"} completed in ${elapsed.toFixed(2)}ms - Total: ${counts.total}`);
|
|
4193
|
-
if (buildIndex && videoIndex) {
|
|
4194
|
-
console.log(`[S3ClipsService] Final video index summary:`);
|
|
4195
|
-
console.log(` - VideoIndex ID: ${videoIndex._debugId}`);
|
|
4196
|
-
console.log(` - Total videos in allVideos: ${videoIndex.allVideos.length}`);
|
|
4197
|
-
console.log(` - Categories in byCategory Map: ${Array.from(videoIndex.byCategory.keys()).join(", ")}`);
|
|
4198
|
-
for (const [cat, vids] of videoIndex.byCategory.entries()) {
|
|
4199
|
-
console.log(` - '${cat}': ${vids.length} videos`);
|
|
4200
|
-
}
|
|
4201
|
-
return { counts, videoIndex };
|
|
4202
|
-
}
|
|
4203
|
-
return counts;
|
|
4204
|
-
} finally {
|
|
4205
4316
|
if (buildIndex) {
|
|
4206
|
-
this.
|
|
4207
|
-
|
|
4317
|
+
const result = await this.apiClient.getClipCountsWithIndex(workspaceId, date, shiftId);
|
|
4318
|
+
const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
|
|
4319
|
+
await smartVideoCache.setClipCounts(cacheKey, result);
|
|
4320
|
+
return result;
|
|
4321
|
+
} else {
|
|
4322
|
+
const counts = await this.apiClient.getClipCounts(workspaceId, date, shiftId);
|
|
4323
|
+
return counts;
|
|
4208
4324
|
}
|
|
4325
|
+
} catch (error) {
|
|
4326
|
+
console.error("[S3ClipsService] Error fetching clip counts:", error);
|
|
4327
|
+
return buildIndex ? { counts: {}, videoIndex: this.createEmptyVideoIndex(workspaceId, date, shiftId.toString()) } : {};
|
|
4209
4328
|
}
|
|
4210
4329
|
}
|
|
4211
4330
|
async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex) {
|
|
4212
4331
|
const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
|
|
4213
4332
|
const cachedResult = await smartVideoCache.getClipCounts(cacheKey);
|
|
4214
4333
|
if (cachedResult) {
|
|
4215
|
-
console.log(
|
|
4216
|
-
|
|
4217
|
-
return cachedResult;
|
|
4218
|
-
} else {
|
|
4219
|
-
return cachedResult.counts;
|
|
4220
|
-
}
|
|
4334
|
+
console.log("[S3ClipsService] Using cached clip counts");
|
|
4335
|
+
return buildIndex ? cachedResult : cachedResult.counts;
|
|
4221
4336
|
}
|
|
4222
|
-
console.log(
|
|
4337
|
+
console.log("[S3ClipsService] Cache miss - fetching from API");
|
|
4223
4338
|
return buildIndex ? this.getClipCounts(workspaceId, date, shiftId, true) : this.getClipCounts(workspaceId, date, shiftId);
|
|
4224
4339
|
}
|
|
4225
4340
|
/**
|
|
4226
|
-
* Get first clip for
|
|
4341
|
+
* Get first clip for category
|
|
4227
4342
|
*/
|
|
4228
4343
|
async getFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4229
4344
|
if (!isValidShiftId(shiftId)) {
|
|
4230
|
-
console.error(`[S3ClipsService]
|
|
4345
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
4231
4346
|
return null;
|
|
4232
4347
|
}
|
|
4233
|
-
const deduplicationKey = `first-clip:${workspaceId}:${date}:${shiftId}:${category}`;
|
|
4234
|
-
return this.requestCache.deduplicate(
|
|
4235
|
-
deduplicationKey,
|
|
4236
|
-
() => this.executeGetFirstClipForCategory(workspaceId, date, shiftId, category),
|
|
4237
|
-
"FirstClip"
|
|
4238
|
-
);
|
|
4239
|
-
}
|
|
4240
|
-
/**
|
|
4241
|
-
* Internal implementation of first clip fetching
|
|
4242
|
-
*/
|
|
4243
|
-
async executeGetFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4244
|
-
const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
|
|
4245
4348
|
try {
|
|
4246
|
-
|
|
4247
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4248
|
-
Prefix: categoryPrefix,
|
|
4249
|
-
MaxKeys: 10
|
|
4250
|
-
// Small limit since we only need one
|
|
4251
|
-
});
|
|
4252
|
-
const response = await this.s3Client.send(command);
|
|
4253
|
-
if (response.Contents) {
|
|
4254
|
-
const playlistObj = response.Contents.find((obj) => obj.Key?.endsWith("playlist.m3u8"));
|
|
4255
|
-
if (playlistObj && playlistObj.Key) {
|
|
4256
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${playlistObj.Key}`;
|
|
4257
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4258
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4259
|
-
if (parsedInfo) {
|
|
4260
|
-
const cloudfrontUrl = this.s3UriToCloudfront(s3Uri);
|
|
4261
|
-
return {
|
|
4262
|
-
id: `${workspaceId}-${date}-${shiftId}-${category}-first`,
|
|
4263
|
-
src: cloudfrontUrl,
|
|
4264
|
-
...parsedInfo,
|
|
4265
|
-
originalUri: s3Uri
|
|
4266
|
-
};
|
|
4267
|
-
}
|
|
4268
|
-
}
|
|
4269
|
-
}
|
|
4349
|
+
return await this.apiClient.getFirstClipForCategory(workspaceId, date, shiftId, category);
|
|
4270
4350
|
} catch (error) {
|
|
4271
|
-
console.error(
|
|
4351
|
+
console.error("[S3ClipsService] Error fetching first clip:", error);
|
|
4352
|
+
return null;
|
|
4272
4353
|
}
|
|
4273
|
-
return null;
|
|
4274
4354
|
}
|
|
4275
4355
|
/**
|
|
4276
|
-
* Get
|
|
4356
|
+
* Get video from index (for compatibility)
|
|
4277
4357
|
*/
|
|
4278
4358
|
async getVideoFromIndex(videoIndex, category, index, includeCycleTime = true, includeMetadata = true) {
|
|
4359
|
+
const { workspaceId, date, shiftId } = videoIndex;
|
|
4360
|
+
return this.getClipByIndex(
|
|
4361
|
+
workspaceId,
|
|
4362
|
+
date,
|
|
4363
|
+
shiftId,
|
|
4364
|
+
category,
|
|
4365
|
+
index,
|
|
4366
|
+
includeCycleTime,
|
|
4367
|
+
includeMetadata
|
|
4368
|
+
);
|
|
4369
|
+
}
|
|
4370
|
+
/**
|
|
4371
|
+
* Get clip by index
|
|
4372
|
+
*/
|
|
4373
|
+
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4279
4374
|
try {
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
console.warn(`Video index total videos: ${videoIndex.allVideos.length}`);
|
|
4286
|
-
return null;
|
|
4287
|
-
}
|
|
4288
|
-
const videoEntry = categoryVideos[index];
|
|
4289
|
-
return this.processFullVideo(
|
|
4290
|
-
videoEntry.uri,
|
|
4375
|
+
return await this.apiClient.getClipByIndex(
|
|
4376
|
+
workspaceId,
|
|
4377
|
+
date,
|
|
4378
|
+
shiftId,
|
|
4379
|
+
category,
|
|
4291
4380
|
index,
|
|
4292
|
-
videoEntry.workspaceId,
|
|
4293
|
-
videoEntry.date,
|
|
4294
|
-
videoEntry.shiftId,
|
|
4295
4381
|
includeCycleTime,
|
|
4296
4382
|
includeMetadata
|
|
4297
4383
|
);
|
|
4298
4384
|
} catch (error) {
|
|
4299
|
-
console.error(
|
|
4385
|
+
console.error("[S3ClipsService] Error fetching clip by index:", error);
|
|
4300
4386
|
return null;
|
|
4301
4387
|
}
|
|
4302
4388
|
}
|
|
4303
4389
|
/**
|
|
4304
|
-
*
|
|
4305
|
-
* @deprecated Use getVideoFromIndex with a pre-built VideoIndex for better performance
|
|
4306
|
-
*/
|
|
4307
|
-
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4308
|
-
const deduplicationKey = `clip-by-index:${workspaceId}:${date}:${shiftId}:${category}:${index}:${includeCycleTime}:${includeMetadata}`;
|
|
4309
|
-
return this.requestCache.deduplicate(
|
|
4310
|
-
deduplicationKey,
|
|
4311
|
-
() => this.executeGetClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime, includeMetadata),
|
|
4312
|
-
"ClipByIndex"
|
|
4313
|
-
);
|
|
4314
|
-
}
|
|
4315
|
-
/**
|
|
4316
|
-
* Internal implementation of clip by index fetching
|
|
4317
|
-
*/
|
|
4318
|
-
async executeGetClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4319
|
-
const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
|
|
4320
|
-
try {
|
|
4321
|
-
const command = new clientS3.ListObjectsV2Command({
|
|
4322
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4323
|
-
Prefix: categoryPrefix,
|
|
4324
|
-
MaxKeys: 1e3
|
|
4325
|
-
});
|
|
4326
|
-
let playlists = [];
|
|
4327
|
-
let continuationToken = void 0;
|
|
4328
|
-
do {
|
|
4329
|
-
if (continuationToken) {
|
|
4330
|
-
command.input.ContinuationToken = continuationToken;
|
|
4331
|
-
}
|
|
4332
|
-
const response = await this.s3Client.send(command);
|
|
4333
|
-
if (response.Contents) {
|
|
4334
|
-
for (const obj of response.Contents) {
|
|
4335
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
4336
|
-
playlists.push(`s3://${this.config.s3Config.bucketName}/${obj.Key}`);
|
|
4337
|
-
}
|
|
4338
|
-
}
|
|
4339
|
-
}
|
|
4340
|
-
continuationToken = response.NextContinuationToken;
|
|
4341
|
-
} while (continuationToken && playlists.length < index + 10);
|
|
4342
|
-
playlists.sort((a, b) => {
|
|
4343
|
-
const parsedA = parseS3Uri(a);
|
|
4344
|
-
const parsedB = parseS3Uri(b);
|
|
4345
|
-
if (!parsedA || !parsedB) return 0;
|
|
4346
|
-
return parsedB.timestamp.localeCompare(parsedA.timestamp);
|
|
4347
|
-
});
|
|
4348
|
-
if (index < playlists.length) {
|
|
4349
|
-
const uri = playlists[index];
|
|
4350
|
-
return this.processFullVideo(
|
|
4351
|
-
uri,
|
|
4352
|
-
index,
|
|
4353
|
-
workspaceId,
|
|
4354
|
-
date,
|
|
4355
|
-
shiftId.toString(),
|
|
4356
|
-
includeCycleTime,
|
|
4357
|
-
includeMetadata
|
|
4358
|
-
);
|
|
4359
|
-
}
|
|
4360
|
-
} catch (error) {
|
|
4361
|
-
console.error(`[getClipByIndex] Error getting clip at index ${index} for category ${category}:`, error);
|
|
4362
|
-
}
|
|
4363
|
-
return null;
|
|
4364
|
-
}
|
|
4365
|
-
/**
|
|
4366
|
-
* Get one sample video from each category for preloading
|
|
4367
|
-
*/
|
|
4368
|
-
async getSampleVideos(workspaceId, date, shiftId) {
|
|
4369
|
-
const basePrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
|
|
4370
|
-
const samples = {};
|
|
4371
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4372
|
-
const categoriesToSample = sopCategories ? sopCategories.map((cat) => cat.id) : ["low_value", "best_cycle_time", "worst_cycle_time", "long_cycle_time", "cycle_completion"];
|
|
4373
|
-
console.log(`[S3ClipsService] Getting sample videos for categories:`, categoriesToSample);
|
|
4374
|
-
const samplePromises = categoriesToSample.map(async (category) => {
|
|
4375
|
-
const categoryPrefix = `${basePrefix}${category}/`;
|
|
4376
|
-
try {
|
|
4377
|
-
const command = new clientS3.ListObjectsV2Command({
|
|
4378
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4379
|
-
Prefix: categoryPrefix,
|
|
4380
|
-
MaxKeys: 20
|
|
4381
|
-
// Small limit since we only need one
|
|
4382
|
-
});
|
|
4383
|
-
const response = await this.s3Client.send(command);
|
|
4384
|
-
if (response.Contents) {
|
|
4385
|
-
const playlistObj = response.Contents.find((obj) => obj.Key?.endsWith("playlist.m3u8"));
|
|
4386
|
-
if (playlistObj && playlistObj.Key) {
|
|
4387
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${playlistObj.Key}`;
|
|
4388
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4389
|
-
if (parsedInfo) {
|
|
4390
|
-
return {
|
|
4391
|
-
category,
|
|
4392
|
-
sample: {
|
|
4393
|
-
id: `${workspaceId}-${date}-${shiftId}-${category}-sample`,
|
|
4394
|
-
src: this.s3UriToCloudfront(s3Uri),
|
|
4395
|
-
// Pre-convert to CloudFront URL
|
|
4396
|
-
...parsedInfo,
|
|
4397
|
-
originalUri: s3Uri
|
|
4398
|
-
}
|
|
4399
|
-
};
|
|
4400
|
-
}
|
|
4401
|
-
}
|
|
4402
|
-
}
|
|
4403
|
-
} catch (error) {
|
|
4404
|
-
console.error(`Error getting sample for category ${category}:`, error);
|
|
4405
|
-
}
|
|
4406
|
-
return { category, sample: null };
|
|
4407
|
-
});
|
|
4408
|
-
const sampleResults = await Promise.all(samplePromises);
|
|
4409
|
-
for (const { category, sample } of sampleResults) {
|
|
4410
|
-
if (sample) {
|
|
4411
|
-
samples[category] = sample;
|
|
4412
|
-
}
|
|
4413
|
-
}
|
|
4414
|
-
return samples;
|
|
4415
|
-
}
|
|
4416
|
-
/**
|
|
4417
|
-
* Processes a single video completely
|
|
4390
|
+
* Process full video (for compatibility)
|
|
4418
4391
|
*/
|
|
4419
4392
|
async processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime, includeMetadata = false) {
|
|
4420
4393
|
const sopCategories = this.getSOPCategories(workspaceId);
|
|
@@ -4423,35 +4396,35 @@ var S3ClipsService = class {
|
|
|
4423
4396
|
console.warn(`Skipping URI due to parsing failure: ${uri}`);
|
|
4424
4397
|
return null;
|
|
4425
4398
|
}
|
|
4426
|
-
|
|
4427
|
-
|
|
4399
|
+
const video = {
|
|
4400
|
+
id: `${workspaceId}-${date}-${shiftId}-${index}`,
|
|
4401
|
+
src: uri,
|
|
4402
|
+
// Already signed from API
|
|
4403
|
+
...parsedInfo,
|
|
4404
|
+
originalUri: uri
|
|
4405
|
+
};
|
|
4428
4406
|
if (includeMetadata) {
|
|
4429
4407
|
const metadata = await this.getFullMetadata(uri);
|
|
4430
4408
|
if (metadata) {
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
}
|
|
4434
|
-
creationTimestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp || metadata[""];
|
|
4409
|
+
video.cycle_time_seconds = metadata.original_task_metadata?.cycle_time;
|
|
4410
|
+
video.creation_timestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp;
|
|
4435
4411
|
}
|
|
4436
|
-
} 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")) {
|
|
4437
|
-
cycleTimeSeconds = null;
|
|
4438
4412
|
}
|
|
4439
|
-
|
|
4440
|
-
const { type: videoType, timestamp: videoTimestamp } = parsedInfo;
|
|
4441
|
-
return {
|
|
4442
|
-
id: `${workspaceId}-${date}-${shiftId}-${videoType}-${videoTimestamp.replace(/:/g, "")}-${index}`,
|
|
4443
|
-
src: cloudfrontPlaylistUrl,
|
|
4444
|
-
// Direct CloudFront playlist URL
|
|
4445
|
-
...parsedInfo,
|
|
4446
|
-
cycle_time_seconds: cycleTimeSeconds || void 0,
|
|
4447
|
-
creation_timestamp: creationTimestamp
|
|
4448
|
-
};
|
|
4413
|
+
return video;
|
|
4449
4414
|
}
|
|
4450
4415
|
/**
|
|
4451
|
-
*
|
|
4416
|
+
* Fetch clips (main method for compatibility)
|
|
4452
4417
|
*/
|
|
4453
4418
|
async fetchClips(params) {
|
|
4454
|
-
const {
|
|
4419
|
+
const {
|
|
4420
|
+
workspaceId,
|
|
4421
|
+
date: inputDate,
|
|
4422
|
+
shift,
|
|
4423
|
+
category,
|
|
4424
|
+
limit,
|
|
4425
|
+
offset = 0,
|
|
4426
|
+
mode
|
|
4427
|
+
} = params;
|
|
4455
4428
|
if (!workspaceId) {
|
|
4456
4429
|
throw new Error("Valid Workspace ID is required");
|
|
4457
4430
|
}
|
|
@@ -4469,88 +4442,66 @@ var S3ClipsService = class {
|
|
|
4469
4442
|
const { shiftId: currentShiftId } = getCurrentShift2(this.config.dateTimeConfig?.defaultTimezone);
|
|
4470
4443
|
shiftId = currentShiftId;
|
|
4471
4444
|
}
|
|
4472
|
-
console.log(`S3ClipsService
|
|
4445
|
+
console.log(`[S3ClipsService] Fetching clips for workspace ${workspaceId}`);
|
|
4473
4446
|
if (mode === "summary") {
|
|
4474
4447
|
const counts = await this.getClipCounts(workspaceId, date, shiftId.toString());
|
|
4475
4448
|
return { counts, samples: {} };
|
|
4476
4449
|
}
|
|
4477
4450
|
const effectiveLimit = limit || this.defaultLimitPerCategory;
|
|
4478
|
-
const
|
|
4451
|
+
const result = await this.getVideosPage(
|
|
4479
4452
|
workspaceId,
|
|
4480
4453
|
date,
|
|
4481
4454
|
shiftId,
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
if (!parsedInfo) return false;
|
|
4494
|
-
if (category === "long_cycle_time") {
|
|
4495
|
-
return parsedInfo.type === "long_cycle_time" || parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time");
|
|
4496
|
-
}
|
|
4497
|
-
return parsedInfo.type === category;
|
|
4498
|
-
});
|
|
4499
|
-
filteredUris = filteredUris.slice(offset, offset + effectiveLimit);
|
|
4500
|
-
}
|
|
4501
|
-
const videoPromises = filteredUris.map(async (uri, index) => {
|
|
4502
|
-
return this.processFullVideo(
|
|
4503
|
-
uri,
|
|
4504
|
-
index,
|
|
4455
|
+
category || "all",
|
|
4456
|
+
effectiveLimit
|
|
4457
|
+
);
|
|
4458
|
+
return result.videos;
|
|
4459
|
+
}
|
|
4460
|
+
/**
|
|
4461
|
+
* Get videos page using pagination API
|
|
4462
|
+
*/
|
|
4463
|
+
async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
|
|
4464
|
+
try {
|
|
4465
|
+
return await this.apiClient.getVideosPage(
|
|
4505
4466
|
workspaceId,
|
|
4506
4467
|
date,
|
|
4507
|
-
shiftId
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4468
|
+
shiftId,
|
|
4469
|
+
category,
|
|
4470
|
+
pageSize,
|
|
4471
|
+
startAfter
|
|
4511
4472
|
);
|
|
4512
|
-
})
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
if (timestampStart || timestampEnd) {
|
|
4516
|
-
videos = videos.filter((video) => {
|
|
4517
|
-
if (!video.creation_timestamp) return false;
|
|
4518
|
-
const videoTimestamp = new Date(video.creation_timestamp).getTime();
|
|
4519
|
-
if (timestampStart && timestampEnd) {
|
|
4520
|
-
const start = new Date(timestampStart).getTime();
|
|
4521
|
-
const end = new Date(timestampEnd).getTime();
|
|
4522
|
-
return videoTimestamp >= start && videoTimestamp <= end;
|
|
4523
|
-
} else if (timestampStart) {
|
|
4524
|
-
const start = new Date(timestampStart).getTime();
|
|
4525
|
-
return videoTimestamp >= start;
|
|
4526
|
-
} else if (timestampEnd) {
|
|
4527
|
-
const end = new Date(timestampEnd).getTime();
|
|
4528
|
-
return videoTimestamp <= end;
|
|
4529
|
-
}
|
|
4530
|
-
return true;
|
|
4531
|
-
});
|
|
4532
|
-
}
|
|
4533
|
-
videos.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
4534
|
-
const cacheKey = `videos:${workspaceId}:${date}:${shiftId}:${category || "all"}`;
|
|
4535
|
-
if (videos.length > 0) {
|
|
4536
|
-
smartVideoCache.setVideos(cacheKey, videos);
|
|
4473
|
+
} catch (error) {
|
|
4474
|
+
console.error("[S3ClipsService] Error fetching videos page:", error);
|
|
4475
|
+
return { videos: [], hasMore: false };
|
|
4537
4476
|
}
|
|
4538
|
-
return videos;
|
|
4539
4477
|
}
|
|
4540
4478
|
/**
|
|
4541
|
-
*
|
|
4479
|
+
* Create empty video index for compatibility
|
|
4480
|
+
*/
|
|
4481
|
+
createEmptyVideoIndex(workspaceId, date, shiftId) {
|
|
4482
|
+
return {
|
|
4483
|
+
byCategory: /* @__PURE__ */ new Map(),
|
|
4484
|
+
allVideos: [],
|
|
4485
|
+
counts: {},
|
|
4486
|
+
workspaceId,
|
|
4487
|
+
date,
|
|
4488
|
+
shiftId,
|
|
4489
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
4490
|
+
_debugId: `empty_${Date.now()}`
|
|
4491
|
+
};
|
|
4492
|
+
}
|
|
4493
|
+
/**
|
|
4494
|
+
* Cleanup
|
|
4542
4495
|
*/
|
|
4543
4496
|
dispose() {
|
|
4544
|
-
console.log("[S3ClipsService] Disposing service
|
|
4545
|
-
this.
|
|
4497
|
+
console.log("[S3ClipsService] Disposing service");
|
|
4498
|
+
this.apiClient.dispose();
|
|
4546
4499
|
}
|
|
4547
4500
|
/**
|
|
4548
|
-
* Get
|
|
4501
|
+
* Get statistics
|
|
4549
4502
|
*/
|
|
4550
4503
|
getStats() {
|
|
4551
|
-
return
|
|
4552
|
-
requestCache: this.requestCache.getStats()
|
|
4553
|
-
};
|
|
4504
|
+
return this.apiClient.getStats();
|
|
4554
4505
|
}
|
|
4555
4506
|
};
|
|
4556
4507
|
|
|
@@ -8465,6 +8416,16 @@ function useTicketHistory(companyId) {
|
|
|
8465
8416
|
throw err;
|
|
8466
8417
|
}
|
|
8467
8418
|
}, [refreshTickets]);
|
|
8419
|
+
const updateTicketServicedStatus = React19.useCallback(async (ticketId, serviced) => {
|
|
8420
|
+
setError(null);
|
|
8421
|
+
try {
|
|
8422
|
+
await TicketHistoryService.updateTicketServicedStatus(ticketId, serviced);
|
|
8423
|
+
await refreshTickets();
|
|
8424
|
+
} catch (err) {
|
|
8425
|
+
setError(err instanceof Error ? err.message : "Failed to update ticket serviced status");
|
|
8426
|
+
throw err;
|
|
8427
|
+
}
|
|
8428
|
+
}, [refreshTickets]);
|
|
8468
8429
|
React19.useEffect(() => {
|
|
8469
8430
|
if (companyId) {
|
|
8470
8431
|
refreshTickets();
|
|
@@ -8476,7 +8437,8 @@ function useTicketHistory(companyId) {
|
|
|
8476
8437
|
error,
|
|
8477
8438
|
createTicket,
|
|
8478
8439
|
refreshTickets,
|
|
8479
|
-
updateTicketStatus
|
|
8440
|
+
updateTicketStatus,
|
|
8441
|
+
updateTicketServicedStatus
|
|
8480
8442
|
};
|
|
8481
8443
|
}
|
|
8482
8444
|
|
|
@@ -11628,7 +11590,8 @@ var usePrefetchClipCounts = ({
|
|
|
11628
11590
|
date,
|
|
11629
11591
|
shift,
|
|
11630
11592
|
enabled = true,
|
|
11631
|
-
buildIndex =
|
|
11593
|
+
buildIndex = false,
|
|
11594
|
+
// Default to false for cost efficiency
|
|
11632
11595
|
subscriberId
|
|
11633
11596
|
}) => {
|
|
11634
11597
|
const dashboardConfig = useDashboardConfig();
|
|
@@ -11716,12 +11679,12 @@ var usePrefetchClipCounts = ({
|
|
|
11716
11679
|
}
|
|
11717
11680
|
},
|
|
11718
11681
|
onRenderReady: (key, newData) => {
|
|
11719
|
-
console.log(`[usePrefetchClipCounts] Render ready with ${Object.values(newData.counts).reduce((sum, count) => sum + count, 0)} total clips`);
|
|
11682
|
+
console.log(`[usePrefetchClipCounts] Render ready with ${Object.values(newData.counts || {}).reduce((sum, count) => sum + count, 0)} total clips`);
|
|
11720
11683
|
setData(newData);
|
|
11721
11684
|
setError(null);
|
|
11722
11685
|
},
|
|
11723
11686
|
onFullyIndexed: (key, newData) => {
|
|
11724
|
-
console.log(`[usePrefetchClipCounts] Fully indexed with ${newData.videoIndex
|
|
11687
|
+
console.log(`[usePrefetchClipCounts] Fully indexed with ${newData.videoIndex?.allVideos?.length || 0} videos`);
|
|
11725
11688
|
setData(newData);
|
|
11726
11689
|
setError(null);
|
|
11727
11690
|
},
|
|
@@ -12127,6 +12090,140 @@ function useWorkspaceNavigation() {
|
|
|
12127
12090
|
getWorkspaceNavigationParams: getWorkspaceNavigationParams2
|
|
12128
12091
|
};
|
|
12129
12092
|
}
|
|
12093
|
+
function useWorkspaceHealth(options = {}) {
|
|
12094
|
+
const [workspaces, setWorkspaces] = React19.useState([]);
|
|
12095
|
+
const [summary, setSummary] = React19.useState(null);
|
|
12096
|
+
const [loading, setLoading] = React19.useState(true);
|
|
12097
|
+
const [error, setError] = React19.useState(null);
|
|
12098
|
+
const unsubscribeRef = React19.useRef(null);
|
|
12099
|
+
const refreshIntervalRef = React19.useRef(null);
|
|
12100
|
+
const {
|
|
12101
|
+
enableRealtime = true,
|
|
12102
|
+
refreshInterval = 3e4,
|
|
12103
|
+
// 30 seconds default
|
|
12104
|
+
...filterOptions
|
|
12105
|
+
} = options;
|
|
12106
|
+
const fetchData = React19.useCallback(async () => {
|
|
12107
|
+
try {
|
|
12108
|
+
setError(null);
|
|
12109
|
+
workspaceHealthService.clearCache();
|
|
12110
|
+
const [workspacesData, summaryData] = await Promise.all([
|
|
12111
|
+
workspaceHealthService.getWorkspaceHealthStatus(filterOptions),
|
|
12112
|
+
workspaceHealthService.getHealthSummary(filterOptions.lineId, filterOptions.companyId)
|
|
12113
|
+
]);
|
|
12114
|
+
setWorkspaces(workspacesData);
|
|
12115
|
+
setSummary(summaryData);
|
|
12116
|
+
} catch (err) {
|
|
12117
|
+
console.error("Error fetching workspace health:", err);
|
|
12118
|
+
setError(err);
|
|
12119
|
+
} finally {
|
|
12120
|
+
setLoading(false);
|
|
12121
|
+
}
|
|
12122
|
+
}, [filterOptions.lineId, filterOptions.companyId, filterOptions.status, filterOptions.searchTerm, filterOptions.sortBy, filterOptions.sortOrder]);
|
|
12123
|
+
const handleRealtimeUpdate = React19.useCallback(async (data) => {
|
|
12124
|
+
try {
|
|
12125
|
+
const [workspacesData, summaryData] = await Promise.all([
|
|
12126
|
+
workspaceHealthService.getWorkspaceHealthStatus(filterOptions),
|
|
12127
|
+
workspaceHealthService.getHealthSummary(filterOptions.lineId, filterOptions.companyId)
|
|
12128
|
+
]);
|
|
12129
|
+
setWorkspaces(workspacesData);
|
|
12130
|
+
setSummary(summaryData);
|
|
12131
|
+
} catch (err) {
|
|
12132
|
+
console.error("Error updating real-time health data:", err);
|
|
12133
|
+
}
|
|
12134
|
+
}, [filterOptions]);
|
|
12135
|
+
React19.useEffect(() => {
|
|
12136
|
+
fetchData();
|
|
12137
|
+
if (refreshInterval > 0) {
|
|
12138
|
+
refreshIntervalRef.current = setInterval(fetchData, 1e4);
|
|
12139
|
+
}
|
|
12140
|
+
if (enableRealtime) {
|
|
12141
|
+
unsubscribeRef.current = workspaceHealthService.subscribeToHealthUpdates(
|
|
12142
|
+
handleRealtimeUpdate,
|
|
12143
|
+
{ lineId: filterOptions.lineId, companyId: filterOptions.companyId }
|
|
12144
|
+
);
|
|
12145
|
+
}
|
|
12146
|
+
return () => {
|
|
12147
|
+
if (refreshIntervalRef.current) {
|
|
12148
|
+
clearInterval(refreshIntervalRef.current);
|
|
12149
|
+
}
|
|
12150
|
+
if (unsubscribeRef.current) {
|
|
12151
|
+
unsubscribeRef.current();
|
|
12152
|
+
}
|
|
12153
|
+
};
|
|
12154
|
+
}, [fetchData, enableRealtime, refreshInterval, handleRealtimeUpdate, filterOptions.lineId, filterOptions.companyId]);
|
|
12155
|
+
return {
|
|
12156
|
+
workspaces,
|
|
12157
|
+
summary,
|
|
12158
|
+
loading,
|
|
12159
|
+
error,
|
|
12160
|
+
refetch: fetchData
|
|
12161
|
+
};
|
|
12162
|
+
}
|
|
12163
|
+
function useWorkspaceHealthById(workspaceId, options = {}) {
|
|
12164
|
+
const [workspace, setWorkspace] = React19.useState(null);
|
|
12165
|
+
const [metrics2, setMetrics] = React19.useState(null);
|
|
12166
|
+
const [loading, setLoading] = React19.useState(true);
|
|
12167
|
+
const [error, setError] = React19.useState(null);
|
|
12168
|
+
const unsubscribeRef = React19.useRef(null);
|
|
12169
|
+
const refreshIntervalRef = React19.useRef(null);
|
|
12170
|
+
const { enableRealtime = true, refreshInterval = 3e4 } = options;
|
|
12171
|
+
const fetchData = React19.useCallback(async () => {
|
|
12172
|
+
if (!workspaceId) {
|
|
12173
|
+
setWorkspace(null);
|
|
12174
|
+
setMetrics(null);
|
|
12175
|
+
setLoading(false);
|
|
12176
|
+
return;
|
|
12177
|
+
}
|
|
12178
|
+
try {
|
|
12179
|
+
setLoading(true);
|
|
12180
|
+
setError(null);
|
|
12181
|
+
const [workspaceData, metricsData] = await Promise.all([
|
|
12182
|
+
workspaceHealthService.getWorkspaceHealthById(workspaceId),
|
|
12183
|
+
workspaceHealthService.getHealthMetrics(workspaceId)
|
|
12184
|
+
]);
|
|
12185
|
+
setWorkspace(workspaceData);
|
|
12186
|
+
setMetrics(metricsData);
|
|
12187
|
+
} catch (err) {
|
|
12188
|
+
console.error("Error fetching workspace health by ID:", err);
|
|
12189
|
+
setError(err);
|
|
12190
|
+
} finally {
|
|
12191
|
+
setLoading(false);
|
|
12192
|
+
}
|
|
12193
|
+
}, [workspaceId]);
|
|
12194
|
+
const handleRealtimeUpdate = React19.useCallback((data) => {
|
|
12195
|
+
if (data.workspace_id === workspaceId) {
|
|
12196
|
+
const updatedWorkspace = workspaceHealthService["processHealthStatus"](data);
|
|
12197
|
+
setWorkspace(updatedWorkspace);
|
|
12198
|
+
}
|
|
12199
|
+
}, [workspaceId]);
|
|
12200
|
+
React19.useEffect(() => {
|
|
12201
|
+
fetchData();
|
|
12202
|
+
if (refreshInterval > 0) {
|
|
12203
|
+
refreshIntervalRef.current = setInterval(fetchData, 1e4);
|
|
12204
|
+
}
|
|
12205
|
+
if (enableRealtime && workspaceId) {
|
|
12206
|
+
unsubscribeRef.current = workspaceHealthService.subscribeToHealthUpdates(
|
|
12207
|
+
handleRealtimeUpdate
|
|
12208
|
+
);
|
|
12209
|
+
}
|
|
12210
|
+
return () => {
|
|
12211
|
+
if (refreshIntervalRef.current) {
|
|
12212
|
+
clearInterval(refreshIntervalRef.current);
|
|
12213
|
+
}
|
|
12214
|
+
if (unsubscribeRef.current) {
|
|
12215
|
+
unsubscribeRef.current();
|
|
12216
|
+
}
|
|
12217
|
+
};
|
|
12218
|
+
}, [fetchData, enableRealtime, refreshInterval, handleRealtimeUpdate, workspaceId]);
|
|
12219
|
+
return {
|
|
12220
|
+
workspace,
|
|
12221
|
+
metrics: metrics2,
|
|
12222
|
+
loading,
|
|
12223
|
+
error,
|
|
12224
|
+
refetch: fetchData
|
|
12225
|
+
};
|
|
12226
|
+
}
|
|
12130
12227
|
function useDateFormatter() {
|
|
12131
12228
|
const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
|
|
12132
12229
|
const formatDate = React19.useCallback(
|
|
@@ -23314,8 +23411,7 @@ var ISTTimer = React19.memo(() => {
|
|
|
23314
23411
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
23315
23412
|
TimeDisplay2,
|
|
23316
23413
|
{
|
|
23317
|
-
variant: "minimal"
|
|
23318
|
-
className: "text-sm font-medium text-gray-700"
|
|
23414
|
+
variant: "minimal"
|
|
23319
23415
|
}
|
|
23320
23416
|
);
|
|
23321
23417
|
});
|
|
@@ -23347,6 +23443,7 @@ var CardFooter2 = (props) => {
|
|
|
23347
23443
|
};
|
|
23348
23444
|
var TicketHistory = ({ companyId }) => {
|
|
23349
23445
|
const { tickets, loading, error } = useTicketHistory(companyId);
|
|
23446
|
+
const [expandedTickets, setExpandedTickets] = React19.useState(/* @__PURE__ */ new Set());
|
|
23350
23447
|
const getCategoryIcon = (category) => {
|
|
23351
23448
|
switch (category) {
|
|
23352
23449
|
case "general":
|
|
@@ -23403,6 +23500,23 @@ var TicketHistory = ({ companyId }) => {
|
|
|
23403
23500
|
return "text-gray-600 bg-gray-50";
|
|
23404
23501
|
}
|
|
23405
23502
|
};
|
|
23503
|
+
const toggleTicketExpansion = (ticketId) => {
|
|
23504
|
+
setExpandedTickets((prev) => {
|
|
23505
|
+
const newSet = new Set(prev);
|
|
23506
|
+
if (newSet.has(ticketId)) {
|
|
23507
|
+
newSet.delete(ticketId);
|
|
23508
|
+
} else {
|
|
23509
|
+
newSet.add(ticketId);
|
|
23510
|
+
}
|
|
23511
|
+
return newSet;
|
|
23512
|
+
});
|
|
23513
|
+
};
|
|
23514
|
+
const isTicketExpanded = (ticketId) => {
|
|
23515
|
+
return expandedTickets.has(ticketId);
|
|
23516
|
+
};
|
|
23517
|
+
const shouldShowExpandButton = (description) => {
|
|
23518
|
+
return description.length > 100 || description.includes("\n");
|
|
23519
|
+
};
|
|
23406
23520
|
if (loading) {
|
|
23407
23521
|
return /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "h-full", children: [
|
|
23408
23522
|
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg font-semibold text-gray-800", children: "History of Tickets" }) }),
|
|
@@ -23443,12 +23557,14 @@ var TicketHistory = ({ companyId }) => {
|
|
|
23443
23557
|
tickets.map((ticket, index) => {
|
|
23444
23558
|
const CategoryIcon = getCategoryIcon(ticket.category);
|
|
23445
23559
|
const StatusIcon = getStatusIcon(ticket.status);
|
|
23560
|
+
const isExpanded = isTicketExpanded(ticket.id);
|
|
23561
|
+
const showExpandButton = shouldShowExpandButton(ticket.description);
|
|
23446
23562
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
23447
23563
|
motion.div,
|
|
23448
23564
|
{
|
|
23449
23565
|
initial: { opacity: 0, y: 10 },
|
|
23450
23566
|
animate: { opacity: 1, y: 0 },
|
|
23451
|
-
className: `border-b border-gray-100 p-4 hover:bg-gray-50 transition-colors
|
|
23567
|
+
className: `border-b border-gray-100 p-4 hover:bg-gray-50 transition-colors ${index === 0 ? "border-t-0" : ""}`,
|
|
23452
23568
|
children: [
|
|
23453
23569
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start justify-between mb-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [
|
|
23454
23570
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-1 bg-gray-100 rounded", children: /* @__PURE__ */ jsxRuntime.jsx(CategoryIcon, { className: "h-3 w-3 text-gray-600" }) }),
|
|
@@ -23459,9 +23575,42 @@ var TicketHistory = ({ companyId }) => {
|
|
|
23459
23575
|
/* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { className: "h-2.5 w-2.5 mr-1" }),
|
|
23460
23576
|
ticket.status === "submitted" ? "New" : ticket.status.replace("_", " ")
|
|
23461
23577
|
] }),
|
|
23462
|
-
/* @__PURE__ */ jsxRuntime.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 })
|
|
23578
|
+
/* @__PURE__ */ jsxRuntime.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 }),
|
|
23579
|
+
ticket.serviced && /* @__PURE__ */ jsxRuntime.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: [
|
|
23580
|
+
/* @__PURE__ */ jsxRuntime.jsx(solid.CheckIcon, { className: "h-2.5 w-2.5 mr-1" }),
|
|
23581
|
+
"Serviced"
|
|
23582
|
+
] })
|
|
23583
|
+
] }),
|
|
23584
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3", children: [
|
|
23585
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
23586
|
+
motion.div,
|
|
23587
|
+
{
|
|
23588
|
+
initial: { opacity: 0 },
|
|
23589
|
+
animate: { opacity: 1 },
|
|
23590
|
+
exit: { opacity: 0 },
|
|
23591
|
+
transition: { duration: 0.2 },
|
|
23592
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-xs text-gray-600 leading-relaxed whitespace-pre-wrap ${!isExpanded && showExpandButton ? "line-clamp-2" : ""}`, children: ticket.description })
|
|
23593
|
+
},
|
|
23594
|
+
isExpanded ? "expanded" : "collapsed"
|
|
23595
|
+
) }),
|
|
23596
|
+
showExpandButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
23597
|
+
"button",
|
|
23598
|
+
{
|
|
23599
|
+
onClick: (e) => {
|
|
23600
|
+
e.stopPropagation();
|
|
23601
|
+
toggleTicketExpansion(ticket.id);
|
|
23602
|
+
},
|
|
23603
|
+
className: "mt-2 flex items-center gap-1 text-xs text-blue-600 hover:text-blue-700 font-medium transition-colors",
|
|
23604
|
+
children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
23605
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.ChevronUpIcon, { className: "h-3 w-3" }),
|
|
23606
|
+
"Show less"
|
|
23607
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
23608
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.ChevronDownIcon, { className: "h-3 w-3" }),
|
|
23609
|
+
"Show more"
|
|
23610
|
+
] })
|
|
23611
|
+
}
|
|
23612
|
+
)
|
|
23463
23613
|
] }),
|
|
23464
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-600 mb-3 line-clamp-2 leading-relaxed", children: ticket.description }),
|
|
23465
23614
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
|
|
23466
23615
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500 capitalize font-medium", children: ticket.category }),
|
|
23467
23616
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-gray-500", children: [
|
|
@@ -23483,6 +23632,165 @@ var TicketHistory = ({ companyId }) => {
|
|
|
23483
23632
|
] });
|
|
23484
23633
|
};
|
|
23485
23634
|
var TicketHistory_default = TicketHistory;
|
|
23635
|
+
var HealthStatusIndicator = ({
|
|
23636
|
+
status,
|
|
23637
|
+
lastUpdated,
|
|
23638
|
+
showLabel = false,
|
|
23639
|
+
showTime = true,
|
|
23640
|
+
size = "md",
|
|
23641
|
+
className = "",
|
|
23642
|
+
inline = true,
|
|
23643
|
+
pulse = true
|
|
23644
|
+
}) => {
|
|
23645
|
+
const getStatusConfig = () => {
|
|
23646
|
+
switch (status) {
|
|
23647
|
+
case "healthy":
|
|
23648
|
+
return {
|
|
23649
|
+
color: "text-green-500",
|
|
23650
|
+
bgColor: "bg-green-500",
|
|
23651
|
+
borderColor: "border-green-500",
|
|
23652
|
+
label: "Healthy",
|
|
23653
|
+
icon: lucideReact.CheckCircle,
|
|
23654
|
+
shouldPulse: pulse
|
|
23655
|
+
};
|
|
23656
|
+
case "unhealthy":
|
|
23657
|
+
return {
|
|
23658
|
+
color: "text-red-500",
|
|
23659
|
+
bgColor: "bg-red-500",
|
|
23660
|
+
borderColor: "border-red-500",
|
|
23661
|
+
label: "Unhealthy",
|
|
23662
|
+
icon: lucideReact.XCircle,
|
|
23663
|
+
shouldPulse: false
|
|
23664
|
+
};
|
|
23665
|
+
case "warning":
|
|
23666
|
+
return {
|
|
23667
|
+
color: "text-yellow-500",
|
|
23668
|
+
bgColor: "bg-yellow-500",
|
|
23669
|
+
borderColor: "border-yellow-500",
|
|
23670
|
+
label: "Warning",
|
|
23671
|
+
icon: lucideReact.AlertTriangle,
|
|
23672
|
+
shouldPulse: true
|
|
23673
|
+
};
|
|
23674
|
+
case "unknown":
|
|
23675
|
+
default:
|
|
23676
|
+
return {
|
|
23677
|
+
color: "text-gray-400",
|
|
23678
|
+
bgColor: "bg-gray-400",
|
|
23679
|
+
borderColor: "border-gray-400",
|
|
23680
|
+
label: "Unknown",
|
|
23681
|
+
icon: lucideReact.Activity,
|
|
23682
|
+
shouldPulse: false
|
|
23683
|
+
};
|
|
23684
|
+
}
|
|
23685
|
+
};
|
|
23686
|
+
const config = getStatusConfig();
|
|
23687
|
+
config.icon;
|
|
23688
|
+
const sizeClasses = {
|
|
23689
|
+
sm: {
|
|
23690
|
+
dot: "h-2 w-2",
|
|
23691
|
+
icon: "h-3 w-3",
|
|
23692
|
+
text: "text-xs",
|
|
23693
|
+
spacing: "gap-1"
|
|
23694
|
+
},
|
|
23695
|
+
md: {
|
|
23696
|
+
dot: "h-3 w-3",
|
|
23697
|
+
icon: "h-4 w-4",
|
|
23698
|
+
text: "text-sm",
|
|
23699
|
+
spacing: "gap-1.5"
|
|
23700
|
+
},
|
|
23701
|
+
lg: {
|
|
23702
|
+
dot: "h-4 w-4",
|
|
23703
|
+
icon: "h-5 w-5",
|
|
23704
|
+
text: "text-base",
|
|
23705
|
+
spacing: "gap-2"
|
|
23706
|
+
}
|
|
23707
|
+
};
|
|
23708
|
+
const currentSize = sizeClasses[size];
|
|
23709
|
+
const containerClasses = clsx(
|
|
23710
|
+
"flex items-center",
|
|
23711
|
+
currentSize.spacing,
|
|
23712
|
+
inline ? "inline-flex" : "flex",
|
|
23713
|
+
className
|
|
23714
|
+
);
|
|
23715
|
+
const dotClasses = clsx(
|
|
23716
|
+
"rounded-full",
|
|
23717
|
+
currentSize.dot,
|
|
23718
|
+
config.bgColor,
|
|
23719
|
+
config.shouldPulse && "animate-pulse"
|
|
23720
|
+
);
|
|
23721
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: containerClasses, children: [
|
|
23722
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
|
|
23723
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: dotClasses }),
|
|
23724
|
+
config.shouldPulse && status === "healthy" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
23725
|
+
"div",
|
|
23726
|
+
{
|
|
23727
|
+
className: clsx(
|
|
23728
|
+
"absolute rounded-full opacity-25",
|
|
23729
|
+
currentSize.dot,
|
|
23730
|
+
config.bgColor,
|
|
23731
|
+
"animate-ping"
|
|
23732
|
+
)
|
|
23733
|
+
}
|
|
23734
|
+
)
|
|
23735
|
+
] }),
|
|
23736
|
+
showLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(currentSize.text, "font-medium", config.color), children: config.label }),
|
|
23737
|
+
showTime && lastUpdated && /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(currentSize.text, "text-gray-500 dark:text-gray-400"), children: lastUpdated })
|
|
23738
|
+
] });
|
|
23739
|
+
};
|
|
23740
|
+
var DetailedHealthStatus = ({
|
|
23741
|
+
workspaceName,
|
|
23742
|
+
lineName,
|
|
23743
|
+
consecutiveMisses,
|
|
23744
|
+
showDetails = true,
|
|
23745
|
+
...indicatorProps
|
|
23746
|
+
}) => {
|
|
23747
|
+
const getStatusConfig = () => {
|
|
23748
|
+
switch (indicatorProps.status) {
|
|
23749
|
+
case "healthy":
|
|
23750
|
+
return {
|
|
23751
|
+
bgClass: "bg-green-50 dark:bg-green-900/20",
|
|
23752
|
+
borderClass: "border-green-200 dark:border-green-800"
|
|
23753
|
+
};
|
|
23754
|
+
case "unhealthy":
|
|
23755
|
+
return {
|
|
23756
|
+
bgClass: "bg-red-50 dark:bg-red-900/20",
|
|
23757
|
+
borderClass: "border-red-200 dark:border-red-800"
|
|
23758
|
+
};
|
|
23759
|
+
case "warning":
|
|
23760
|
+
return {
|
|
23761
|
+
bgClass: "bg-yellow-50 dark:bg-yellow-900/20",
|
|
23762
|
+
borderClass: "border-yellow-200 dark:border-yellow-800"
|
|
23763
|
+
};
|
|
23764
|
+
default:
|
|
23765
|
+
return {
|
|
23766
|
+
bgClass: "bg-gray-50 dark:bg-gray-900/20",
|
|
23767
|
+
borderClass: "border-gray-200 dark:border-gray-800"
|
|
23768
|
+
};
|
|
23769
|
+
}
|
|
23770
|
+
};
|
|
23771
|
+
const config = getStatusConfig();
|
|
23772
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
23773
|
+
"div",
|
|
23774
|
+
{
|
|
23775
|
+
className: clsx(
|
|
23776
|
+
"rounded-lg border p-3",
|
|
23777
|
+
config.bgClass,
|
|
23778
|
+
config.borderClass
|
|
23779
|
+
),
|
|
23780
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between", children: [
|
|
23781
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
23782
|
+
showDetails && workspaceName && /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: workspaceName }),
|
|
23783
|
+
showDetails && lineName && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5", children: lineName }),
|
|
23784
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(HealthStatusIndicator, { ...indicatorProps, showLabel: true }) })
|
|
23785
|
+
] }),
|
|
23786
|
+
showDetails && consecutiveMisses !== void 0 && consecutiveMisses > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-3 text-right", children: [
|
|
23787
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: "Missed" }),
|
|
23788
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-bold text-gray-900 dark:text-gray-100", children: consecutiveMisses })
|
|
23789
|
+
] })
|
|
23790
|
+
] })
|
|
23791
|
+
}
|
|
23792
|
+
);
|
|
23793
|
+
};
|
|
23486
23794
|
var LinePdfExportButton = ({
|
|
23487
23795
|
targetElement,
|
|
23488
23796
|
fileName = "line-export",
|
|
@@ -24605,6 +24913,8 @@ var WorkspaceCard = ({
|
|
|
24605
24913
|
cycleTime,
|
|
24606
24914
|
operators,
|
|
24607
24915
|
status = "normal",
|
|
24916
|
+
healthStatus,
|
|
24917
|
+
healthLastUpdated,
|
|
24608
24918
|
onCardClick,
|
|
24609
24919
|
headerActions,
|
|
24610
24920
|
footerContent,
|
|
@@ -24697,6 +25007,19 @@ var WorkspaceCard = ({
|
|
|
24697
25007
|
] })
|
|
24698
25008
|
] })
|
|
24699
25009
|
] }),
|
|
25010
|
+
healthStatus && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-2 mt-auto border-t dark:border-gray-700", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
25011
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "System Health" }),
|
|
25012
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
25013
|
+
HealthStatusIndicator,
|
|
25014
|
+
{
|
|
25015
|
+
status: healthStatus,
|
|
25016
|
+
lastUpdated: healthLastUpdated,
|
|
25017
|
+
showTime: false,
|
|
25018
|
+
size: "sm",
|
|
25019
|
+
pulse: healthStatus === "healthy"
|
|
25020
|
+
}
|
|
25021
|
+
)
|
|
25022
|
+
] }) }),
|
|
24700
25023
|
chartData && chartData.data && chartData.data.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
24701
25024
|
"div",
|
|
24702
25025
|
{
|
|
@@ -26234,6 +26557,315 @@ var VideoPlayer = React19__namespace.default.forwardRef(({
|
|
|
26234
26557
|
] });
|
|
26235
26558
|
});
|
|
26236
26559
|
VideoPlayer.displayName = "VideoPlayer";
|
|
26560
|
+
var BackButton = ({
|
|
26561
|
+
onClick,
|
|
26562
|
+
text = "Back",
|
|
26563
|
+
className,
|
|
26564
|
+
size = "default",
|
|
26565
|
+
disabled = false,
|
|
26566
|
+
"aria-label": ariaLabel
|
|
26567
|
+
}) => {
|
|
26568
|
+
const sizeClasses = {
|
|
26569
|
+
sm: {
|
|
26570
|
+
container: "gap-1 px-2 py-1.5",
|
|
26571
|
+
icon: "w-3.5 h-3.5",
|
|
26572
|
+
text: "text-xs"
|
|
26573
|
+
},
|
|
26574
|
+
default: {
|
|
26575
|
+
container: "gap-2 px-3 py-2",
|
|
26576
|
+
icon: "w-4 h-4",
|
|
26577
|
+
text: "text-sm"
|
|
26578
|
+
},
|
|
26579
|
+
lg: {
|
|
26580
|
+
container: "gap-2 px-4 py-2.5",
|
|
26581
|
+
icon: "w-5 h-5",
|
|
26582
|
+
text: "text-base"
|
|
26583
|
+
}
|
|
26584
|
+
};
|
|
26585
|
+
const currentSize = sizeClasses[size];
|
|
26586
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
26587
|
+
"button",
|
|
26588
|
+
{
|
|
26589
|
+
onClick,
|
|
26590
|
+
disabled,
|
|
26591
|
+
"aria-label": ariaLabel || `${text} button`,
|
|
26592
|
+
className: cn(
|
|
26593
|
+
// Base styles
|
|
26594
|
+
"flex items-center font-medium rounded-lg transition-all duration-200",
|
|
26595
|
+
// Size-specific styles
|
|
26596
|
+
currentSize.container,
|
|
26597
|
+
// Color and interaction styles
|
|
26598
|
+
disabled ? "text-gray-400 cursor-not-allowed" : "text-gray-600 hover:text-gray-900 hover:bg-gray-50 active:bg-gray-100",
|
|
26599
|
+
// Focus styles for accessibility
|
|
26600
|
+
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
|
|
26601
|
+
className
|
|
26602
|
+
),
|
|
26603
|
+
children: [
|
|
26604
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26605
|
+
lucideReact.ArrowLeft,
|
|
26606
|
+
{
|
|
26607
|
+
className: cn(
|
|
26608
|
+
"flex-shrink-0",
|
|
26609
|
+
currentSize.icon,
|
|
26610
|
+
disabled && "opacity-50"
|
|
26611
|
+
)
|
|
26612
|
+
}
|
|
26613
|
+
),
|
|
26614
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(
|
|
26615
|
+
"font-medium select-none",
|
|
26616
|
+
currentSize.text,
|
|
26617
|
+
disabled && "opacity-50"
|
|
26618
|
+
), children: text })
|
|
26619
|
+
]
|
|
26620
|
+
}
|
|
26621
|
+
);
|
|
26622
|
+
};
|
|
26623
|
+
var BackButtonMinimal = ({
|
|
26624
|
+
onClick,
|
|
26625
|
+
text = "Back",
|
|
26626
|
+
className,
|
|
26627
|
+
size = "default",
|
|
26628
|
+
disabled = false,
|
|
26629
|
+
"aria-label": ariaLabel
|
|
26630
|
+
}) => {
|
|
26631
|
+
const sizeClasses = {
|
|
26632
|
+
sm: {
|
|
26633
|
+
icon: "w-3.5 h-3.5",
|
|
26634
|
+
text: "text-xs ml-1"
|
|
26635
|
+
},
|
|
26636
|
+
default: {
|
|
26637
|
+
icon: "w-4 h-4",
|
|
26638
|
+
text: "text-sm ml-2"
|
|
26639
|
+
},
|
|
26640
|
+
lg: {
|
|
26641
|
+
icon: "w-5 h-5",
|
|
26642
|
+
text: "text-base ml-2"
|
|
26643
|
+
}
|
|
26644
|
+
};
|
|
26645
|
+
const currentSize = sizeClasses[size];
|
|
26646
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
26647
|
+
"button",
|
|
26648
|
+
{
|
|
26649
|
+
onClick,
|
|
26650
|
+
disabled,
|
|
26651
|
+
"aria-label": ariaLabel || `${text} button`,
|
|
26652
|
+
className: cn(
|
|
26653
|
+
// Base styles - minimal padding for tight spaces
|
|
26654
|
+
"flex items-center transition-colors duration-200",
|
|
26655
|
+
// Color and interaction styles
|
|
26656
|
+
disabled ? "text-gray-400 cursor-not-allowed" : "text-gray-600 hover:text-gray-900",
|
|
26657
|
+
// Focus styles for accessibility
|
|
26658
|
+
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded",
|
|
26659
|
+
className
|
|
26660
|
+
),
|
|
26661
|
+
children: [
|
|
26662
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26663
|
+
lucideReact.ArrowLeft,
|
|
26664
|
+
{
|
|
26665
|
+
className: cn(
|
|
26666
|
+
"flex-shrink-0",
|
|
26667
|
+
currentSize.icon,
|
|
26668
|
+
disabled && "opacity-50"
|
|
26669
|
+
)
|
|
26670
|
+
}
|
|
26671
|
+
),
|
|
26672
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(
|
|
26673
|
+
"font-medium select-none",
|
|
26674
|
+
currentSize.text,
|
|
26675
|
+
disabled && "opacity-50"
|
|
26676
|
+
), children: text })
|
|
26677
|
+
]
|
|
26678
|
+
}
|
|
26679
|
+
);
|
|
26680
|
+
};
|
|
26681
|
+
var InlineEditableText = ({
|
|
26682
|
+
value,
|
|
26683
|
+
onSave,
|
|
26684
|
+
placeholder = "Click to edit",
|
|
26685
|
+
className = "",
|
|
26686
|
+
editIconClassName = "",
|
|
26687
|
+
inputClassName = "",
|
|
26688
|
+
debounceDelay = 750,
|
|
26689
|
+
// 750ms for quick auto-save
|
|
26690
|
+
disabled = false
|
|
26691
|
+
}) => {
|
|
26692
|
+
const [isEditing, setIsEditing] = React19.useState(false);
|
|
26693
|
+
const [editValue, setEditValue] = React19.useState(value);
|
|
26694
|
+
const [saveStatus, setSaveStatus] = React19.useState("idle");
|
|
26695
|
+
const [lastSavedValue, setLastSavedValue] = React19.useState(value);
|
|
26696
|
+
const [hasUnsavedChanges, setHasUnsavedChanges] = React19.useState(false);
|
|
26697
|
+
const inputRef = React19.useRef(null);
|
|
26698
|
+
const containerRef = React19.useRef(null);
|
|
26699
|
+
const debounceTimeout = React19.useRef(void 0);
|
|
26700
|
+
const saveStatusTimeout = React19.useRef(void 0);
|
|
26701
|
+
React19.useEffect(() => {
|
|
26702
|
+
if (!isEditing) {
|
|
26703
|
+
setEditValue(value);
|
|
26704
|
+
setLastSavedValue(value);
|
|
26705
|
+
}
|
|
26706
|
+
}, [value, isEditing]);
|
|
26707
|
+
React19.useEffect(() => {
|
|
26708
|
+
if (isEditing && inputRef.current) {
|
|
26709
|
+
requestAnimationFrame(() => {
|
|
26710
|
+
inputRef.current?.focus();
|
|
26711
|
+
inputRef.current?.select();
|
|
26712
|
+
});
|
|
26713
|
+
}
|
|
26714
|
+
}, [isEditing]);
|
|
26715
|
+
React19.useEffect(() => {
|
|
26716
|
+
return () => {
|
|
26717
|
+
if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
|
|
26718
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26719
|
+
};
|
|
26720
|
+
}, []);
|
|
26721
|
+
const performSave = React19.useCallback(async (valueToSave, shouldClose = false) => {
|
|
26722
|
+
const trimmedValue = valueToSave.trim();
|
|
26723
|
+
if (trimmedValue === lastSavedValue.trim()) {
|
|
26724
|
+
setHasUnsavedChanges(false);
|
|
26725
|
+
if (shouldClose) {
|
|
26726
|
+
setIsEditing(false);
|
|
26727
|
+
setSaveStatus("idle");
|
|
26728
|
+
}
|
|
26729
|
+
return;
|
|
26730
|
+
}
|
|
26731
|
+
setSaveStatus("saving");
|
|
26732
|
+
setHasUnsavedChanges(false);
|
|
26733
|
+
try {
|
|
26734
|
+
await onSave(trimmedValue);
|
|
26735
|
+
setLastSavedValue(trimmedValue);
|
|
26736
|
+
setSaveStatus("saved");
|
|
26737
|
+
if (!shouldClose && inputRef.current) {
|
|
26738
|
+
requestAnimationFrame(() => {
|
|
26739
|
+
inputRef.current?.focus();
|
|
26740
|
+
});
|
|
26741
|
+
}
|
|
26742
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26743
|
+
saveStatusTimeout.current = setTimeout(() => {
|
|
26744
|
+
setSaveStatus("idle");
|
|
26745
|
+
}, 2e3);
|
|
26746
|
+
if (shouldClose) {
|
|
26747
|
+
setIsEditing(false);
|
|
26748
|
+
}
|
|
26749
|
+
} catch (error) {
|
|
26750
|
+
console.error("Failed to save:", error);
|
|
26751
|
+
setSaveStatus("error");
|
|
26752
|
+
setHasUnsavedChanges(true);
|
|
26753
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26754
|
+
saveStatusTimeout.current = setTimeout(() => {
|
|
26755
|
+
setSaveStatus("idle");
|
|
26756
|
+
}, 3e3);
|
|
26757
|
+
if (!shouldClose && inputRef.current) {
|
|
26758
|
+
requestAnimationFrame(() => {
|
|
26759
|
+
inputRef.current?.focus();
|
|
26760
|
+
});
|
|
26761
|
+
}
|
|
26762
|
+
}
|
|
26763
|
+
}, [lastSavedValue, onSave]);
|
|
26764
|
+
React19.useEffect(() => {
|
|
26765
|
+
const handleClickOutside = (event) => {
|
|
26766
|
+
if (isEditing && containerRef.current && !containerRef.current.contains(event.target)) {
|
|
26767
|
+
if (debounceTimeout.current) {
|
|
26768
|
+
clearTimeout(debounceTimeout.current);
|
|
26769
|
+
}
|
|
26770
|
+
performSave(editValue, true);
|
|
26771
|
+
}
|
|
26772
|
+
};
|
|
26773
|
+
if (isEditing) {
|
|
26774
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
26775
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
26776
|
+
}
|
|
26777
|
+
}, [isEditing, editValue, performSave]);
|
|
26778
|
+
const handleInputChange = (e) => {
|
|
26779
|
+
const newValue = e.target.value;
|
|
26780
|
+
setEditValue(newValue);
|
|
26781
|
+
if (newValue.trim() !== lastSavedValue.trim()) {
|
|
26782
|
+
setHasUnsavedChanges(true);
|
|
26783
|
+
setSaveStatus("idle");
|
|
26784
|
+
} else {
|
|
26785
|
+
setHasUnsavedChanges(false);
|
|
26786
|
+
}
|
|
26787
|
+
if (debounceTimeout.current) {
|
|
26788
|
+
clearTimeout(debounceTimeout.current);
|
|
26789
|
+
}
|
|
26790
|
+
if (newValue.trim() !== lastSavedValue.trim()) {
|
|
26791
|
+
debounceTimeout.current = setTimeout(() => {
|
|
26792
|
+
performSave(newValue, false);
|
|
26793
|
+
}, debounceDelay);
|
|
26794
|
+
}
|
|
26795
|
+
};
|
|
26796
|
+
const handleKeyDown = (e) => {
|
|
26797
|
+
if (e.key === "Enter") {
|
|
26798
|
+
e.preventDefault();
|
|
26799
|
+
if (debounceTimeout.current) {
|
|
26800
|
+
clearTimeout(debounceTimeout.current);
|
|
26801
|
+
}
|
|
26802
|
+
performSave(editValue, true);
|
|
26803
|
+
} else if (e.key === "Escape") {
|
|
26804
|
+
e.preventDefault();
|
|
26805
|
+
if (debounceTimeout.current) {
|
|
26806
|
+
clearTimeout(debounceTimeout.current);
|
|
26807
|
+
}
|
|
26808
|
+
setEditValue(lastSavedValue);
|
|
26809
|
+
setHasUnsavedChanges(false);
|
|
26810
|
+
setSaveStatus("idle");
|
|
26811
|
+
setIsEditing(false);
|
|
26812
|
+
}
|
|
26813
|
+
};
|
|
26814
|
+
const handleClick = () => {
|
|
26815
|
+
if (!disabled && !isEditing) {
|
|
26816
|
+
setIsEditing(true);
|
|
26817
|
+
setSaveStatus("idle");
|
|
26818
|
+
}
|
|
26819
|
+
};
|
|
26820
|
+
if (isEditing) {
|
|
26821
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "inline-flex items-center gap-1.5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
26822
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26823
|
+
"input",
|
|
26824
|
+
{
|
|
26825
|
+
ref: inputRef,
|
|
26826
|
+
type: "text",
|
|
26827
|
+
value: editValue,
|
|
26828
|
+
onChange: handleInputChange,
|
|
26829
|
+
onKeyDown: handleKeyDown,
|
|
26830
|
+
className: `px-2 py-1 pr-7 text-sm border rounded-md transition-colors duration-200
|
|
26831
|
+
${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"}
|
|
26832
|
+
focus:outline-none focus:ring-2 bg-white
|
|
26833
|
+
${inputClassName}`,
|
|
26834
|
+
placeholder,
|
|
26835
|
+
autoComplete: "off"
|
|
26836
|
+
}
|
|
26837
|
+
),
|
|
26838
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-1.5 top-1/2 -translate-y-1/2", children: [
|
|
26839
|
+
saveStatus === "saving" && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "w-3.5 h-3.5 text-blue-500 animate-spin" }),
|
|
26840
|
+
saveStatus === "saved" && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-3.5 h-3.5 text-green-500" }),
|
|
26841
|
+
saveStatus === "error" && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-3.5 h-3.5 text-red-500" }),
|
|
26842
|
+
saveStatus === "idle" && hasUnsavedChanges && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 bg-yellow-400 rounded-full" })
|
|
26843
|
+
] })
|
|
26844
|
+
] }) });
|
|
26845
|
+
}
|
|
26846
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
26847
|
+
"div",
|
|
26848
|
+
{
|
|
26849
|
+
onClick: handleClick,
|
|
26850
|
+
className: `inline-flex items-center gap-1.5 cursor-pointer px-2 py-1 rounded-md
|
|
26851
|
+
transition-all duration-200 hover:bg-gray-50 group
|
|
26852
|
+
${disabled ? "cursor-not-allowed opacity-50" : ""}
|
|
26853
|
+
${className}`,
|
|
26854
|
+
children: [
|
|
26855
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-900", children: editValue || placeholder }),
|
|
26856
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26857
|
+
lucideReact.Edit2,
|
|
26858
|
+
{
|
|
26859
|
+
className: `w-3.5 h-3.5 text-gray-400 transition-opacity duration-200
|
|
26860
|
+
opacity-0 group-hover:opacity-100
|
|
26861
|
+
${disabled ? "hidden" : ""}
|
|
26862
|
+
${editIconClassName}`
|
|
26863
|
+
}
|
|
26864
|
+
)
|
|
26865
|
+
]
|
|
26866
|
+
}
|
|
26867
|
+
);
|
|
26868
|
+
};
|
|
26237
26869
|
var BottlenecksContent = ({
|
|
26238
26870
|
workspaceId,
|
|
26239
26871
|
workspaceName,
|
|
@@ -26253,7 +26885,6 @@ var BottlenecksContent = ({
|
|
|
26253
26885
|
const videoRef = React19.useRef(null);
|
|
26254
26886
|
const timestampFilterRef = React19.useRef(null);
|
|
26255
26887
|
const initialFilter = sopCategories && sopCategories.length > 0 ? sopCategories[0].id : "low_value";
|
|
26256
|
-
const videoIndexRef = React19.useRef(null);
|
|
26257
26888
|
const currentIndexRef = React19.useRef(0);
|
|
26258
26889
|
const activeFilterRef = React19.useRef(initialFilter);
|
|
26259
26890
|
const isMountedRef = React19.useRef(true);
|
|
@@ -26271,15 +26902,6 @@ var BottlenecksContent = ({
|
|
|
26271
26902
|
const [isNavigating, setIsNavigating] = React19.useState(false);
|
|
26272
26903
|
const [error, setError] = React19.useState(null);
|
|
26273
26904
|
const [clipCounts, setClipCounts] = React19.useState({});
|
|
26274
|
-
const [videoIndex, setVideoIndex] = React19.useState(null);
|
|
26275
|
-
const updateVideoIndex = React19.useCallback((newIndex) => {
|
|
26276
|
-
console.log(`[BottlenecksContent] Updating video index - ID: ${newIndex?._debugId || "NO_ID"}, total videos: ${newIndex?.allVideos.length || 0}, categories: ${newIndex?.byCategory.size || 0}`);
|
|
26277
|
-
if (newIndex) {
|
|
26278
|
-
console.log(`[BottlenecksContent] VideoIndex categories: [${Array.from(newIndex.byCategory.keys()).join(", ")}]`);
|
|
26279
|
-
}
|
|
26280
|
-
setVideoIndex(newIndex);
|
|
26281
|
-
videoIndexRef.current = newIndex;
|
|
26282
|
-
}, []);
|
|
26283
26905
|
const updateActiveFilter = React19.useCallback((newFilter) => {
|
|
26284
26906
|
console.log(`[BottlenecksContent] Updating active filter: ${activeFilterRef.current} -> ${newFilter}`);
|
|
26285
26907
|
setActiveFilter(newFilter);
|
|
@@ -26340,7 +26962,8 @@ var BottlenecksContent = ({
|
|
|
26340
26962
|
date: date || getOperationalDate(),
|
|
26341
26963
|
shift: effectiveShift,
|
|
26342
26964
|
enabled: !!workspaceId && !!s3ClipsService,
|
|
26343
|
-
buildIndex:
|
|
26965
|
+
buildIndex: false
|
|
26966
|
+
// Disabled to reduce S3 costs - use pagination instead
|
|
26344
26967
|
});
|
|
26345
26968
|
const fetchClipCounts = React19.useCallback(async () => {
|
|
26346
26969
|
if (!workspaceId || !s3ClipsService || !dashboardConfig?.s3Config || !isMountedRef.current) return;
|
|
@@ -26359,7 +26982,6 @@ var BottlenecksContent = ({
|
|
|
26359
26982
|
if (cachedResult) {
|
|
26360
26983
|
console.log(`[BottlenecksContent] Using cached clip counts`);
|
|
26361
26984
|
updateClipCounts(cachedResult.counts);
|
|
26362
|
-
updateVideoIndex(cachedResult.videoIndex);
|
|
26363
26985
|
setIsLoading(false);
|
|
26364
26986
|
setHasInitialLoad(true);
|
|
26365
26987
|
return;
|
|
@@ -26368,19 +26990,13 @@ var BottlenecksContent = ({
|
|
|
26368
26990
|
const fullResult = await s3ClipsService.getClipCounts(
|
|
26369
26991
|
workspaceId,
|
|
26370
26992
|
operationalDate,
|
|
26371
|
-
shiftStr
|
|
26372
|
-
|
|
26373
|
-
// Build index
|
|
26993
|
+
shiftStr
|
|
26994
|
+
// Don't build index - use pagination for cost efficiency
|
|
26374
26995
|
);
|
|
26375
|
-
if (fullResult
|
|
26376
|
-
updateClipCounts(fullResult.counts);
|
|
26377
|
-
updateVideoIndex(fullResult.videoIndex);
|
|
26378
|
-
await smartVideoCache.setClipCounts(cacheKey, fullResult, 5);
|
|
26379
|
-
console.log(`[BottlenecksContent] Fetched and cached clip counts with ${fullResult.videoIndex.allVideos.length} videos`);
|
|
26380
|
-
} else if (fullResult) {
|
|
26996
|
+
if (fullResult) {
|
|
26381
26997
|
const counts = fullResult;
|
|
26382
26998
|
updateClipCounts(counts);
|
|
26383
|
-
console.log(`[BottlenecksContent] Fetched clip counts
|
|
26999
|
+
console.log(`[BottlenecksContent] Fetched and cached clip counts`);
|
|
26384
27000
|
}
|
|
26385
27001
|
setIsLoading(false);
|
|
26386
27002
|
setHasInitialLoad(true);
|
|
@@ -26393,7 +27009,7 @@ var BottlenecksContent = ({
|
|
|
26393
27009
|
} finally {
|
|
26394
27010
|
fetchInProgressRef.current.delete(operationKey);
|
|
26395
27011
|
}
|
|
26396
|
-
}, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts
|
|
27012
|
+
}, [workspaceId, date, s3ClipsService, effectiveShift, dashboardConfig, updateClipCounts]);
|
|
26397
27013
|
const loadingCategoryRef = React19.useRef(null);
|
|
26398
27014
|
const videoRetryCountRef = React19.useRef(0);
|
|
26399
27015
|
const loadingVideosRef = React19.useRef(/* @__PURE__ */ new Set());
|
|
@@ -26401,7 +27017,6 @@ var BottlenecksContent = ({
|
|
|
26401
27017
|
const ensureVideosLoaded = React19.useCallback(async (centerIndex) => {
|
|
26402
27018
|
if (!s3ClipsService || !workspaceId || !isMountedRef.current) return;
|
|
26403
27019
|
const currentFilter = activeFilterRef.current;
|
|
26404
|
-
const currentVideoIndex = videoIndexRef.current;
|
|
26405
27020
|
let effectiveFilter = currentFilter;
|
|
26406
27021
|
if (sopCategories && sopCategories.length > 0) {
|
|
26407
27022
|
const category = sopCategories.find((cat) => cat.id === currentFilter);
|
|
@@ -26428,17 +27043,6 @@ var BottlenecksContent = ({
|
|
|
26428
27043
|
const loadPromises = indicesToLoad.map(async (index) => {
|
|
26429
27044
|
try {
|
|
26430
27045
|
let video = null;
|
|
26431
|
-
if (currentVideoIndex && currentVideoIndex.byCategory && currentVideoIndex.allVideos.length > 0) {
|
|
26432
|
-
video = await s3ClipsService.getVideoFromIndex(
|
|
26433
|
-
currentVideoIndex,
|
|
26434
|
-
effectiveFilter,
|
|
26435
|
-
index,
|
|
26436
|
-
true,
|
|
26437
|
-
// includeCycleTime - OK for preloading
|
|
26438
|
-
false
|
|
26439
|
-
// includeMetadata - NO metadata during bulk preloading to prevent flooding
|
|
26440
|
-
);
|
|
26441
|
-
}
|
|
26442
27046
|
if (!video) {
|
|
26443
27047
|
const operationalDate = date || getOperationalDate();
|
|
26444
27048
|
const shiftStr = effectiveShift;
|
|
@@ -26491,12 +27095,11 @@ var BottlenecksContent = ({
|
|
|
26491
27095
|
try {
|
|
26492
27096
|
const operationalDate = date || getOperationalDate();
|
|
26493
27097
|
const shiftStr = effectiveShift;
|
|
26494
|
-
if (!clipCounts[targetCategory]
|
|
27098
|
+
if (!clipCounts[targetCategory]) {
|
|
26495
27099
|
const cacheKey = `clip-counts:${workspaceId}:${operationalDate}:${shiftStr}`;
|
|
26496
27100
|
const cachedResult = await smartVideoCache.getClipCounts(cacheKey);
|
|
26497
27101
|
if (cachedResult && cachedResult.counts[targetCategory] > 0) {
|
|
26498
27102
|
updateClipCounts(cachedResult.counts);
|
|
26499
|
-
updateVideoIndex(cachedResult.videoIndex);
|
|
26500
27103
|
setHasInitialLoad(true);
|
|
26501
27104
|
console.log(`[BottlenecksContent] Used cached data for loadFirstVideoForCategory - ${targetCategory}`);
|
|
26502
27105
|
}
|
|
@@ -26528,11 +27131,14 @@ var BottlenecksContent = ({
|
|
|
26528
27131
|
} catch (directError) {
|
|
26529
27132
|
console.warn(`[BottlenecksContent] Direct S3 loading failed, trying video index:`, directError);
|
|
26530
27133
|
}
|
|
26531
|
-
|
|
26532
|
-
if (clipCounts[targetCategory] > 0 && currentVideoIndex && currentVideoIndex.allVideos.length > 0) {
|
|
27134
|
+
if (clipCounts[targetCategory] > 0) {
|
|
26533
27135
|
try {
|
|
26534
|
-
const
|
|
26535
|
-
|
|
27136
|
+
const operationalDate2 = date || getOperationalDate();
|
|
27137
|
+
const shiftStr2 = effectiveShift;
|
|
27138
|
+
const firstVideo = await s3ClipsService.getClipByIndex(
|
|
27139
|
+
workspaceId,
|
|
27140
|
+
operationalDate2,
|
|
27141
|
+
shiftStr2,
|
|
26536
27142
|
targetCategory,
|
|
26537
27143
|
0,
|
|
26538
27144
|
// First video (index 0)
|
|
@@ -26572,25 +27178,22 @@ var BottlenecksContent = ({
|
|
|
26572
27178
|
loadingCategoryRef.current = null;
|
|
26573
27179
|
fetchInProgressRef.current.delete(operationKey);
|
|
26574
27180
|
}
|
|
26575
|
-
}, [workspaceId, date, s3ClipsService, clipCounts,
|
|
27181
|
+
}, [workspaceId, date, s3ClipsService, clipCounts, effectiveShift, updateClipCounts]);
|
|
26576
27182
|
React19.useEffect(() => {
|
|
26577
27183
|
if (s3ClipsService && !prefetchData) {
|
|
26578
27184
|
fetchClipCounts();
|
|
26579
27185
|
}
|
|
26580
|
-
}, [workspaceId, date, effectiveShift, s3ClipsService, fetchClipCounts, updateClipCounts,
|
|
27186
|
+
}, [workspaceId, date, effectiveShift, s3ClipsService, fetchClipCounts, updateClipCounts, prefetchData]);
|
|
26581
27187
|
React19.useEffect(() => {
|
|
26582
|
-
if (prefetchData) {
|
|
26583
|
-
console.log(`[BottlenecksContent] Received prefetch update - status: ${prefetchStatus}
|
|
27188
|
+
if (prefetchData && prefetchData.counts) {
|
|
27189
|
+
console.log(`[BottlenecksContent] Received prefetch update - status: ${prefetchStatus}`);
|
|
26584
27190
|
updateClipCounts(prefetchData.counts);
|
|
26585
|
-
if (
|
|
26586
|
-
updateVideoIndex(prefetchData.videoIndex);
|
|
26587
|
-
}
|
|
26588
|
-
if (!hasInitialLoad && prefetchData.videoIndex.allVideos.length > 0) {
|
|
27191
|
+
if (!hasInitialLoad) {
|
|
26589
27192
|
setIsLoading(false);
|
|
26590
27193
|
setHasInitialLoad(true);
|
|
26591
27194
|
}
|
|
26592
27195
|
}
|
|
26593
|
-
}, [prefetchData, prefetchStatus, updateClipCounts,
|
|
27196
|
+
}, [prefetchData, prefetchStatus, updateClipCounts, hasInitialLoad]);
|
|
26594
27197
|
React19.useEffect(() => {
|
|
26595
27198
|
if (s3ClipsService && clipCounts[activeFilter] > 0) {
|
|
26596
27199
|
const hasVideosForCurrentFilter = allVideos.some((video) => {
|
|
@@ -26630,7 +27233,7 @@ var BottlenecksContent = ({
|
|
|
26630
27233
|
setIsCategoryLoading(false);
|
|
26631
27234
|
}
|
|
26632
27235
|
}
|
|
26633
|
-
}, [activeFilter, s3ClipsService,
|
|
27236
|
+
}, [activeFilter, s3ClipsService, clipCounts, loadFirstVideoForCategory, allVideos, sopCategories]);
|
|
26634
27237
|
React19.useEffect(() => {
|
|
26635
27238
|
if (previousFilterRef.current !== activeFilter) {
|
|
26636
27239
|
console.log(`Filter changed from ${previousFilterRef.current} to ${activeFilter} - resetting to first video`);
|
|
@@ -26746,23 +27349,7 @@ var BottlenecksContent = ({
|
|
|
26746
27349
|
}
|
|
26747
27350
|
try {
|
|
26748
27351
|
let video = null;
|
|
26749
|
-
|
|
26750
|
-
if (currentVideoIndex && currentVideoIndex.byCategory && currentVideoIndex.allVideos.length > 0 && s3ClipsService) {
|
|
26751
|
-
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}`);
|
|
26752
|
-
console.log(`[BottlenecksContent] VideoIndex categories in handleNext: [${Array.from(currentVideoIndex.byCategory.keys()).join(", ")}]`);
|
|
26753
|
-
video = await s3ClipsService.getVideoFromIndex(
|
|
26754
|
-
currentVideoIndex,
|
|
26755
|
-
effectiveFilter,
|
|
26756
|
-
nextIndex,
|
|
26757
|
-
true,
|
|
26758
|
-
// includeCycleTime
|
|
26759
|
-
false
|
|
26760
|
-
// includeMetadata - DON'T fetch metadata during navigation to prevent flooding!
|
|
26761
|
-
);
|
|
26762
|
-
} else {
|
|
26763
|
-
console.warn(`[BottlenecksContent] Video index not ready for navigation: ID: ${currentVideoIndex?._debugId || "NO_ID"}, byCategory exists = ${!!currentVideoIndex?.byCategory}, allVideos = ${currentVideoIndex?.allVideos?.length || 0}`);
|
|
26764
|
-
}
|
|
26765
|
-
if (!video && s3ClipsService) {
|
|
27352
|
+
if (s3ClipsService) {
|
|
26766
27353
|
const operationalDate = date || getOperationalDate();
|
|
26767
27354
|
const shiftStr = effectiveShift;
|
|
26768
27355
|
video = await s3ClipsService.getClipByIndex(
|
|
@@ -28024,6 +28611,451 @@ var KPISection = React19.memo(({
|
|
|
28024
28611
|
return true;
|
|
28025
28612
|
});
|
|
28026
28613
|
KPISection.displayName = "KPISection";
|
|
28614
|
+
var WorkspaceHealthCard = ({
|
|
28615
|
+
workspace,
|
|
28616
|
+
onClick,
|
|
28617
|
+
showDetails = true,
|
|
28618
|
+
className = ""
|
|
28619
|
+
}) => {
|
|
28620
|
+
const getStatusConfig = () => {
|
|
28621
|
+
switch (workspace.status) {
|
|
28622
|
+
case "healthy":
|
|
28623
|
+
return {
|
|
28624
|
+
gradient: "from-emerald-50 to-green-50 dark:from-emerald-950/30 dark:to-green-950/30",
|
|
28625
|
+
border: "border-emerald-200 dark:border-emerald-800",
|
|
28626
|
+
icon: lucideReact.CheckCircle2,
|
|
28627
|
+
iconColor: "text-emerald-600 dark:text-emerald-400",
|
|
28628
|
+
badge: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300",
|
|
28629
|
+
statusText: "Online",
|
|
28630
|
+
pulse: true
|
|
28631
|
+
};
|
|
28632
|
+
case "unhealthy":
|
|
28633
|
+
return {
|
|
28634
|
+
gradient: "from-rose-50 to-red-50 dark:from-rose-950/30 dark:to-red-950/30",
|
|
28635
|
+
border: "border-rose-200 dark:border-rose-800",
|
|
28636
|
+
icon: lucideReact.XCircle,
|
|
28637
|
+
iconColor: "text-rose-600 dark:text-rose-400",
|
|
28638
|
+
badge: "bg-rose-100 text-rose-700 dark:bg-rose-900/50 dark:text-rose-300",
|
|
28639
|
+
statusText: "Offline",
|
|
28640
|
+
pulse: false
|
|
28641
|
+
};
|
|
28642
|
+
case "warning":
|
|
28643
|
+
return {
|
|
28644
|
+
gradient: "from-amber-50 to-yellow-50 dark:from-amber-950/30 dark:to-yellow-950/30",
|
|
28645
|
+
border: "border-amber-200 dark:border-amber-800",
|
|
28646
|
+
icon: lucideReact.AlertTriangle,
|
|
28647
|
+
iconColor: "text-amber-600 dark:text-amber-400",
|
|
28648
|
+
badge: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
|
28649
|
+
statusText: "Degraded",
|
|
28650
|
+
pulse: true
|
|
28651
|
+
};
|
|
28652
|
+
default:
|
|
28653
|
+
return {
|
|
28654
|
+
gradient: "from-gray-50 to-slate-50 dark:from-gray-950/30 dark:to-slate-950/30",
|
|
28655
|
+
border: "border-gray-200 dark:border-gray-800",
|
|
28656
|
+
icon: lucideReact.Activity,
|
|
28657
|
+
iconColor: "text-gray-500 dark:text-gray-400",
|
|
28658
|
+
badge: "bg-gray-100 text-gray-700 dark:bg-gray-900/50 dark:text-gray-300",
|
|
28659
|
+
statusText: "Unknown",
|
|
28660
|
+
pulse: false
|
|
28661
|
+
};
|
|
28662
|
+
}
|
|
28663
|
+
};
|
|
28664
|
+
const config = getStatusConfig();
|
|
28665
|
+
const StatusIcon = config.icon;
|
|
28666
|
+
workspace.isStale ? lucideReact.WifiOff : lucideReact.Wifi;
|
|
28667
|
+
const handleClick = () => {
|
|
28668
|
+
if (onClick) {
|
|
28669
|
+
onClick(workspace);
|
|
28670
|
+
}
|
|
28671
|
+
};
|
|
28672
|
+
const handleKeyDown = (event) => {
|
|
28673
|
+
if (onClick && (event.key === "Enter" || event.key === " ")) {
|
|
28674
|
+
event.preventDefault();
|
|
28675
|
+
onClick(workspace);
|
|
28676
|
+
}
|
|
28677
|
+
};
|
|
28678
|
+
const formatTimeAgo = (timeString) => {
|
|
28679
|
+
return timeString.replace("about ", "").replace(" ago", "");
|
|
28680
|
+
};
|
|
28681
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
28682
|
+
Card2,
|
|
28683
|
+
{
|
|
28684
|
+
className: clsx(
|
|
28685
|
+
"relative overflow-hidden transition-all duration-300",
|
|
28686
|
+
"bg-gradient-to-br",
|
|
28687
|
+
config.gradient,
|
|
28688
|
+
"border",
|
|
28689
|
+
config.border,
|
|
28690
|
+
"shadow-sm hover:shadow-md",
|
|
28691
|
+
onClick && "cursor-pointer hover:scale-[1.01]",
|
|
28692
|
+
workspace.isStale && "opacity-90",
|
|
28693
|
+
className
|
|
28694
|
+
),
|
|
28695
|
+
onClick: handleClick,
|
|
28696
|
+
onKeyDown: handleKeyDown,
|
|
28697
|
+
tabIndex: onClick ? 0 : void 0,
|
|
28698
|
+
role: onClick ? "button" : void 0,
|
|
28699
|
+
"aria-label": `Workspace ${workspace.workspace_display_name} status: ${workspace.status}`,
|
|
28700
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4", children: [
|
|
28701
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between mb-3", children: [
|
|
28702
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
28703
|
+
/* @__PURE__ */ jsxRuntime.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)}` }),
|
|
28704
|
+
showDetails && workspace.line_name && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: workspace.line_name })
|
|
28705
|
+
] }),
|
|
28706
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx(
|
|
28707
|
+
"flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium",
|
|
28708
|
+
config.badge
|
|
28709
|
+
), children: [
|
|
28710
|
+
/* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { className: "h-3.5 w-3.5" }),
|
|
28711
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: config.statusText })
|
|
28712
|
+
] })
|
|
28713
|
+
] }),
|
|
28714
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
28715
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
28716
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
28717
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3.5 w-3.5 text-gray-400" }),
|
|
28718
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap", children: [
|
|
28719
|
+
"Last seen: ",
|
|
28720
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: formatTimeAgo(workspace.timeSinceLastUpdate) })
|
|
28721
|
+
] })
|
|
28722
|
+
] }),
|
|
28723
|
+
workspace.isStale && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
28724
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.WifiOff, { className: "h-3.5 w-3.5 text-amber-500" }),
|
|
28725
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-amber-600 dark:text-amber-400 text-xs", children: "No recent updates" })
|
|
28726
|
+
] })
|
|
28727
|
+
] }),
|
|
28728
|
+
config.pulse && !workspace.isStale && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
28729
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2 w-2", children: [
|
|
28730
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
|
|
28731
|
+
"animate-ping absolute inline-flex h-full w-full rounded-full opacity-75",
|
|
28732
|
+
workspace.status === "healthy" ? "bg-emerald-400" : "bg-amber-400"
|
|
28733
|
+
) }),
|
|
28734
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
|
|
28735
|
+
"relative inline-flex rounded-full h-2 w-2",
|
|
28736
|
+
workspace.status === "healthy" ? "bg-emerald-500" : "bg-amber-500"
|
|
28737
|
+
) })
|
|
28738
|
+
] }),
|
|
28739
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Live" })
|
|
28740
|
+
] })
|
|
28741
|
+
] })
|
|
28742
|
+
] })
|
|
28743
|
+
}
|
|
28744
|
+
);
|
|
28745
|
+
};
|
|
28746
|
+
var CompactWorkspaceHealthCard = ({
|
|
28747
|
+
workspace,
|
|
28748
|
+
onClick,
|
|
28749
|
+
className = ""
|
|
28750
|
+
}) => {
|
|
28751
|
+
const getStatusConfig = () => {
|
|
28752
|
+
switch (workspace.status) {
|
|
28753
|
+
case "healthy":
|
|
28754
|
+
return {
|
|
28755
|
+
dot: "bg-emerald-500",
|
|
28756
|
+
icon: lucideReact.CheckCircle2,
|
|
28757
|
+
iconColor: "text-emerald-600 dark:text-emerald-400",
|
|
28758
|
+
bg: "hover:bg-emerald-50 dark:hover:bg-emerald-950/20"
|
|
28759
|
+
};
|
|
28760
|
+
case "unhealthy":
|
|
28761
|
+
return {
|
|
28762
|
+
dot: "bg-rose-500",
|
|
28763
|
+
icon: lucideReact.XCircle,
|
|
28764
|
+
iconColor: "text-rose-600 dark:text-rose-400",
|
|
28765
|
+
bg: "hover:bg-rose-50 dark:hover:bg-rose-950/20"
|
|
28766
|
+
};
|
|
28767
|
+
case "warning":
|
|
28768
|
+
return {
|
|
28769
|
+
dot: "bg-amber-500",
|
|
28770
|
+
icon: lucideReact.AlertTriangle,
|
|
28771
|
+
iconColor: "text-amber-600 dark:text-amber-400",
|
|
28772
|
+
bg: "hover:bg-amber-50 dark:hover:bg-amber-950/20"
|
|
28773
|
+
};
|
|
28774
|
+
default:
|
|
28775
|
+
return {
|
|
28776
|
+
dot: "bg-gray-400",
|
|
28777
|
+
icon: lucideReact.Activity,
|
|
28778
|
+
iconColor: "text-gray-500 dark:text-gray-400",
|
|
28779
|
+
bg: "hover:bg-gray-50 dark:hover:bg-gray-950/20"
|
|
28780
|
+
};
|
|
28781
|
+
}
|
|
28782
|
+
};
|
|
28783
|
+
const config = getStatusConfig();
|
|
28784
|
+
const StatusIcon = config.icon;
|
|
28785
|
+
const handleClick = () => {
|
|
28786
|
+
if (onClick) {
|
|
28787
|
+
onClick(workspace);
|
|
28788
|
+
}
|
|
28789
|
+
};
|
|
28790
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
28791
|
+
"div",
|
|
28792
|
+
{
|
|
28793
|
+
className: clsx(
|
|
28794
|
+
"flex items-center justify-between px-4 py-3 rounded-lg border",
|
|
28795
|
+
"bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700",
|
|
28796
|
+
"transition-all duration-200",
|
|
28797
|
+
onClick && `cursor-pointer ${config.bg}`,
|
|
28798
|
+
className
|
|
28799
|
+
),
|
|
28800
|
+
onClick: handleClick,
|
|
28801
|
+
role: onClick ? "button" : void 0,
|
|
28802
|
+
tabIndex: onClick ? 0 : void 0,
|
|
28803
|
+
children: [
|
|
28804
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
28805
|
+
/* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { className: clsx("h-5 w-5", config.iconColor) }),
|
|
28806
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
28807
|
+
/* @__PURE__ */ jsxRuntime.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)}` }),
|
|
28808
|
+
workspace.line_name && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.line_name })
|
|
28809
|
+
] })
|
|
28810
|
+
] }),
|
|
28811
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
28812
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.timeSinceLastUpdate }),
|
|
28813
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) })
|
|
28814
|
+
] })
|
|
28815
|
+
]
|
|
28816
|
+
}
|
|
28817
|
+
);
|
|
28818
|
+
};
|
|
28819
|
+
var HealthStatusGrid = ({
|
|
28820
|
+
workspaces,
|
|
28821
|
+
onWorkspaceClick,
|
|
28822
|
+
viewMode: initialViewMode = "grid",
|
|
28823
|
+
showFilters = true,
|
|
28824
|
+
groupBy: initialGroupBy = "none",
|
|
28825
|
+
className = ""
|
|
28826
|
+
}) => {
|
|
28827
|
+
const [viewMode, setViewMode] = React19.useState(initialViewMode);
|
|
28828
|
+
const [searchTerm, setSearchTerm] = React19.useState("");
|
|
28829
|
+
const [statusFilter, setStatusFilter] = React19.useState("all");
|
|
28830
|
+
const [groupBy, setGroupBy] = React19.useState(initialGroupBy);
|
|
28831
|
+
const [expandedGroups, setExpandedGroups] = React19.useState(/* @__PURE__ */ new Set());
|
|
28832
|
+
const filteredWorkspaces = React19.useMemo(() => {
|
|
28833
|
+
let filtered = [...workspaces];
|
|
28834
|
+
if (searchTerm) {
|
|
28835
|
+
const search = searchTerm.toLowerCase();
|
|
28836
|
+
filtered = filtered.filter(
|
|
28837
|
+
(w) => w.workspace_display_name?.toLowerCase().includes(search) || w.line_name?.toLowerCase().includes(search) || w.company_name?.toLowerCase().includes(search)
|
|
28838
|
+
);
|
|
28839
|
+
}
|
|
28840
|
+
if (statusFilter !== "all") {
|
|
28841
|
+
filtered = filtered.filter((w) => w.status === statusFilter);
|
|
28842
|
+
}
|
|
28843
|
+
return filtered;
|
|
28844
|
+
}, [workspaces, searchTerm, statusFilter]);
|
|
28845
|
+
const groupedWorkspaces = React19.useMemo(() => {
|
|
28846
|
+
if (groupBy === "none") {
|
|
28847
|
+
return { "All Workspaces": filteredWorkspaces };
|
|
28848
|
+
}
|
|
28849
|
+
const groups = {};
|
|
28850
|
+
filteredWorkspaces.forEach((workspace) => {
|
|
28851
|
+
let key = "Unknown";
|
|
28852
|
+
switch (groupBy) {
|
|
28853
|
+
case "line":
|
|
28854
|
+
key = workspace.line_name || "Unknown Line";
|
|
28855
|
+
break;
|
|
28856
|
+
case "status":
|
|
28857
|
+
key = workspace.status;
|
|
28858
|
+
break;
|
|
28859
|
+
}
|
|
28860
|
+
if (!groups[key]) {
|
|
28861
|
+
groups[key] = [];
|
|
28862
|
+
}
|
|
28863
|
+
groups[key].push(workspace);
|
|
28864
|
+
});
|
|
28865
|
+
const sortedGroups = {};
|
|
28866
|
+
Object.keys(groups).sort().forEach((key) => {
|
|
28867
|
+
sortedGroups[key] = groups[key];
|
|
28868
|
+
});
|
|
28869
|
+
return sortedGroups;
|
|
28870
|
+
}, [filteredWorkspaces, groupBy]);
|
|
28871
|
+
React19.useEffect(() => {
|
|
28872
|
+
if (groupBy !== "none") {
|
|
28873
|
+
setExpandedGroups(new Set(Object.keys(groupedWorkspaces)));
|
|
28874
|
+
}
|
|
28875
|
+
}, [groupBy, groupedWorkspaces]);
|
|
28876
|
+
const toggleGroup = (groupName) => {
|
|
28877
|
+
const newExpanded = new Set(expandedGroups);
|
|
28878
|
+
if (newExpanded.has(groupName)) {
|
|
28879
|
+
newExpanded.delete(groupName);
|
|
28880
|
+
} else {
|
|
28881
|
+
newExpanded.add(groupName);
|
|
28882
|
+
}
|
|
28883
|
+
setExpandedGroups(newExpanded);
|
|
28884
|
+
};
|
|
28885
|
+
const getStatusCounts = () => {
|
|
28886
|
+
const counts = {
|
|
28887
|
+
healthy: 0,
|
|
28888
|
+
unhealthy: 0,
|
|
28889
|
+
warning: 0,
|
|
28890
|
+
unknown: 0
|
|
28891
|
+
};
|
|
28892
|
+
workspaces.forEach((w) => {
|
|
28893
|
+
counts[w.status]++;
|
|
28894
|
+
});
|
|
28895
|
+
return counts;
|
|
28896
|
+
};
|
|
28897
|
+
const statusCounts = getStatusCounts();
|
|
28898
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("space-y-4", className), children: [
|
|
28899
|
+
showFilters && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4", children: [
|
|
28900
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col sm:flex-row gap-3 flex-wrap", children: [
|
|
28901
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-[200px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
28902
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" }),
|
|
28903
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28904
|
+
"input",
|
|
28905
|
+
{
|
|
28906
|
+
type: "text",
|
|
28907
|
+
placeholder: "Search workspaces...",
|
|
28908
|
+
value: searchTerm,
|
|
28909
|
+
onChange: (e) => setSearchTerm(e.target.value),
|
|
28910
|
+
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"
|
|
28911
|
+
}
|
|
28912
|
+
)
|
|
28913
|
+
] }) }),
|
|
28914
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Select, { value: statusFilter, onValueChange: (value) => setStatusFilter(value), children: [
|
|
28915
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-full sm:w-[180px] bg-white border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "All statuses" }) }),
|
|
28916
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SelectContent, { children: [
|
|
28917
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SelectItem, { value: "all", children: [
|
|
28918
|
+
"All (",
|
|
28919
|
+
workspaces.length,
|
|
28920
|
+
")"
|
|
28921
|
+
] }),
|
|
28922
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "healthy", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28923
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-green-500" }),
|
|
28924
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
28925
|
+
"Healthy (",
|
|
28926
|
+
statusCounts.healthy,
|
|
28927
|
+
")"
|
|
28928
|
+
] })
|
|
28929
|
+
] }) }),
|
|
28930
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "unhealthy", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28931
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-red-500" }),
|
|
28932
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
28933
|
+
"Unhealthy (",
|
|
28934
|
+
statusCounts.unhealthy,
|
|
28935
|
+
")"
|
|
28936
|
+
] })
|
|
28937
|
+
] }) }),
|
|
28938
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "warning", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28939
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-500" }),
|
|
28940
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
28941
|
+
"Warning (",
|
|
28942
|
+
statusCounts.warning,
|
|
28943
|
+
")"
|
|
28944
|
+
] })
|
|
28945
|
+
] }) }),
|
|
28946
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "unknown", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28947
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-gray-400" }),
|
|
28948
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
28949
|
+
"Unknown (",
|
|
28950
|
+
statusCounts.unknown,
|
|
28951
|
+
")"
|
|
28952
|
+
] })
|
|
28953
|
+
] }) })
|
|
28954
|
+
] })
|
|
28955
|
+
] }),
|
|
28956
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Select, { value: groupBy, onValueChange: (value) => setGroupBy(value), children: [
|
|
28957
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-full sm:w-[160px] bg-white border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Group by" }) }),
|
|
28958
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SelectContent, { children: [
|
|
28959
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "none", children: "No grouping" }),
|
|
28960
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "line", children: "Group by Line" }),
|
|
28961
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "status", children: "Group by Status" })
|
|
28962
|
+
] })
|
|
28963
|
+
] }),
|
|
28964
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
28965
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28966
|
+
"button",
|
|
28967
|
+
{
|
|
28968
|
+
onClick: () => setViewMode("grid"),
|
|
28969
|
+
className: clsx(
|
|
28970
|
+
"p-2 rounded-lg transition-colors",
|
|
28971
|
+
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"
|
|
28972
|
+
),
|
|
28973
|
+
"aria-label": "Grid view",
|
|
28974
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Grid3x3, { className: "h-5 w-5" })
|
|
28975
|
+
}
|
|
28976
|
+
),
|
|
28977
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28978
|
+
"button",
|
|
28979
|
+
{
|
|
28980
|
+
onClick: () => setViewMode("list"),
|
|
28981
|
+
className: clsx(
|
|
28982
|
+
"p-2 rounded-lg transition-colors",
|
|
28983
|
+
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"
|
|
28984
|
+
),
|
|
28985
|
+
"aria-label": "List view",
|
|
28986
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.List, { className: "h-5 w-5" })
|
|
28987
|
+
}
|
|
28988
|
+
)
|
|
28989
|
+
] })
|
|
28990
|
+
] }),
|
|
28991
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 text-sm text-gray-500 dark:text-gray-400", children: [
|
|
28992
|
+
"Showing ",
|
|
28993
|
+
filteredWorkspaces.length,
|
|
28994
|
+
" of ",
|
|
28995
|
+
workspaces.length,
|
|
28996
|
+
" workspaces"
|
|
28997
|
+
] })
|
|
28998
|
+
] }),
|
|
28999
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-6", children: Object.entries(groupedWorkspaces).map(([groupName, groupWorkspaces]) => {
|
|
29000
|
+
const isExpanded = groupBy === "none" || expandedGroups.has(groupName);
|
|
29001
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
29002
|
+
groupBy !== "none" && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
29003
|
+
"div",
|
|
29004
|
+
{
|
|
29005
|
+
className: "flex items-center justify-between cursor-pointer group",
|
|
29006
|
+
onClick: () => toggleGroup(groupName),
|
|
29007
|
+
children: [
|
|
29008
|
+
/* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
29009
|
+
groupName,
|
|
29010
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-normal text-gray-500 dark:text-gray-400", children: [
|
|
29011
|
+
"(",
|
|
29012
|
+
groupWorkspaces.length,
|
|
29013
|
+
")"
|
|
29014
|
+
] })
|
|
29015
|
+
] }),
|
|
29016
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29017
|
+
lucideReact.ChevronDown,
|
|
29018
|
+
{
|
|
29019
|
+
className: clsx(
|
|
29020
|
+
"h-5 w-5 text-gray-400 transition-transform",
|
|
29021
|
+
isExpanded && "rotate-180"
|
|
29022
|
+
)
|
|
29023
|
+
}
|
|
29024
|
+
)
|
|
29025
|
+
]
|
|
29026
|
+
}
|
|
29027
|
+
),
|
|
29028
|
+
isExpanded && /* @__PURE__ */ jsxRuntime.jsx(
|
|
29029
|
+
"div",
|
|
29030
|
+
{
|
|
29031
|
+
className: clsx(
|
|
29032
|
+
viewMode === "grid" ? "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4" : "space-y-2"
|
|
29033
|
+
),
|
|
29034
|
+
children: groupWorkspaces.map(
|
|
29035
|
+
(workspace) => viewMode === "grid" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
29036
|
+
WorkspaceHealthCard,
|
|
29037
|
+
{
|
|
29038
|
+
workspace,
|
|
29039
|
+
onClick: onWorkspaceClick,
|
|
29040
|
+
showDetails: true
|
|
29041
|
+
},
|
|
29042
|
+
workspace.workspace_id
|
|
29043
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
29044
|
+
CompactWorkspaceHealthCard,
|
|
29045
|
+
{
|
|
29046
|
+
workspace,
|
|
29047
|
+
onClick: onWorkspaceClick
|
|
29048
|
+
},
|
|
29049
|
+
workspace.workspace_id
|
|
29050
|
+
)
|
|
29051
|
+
)
|
|
29052
|
+
}
|
|
29053
|
+
)
|
|
29054
|
+
] }, groupName);
|
|
29055
|
+
}) }),
|
|
29056
|
+
filteredWorkspaces.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500 dark:text-gray-400", children: searchTerm || statusFilter !== "all" ? "No workspaces found matching your filters." : "No workspaces available." }) })
|
|
29057
|
+
] });
|
|
29058
|
+
};
|
|
28027
29059
|
var ISTTimer2 = ISTTimer_default;
|
|
28028
29060
|
var DashboardHeader = React19.memo(({ lineTitle, className = "", headerControls }) => {
|
|
28029
29061
|
const getShiftName = () => {
|
|
@@ -28521,6 +29553,17 @@ var SideNavBar = React19.memo(({
|
|
|
28521
29553
|
});
|
|
28522
29554
|
onMobileMenuClose?.();
|
|
28523
29555
|
}, [navigate, onMobileMenuClose]);
|
|
29556
|
+
const handleHealthClick = React19.useCallback(() => {
|
|
29557
|
+
navigate("/health", {
|
|
29558
|
+
trackingEvent: {
|
|
29559
|
+
name: "Health Status Page Clicked",
|
|
29560
|
+
properties: {
|
|
29561
|
+
source: "side_nav"
|
|
29562
|
+
}
|
|
29563
|
+
}
|
|
29564
|
+
});
|
|
29565
|
+
onMobileMenuClose?.();
|
|
29566
|
+
}, [navigate, onMobileMenuClose]);
|
|
28524
29567
|
const handleLogoClick = React19.useCallback(() => {
|
|
28525
29568
|
navigate("/");
|
|
28526
29569
|
onMobileMenuClose?.();
|
|
@@ -28534,6 +29577,7 @@ var SideNavBar = React19.memo(({
|
|
|
28534
29577
|
const profileButtonClasses = React19.useMemo(() => getButtonClasses("/profile"), [getButtonClasses, pathname]);
|
|
28535
29578
|
const helpButtonClasses = React19.useMemo(() => getButtonClasses("/help"), [getButtonClasses, pathname]);
|
|
28536
29579
|
const skusButtonClasses = React19.useMemo(() => getButtonClasses("/skus"), [getButtonClasses, pathname]);
|
|
29580
|
+
const healthButtonClasses = React19.useMemo(() => getButtonClasses("/health"), [getButtonClasses, pathname]);
|
|
28537
29581
|
const NavigationContent = () => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
28538
29582
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full py-6 px-4 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
28539
29583
|
"button",
|
|
@@ -28678,6 +29722,21 @@ var SideNavBar = React19.memo(({
|
|
|
28678
29722
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Help" })
|
|
28679
29723
|
]
|
|
28680
29724
|
}
|
|
29725
|
+
),
|
|
29726
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
29727
|
+
"button",
|
|
29728
|
+
{
|
|
29729
|
+
onClick: handleHealthClick,
|
|
29730
|
+
className: healthButtonClasses,
|
|
29731
|
+
"aria-label": "System Health",
|
|
29732
|
+
tabIndex: 0,
|
|
29733
|
+
role: "tab",
|
|
29734
|
+
"aria-selected": pathname === "/health" || pathname.startsWith("/health/"),
|
|
29735
|
+
children: [
|
|
29736
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.HeartIcon, { className: "w-5 h-5 mb-1" }),
|
|
29737
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Health" })
|
|
29738
|
+
]
|
|
29739
|
+
}
|
|
28681
29740
|
)
|
|
28682
29741
|
] })
|
|
28683
29742
|
] }),
|
|
@@ -30820,16 +31879,13 @@ var AIAgentView = () => {
|
|
|
30820
31879
|
} }),
|
|
30821
31880
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex-1 flex flex-col h-screen transition-all duration-300 ${isSidebarOpen ? "mr-80" : "mr-0"}`, children: [
|
|
30822
31881
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between relative", children: [
|
|
30823
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.
|
|
30824
|
-
|
|
31882
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
31883
|
+
BackButtonMinimal,
|
|
30825
31884
|
{
|
|
30826
31885
|
onClick: () => navigate("/"),
|
|
30827
|
-
|
|
30828
|
-
|
|
30829
|
-
|
|
30830
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "h-5 w-5" }),
|
|
30831
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2", children: "Back" })
|
|
30832
|
-
]
|
|
31886
|
+
text: "Back",
|
|
31887
|
+
size: "default",
|
|
31888
|
+
"aria-label": "Navigate back to dashboard"
|
|
30833
31889
|
}
|
|
30834
31890
|
) }),
|
|
30835
31891
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 text-center mx-auto", children: [
|
|
@@ -31498,15 +32554,13 @@ var HelpView = ({
|
|
|
31498
32554
|
transition: { duration: 0.3 },
|
|
31499
32555
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
31500
32556
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white px-8 py-6 shadow-sm border-b border-gray-200/80", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between relative", children: [
|
|
31501
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.
|
|
31502
|
-
|
|
32557
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
32558
|
+
BackButtonMinimal,
|
|
31503
32559
|
{
|
|
31504
32560
|
onClick: handleBackClick,
|
|
31505
|
-
|
|
31506
|
-
|
|
31507
|
-
|
|
31508
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2", children: "Back" })
|
|
31509
|
-
]
|
|
32561
|
+
text: "Back",
|
|
32562
|
+
size: "default",
|
|
32563
|
+
"aria-label": "Navigate back to dashboard"
|
|
31510
32564
|
}
|
|
31511
32565
|
) }),
|
|
31512
32566
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 text-center mx-auto", children: [
|
|
@@ -31536,10 +32590,7 @@ var HelpView = ({
|
|
|
31536
32590
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "xl:col-span-3 order-1", children: /* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "shadow-lg border-gray-200 bg-white", children: [
|
|
31537
32591
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 sm:gap-3", children: [
|
|
31538
32592
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-1.5 sm:p-2 bg-blue-100 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx(outline.DocumentTextIcon, { className: "h-4 w-4 sm:h-5 sm:w-5 text-blue-600" }) }),
|
|
31539
|
-
/* @__PURE__ */ jsxRuntime.
|
|
31540
|
-
/* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg sm:text-xl font-bold text-gray-900", children: "Submit Support Request" }),
|
|
31541
|
-
/* @__PURE__ */ jsxRuntime.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" })
|
|
31542
|
-
] })
|
|
32593
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-lg sm:text-xl font-bold text-gray-900", children: "Submit Support Request" }) })
|
|
31543
32594
|
] }) }),
|
|
31544
32595
|
/* @__PURE__ */ jsxRuntime.jsx(CardContent2, { className: "p-4 sm:p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "space-y-4 sm:space-y-5", children: [
|
|
31545
32596
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4 sm:space-y-5", children: [
|
|
@@ -32633,17 +33684,15 @@ var KPIDetailView = ({
|
|
|
32633
33684
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
|
|
32634
33685
|
/* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
|
|
32635
33686
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center relative", children: [
|
|
32636
|
-
/* @__PURE__ */ jsxRuntime.
|
|
32637
|
-
|
|
33687
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
33688
|
+
BackButtonMinimal,
|
|
32638
33689
|
{
|
|
32639
33690
|
onClick: handleBackClick,
|
|
32640
|
-
|
|
32641
|
-
|
|
32642
|
-
|
|
32643
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2", children: "Back" })
|
|
32644
|
-
]
|
|
33691
|
+
text: "Back",
|
|
33692
|
+
size: "default",
|
|
33693
|
+
"aria-label": "Navigate back to previous page"
|
|
32645
33694
|
}
|
|
32646
|
-
),
|
|
33695
|
+
) }),
|
|
32647
33696
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
32648
33697
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: lineInfo?.line_name || "Line" }),
|
|
32649
33698
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
@@ -33005,17 +34054,15 @@ var KPIsOverviewView = ({
|
|
|
33005
34054
|
if (error) {
|
|
33006
34055
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
|
|
33007
34056
|
/* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center relative", children: [
|
|
33008
|
-
/* @__PURE__ */ jsxRuntime.
|
|
33009
|
-
|
|
34057
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
34058
|
+
BackButtonMinimal,
|
|
33010
34059
|
{
|
|
33011
34060
|
onClick: handleBackClick,
|
|
33012
|
-
|
|
33013
|
-
|
|
33014
|
-
|
|
33015
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2", children: "Back" })
|
|
33016
|
-
]
|
|
34061
|
+
text: "Back",
|
|
34062
|
+
size: "default",
|
|
34063
|
+
"aria-label": "Navigate back to previous page"
|
|
33017
34064
|
}
|
|
33018
|
-
),
|
|
34065
|
+
) }),
|
|
33019
34066
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
33020
34067
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "Shop-floor overview" }),
|
|
33021
34068
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
@@ -33027,17 +34074,15 @@ var KPIsOverviewView = ({
|
|
|
33027
34074
|
if (lines.length === 0) {
|
|
33028
34075
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
|
|
33029
34076
|
/* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center relative", children: [
|
|
33030
|
-
/* @__PURE__ */ jsxRuntime.
|
|
33031
|
-
|
|
34077
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
34078
|
+
BackButtonMinimal,
|
|
33032
34079
|
{
|
|
33033
34080
|
onClick: handleBackClick,
|
|
33034
|
-
|
|
33035
|
-
|
|
33036
|
-
|
|
33037
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2", children: "Back" })
|
|
33038
|
-
]
|
|
34081
|
+
text: "Back",
|
|
34082
|
+
size: "default",
|
|
34083
|
+
"aria-label": "Navigate back to previous page"
|
|
33039
34084
|
}
|
|
33040
|
-
),
|
|
34085
|
+
) }),
|
|
33041
34086
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
33042
34087
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "Shop-floor overview" }),
|
|
33043
34088
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
@@ -33052,17 +34097,15 @@ var KPIsOverviewView = ({
|
|
|
33052
34097
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-gray-50 flex flex-col", children: [
|
|
33053
34098
|
/* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
|
|
33054
34099
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center relative", children: [
|
|
33055
|
-
/* @__PURE__ */ jsxRuntime.
|
|
33056
|
-
|
|
34100
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
34101
|
+
BackButtonMinimal,
|
|
33057
34102
|
{
|
|
33058
34103
|
onClick: handleBackClick,
|
|
33059
|
-
|
|
33060
|
-
|
|
33061
|
-
|
|
33062
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2", children: "Back" })
|
|
33063
|
-
]
|
|
34104
|
+
text: "Back",
|
|
34105
|
+
size: "default",
|
|
34106
|
+
"aria-label": "Navigate back to previous page"
|
|
33064
34107
|
}
|
|
33065
|
-
),
|
|
34108
|
+
) }),
|
|
33066
34109
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
33067
34110
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "Shop-floor overview" }),
|
|
33068
34111
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
@@ -33346,18 +34389,13 @@ var LeaderboardDetailView = React19.memo(({
|
|
|
33346
34389
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `min-h-screen bg-slate-50 flex flex-col ${className}`, children: [
|
|
33347
34390
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-20 bg-white shadow-sm border-b border-gray-200/80", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 sm:px-8 py-2 sm:py-2.5", children: [
|
|
33348
34391
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
33349
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-auto sm:w-32", children: /* @__PURE__ */ jsxRuntime.
|
|
33350
|
-
|
|
34392
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-auto sm:w-32", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
34393
|
+
BackButtonMinimal,
|
|
33351
34394
|
{
|
|
33352
34395
|
onClick: handleBackClick,
|
|
33353
|
-
|
|
33354
|
-
|
|
33355
|
-
|
|
33356
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 12H5" }),
|
|
33357
|
-
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "12 19 5 12 12 5" })
|
|
33358
|
-
] }),
|
|
33359
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm font-medium", children: "Back" })
|
|
33360
|
-
]
|
|
34396
|
+
text: "Back",
|
|
34397
|
+
size: "default",
|
|
34398
|
+
"aria-label": "Navigate back to previous page"
|
|
33361
34399
|
}
|
|
33362
34400
|
) }),
|
|
33363
34401
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 sm:gap-3", children: [
|
|
@@ -34418,18 +35456,15 @@ var ShiftsView = ({
|
|
|
34418
35456
|
}, [lineConfigs, supabase, showToast]);
|
|
34419
35457
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `min-h-screen bg-slate-50 ${className}`, children: [
|
|
34420
35458
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200/80 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 sm:px-8 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center relative", children: [
|
|
34421
|
-
/* @__PURE__ */ jsxRuntime.
|
|
34422
|
-
|
|
35459
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
35460
|
+
BackButtonMinimal,
|
|
34423
35461
|
{
|
|
34424
35462
|
onClick: () => onBackClick ? onBackClick() : window.history.back(),
|
|
34425
|
-
|
|
34426
|
-
|
|
34427
|
-
|
|
34428
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "w-5 h-5" }),
|
|
34429
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Back" })
|
|
34430
|
-
]
|
|
35463
|
+
text: "Back",
|
|
35464
|
+
size: "default",
|
|
35465
|
+
"aria-label": "Navigate back to previous page"
|
|
34431
35466
|
}
|
|
34432
|
-
),
|
|
35467
|
+
) }),
|
|
34433
35468
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
|
|
34434
35469
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "Shift Management" }),
|
|
34435
35470
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1", children: "Configure day and night shift timings and breaks for each production line" })
|
|
@@ -35327,6 +36362,7 @@ var TargetsViewUI = ({
|
|
|
35327
36362
|
onSaveLine,
|
|
35328
36363
|
onToggleBulkConfigure,
|
|
35329
36364
|
onBulkConfigure,
|
|
36365
|
+
onUpdateWorkspaceDisplayName,
|
|
35330
36366
|
// SKU props
|
|
35331
36367
|
skuEnabled = false,
|
|
35332
36368
|
skus = [],
|
|
@@ -35338,15 +36374,13 @@ var TargetsViewUI = ({
|
|
|
35338
36374
|
}
|
|
35339
36375
|
return /* @__PURE__ */ jsxRuntime.jsxs("main", { className: "min-h-screen flex-1 bg-gray-50", children: [
|
|
35340
36376
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white px-8 py-6 shadow-sm border-b border-gray-200/80", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between relative", children: [
|
|
35341
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.
|
|
35342
|
-
|
|
36377
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
36378
|
+
BackButtonMinimal,
|
|
35343
36379
|
{
|
|
35344
36380
|
onClick: onBack,
|
|
35345
|
-
|
|
35346
|
-
|
|
35347
|
-
|
|
35348
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2", children: "Back" })
|
|
35349
|
-
]
|
|
36381
|
+
text: "Back",
|
|
36382
|
+
size: "default",
|
|
36383
|
+
"aria-label": "Navigate back to previous page"
|
|
35350
36384
|
}
|
|
35351
36385
|
) }),
|
|
35352
36386
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -35494,7 +36528,18 @@ var TargetsViewUI = ({
|
|
|
35494
36528
|
{
|
|
35495
36529
|
className: "px-6 py-4 hover:bg-gray-50 transition-all duration-200",
|
|
35496
36530
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-12 gap-6 items-center", children: [
|
|
35497
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
36531
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2", children: onUpdateWorkspaceDisplayName ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
36532
|
+
InlineEditableText,
|
|
36533
|
+
{
|
|
36534
|
+
value: formattedName,
|
|
36535
|
+
onSave: async (newName) => {
|
|
36536
|
+
await onUpdateWorkspaceDisplayName(workspace.id, newName);
|
|
36537
|
+
},
|
|
36538
|
+
placeholder: "Workspace name",
|
|
36539
|
+
className: "font-medium text-gray-900",
|
|
36540
|
+
inputClassName: "min-w-[120px]"
|
|
36541
|
+
}
|
|
36542
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-900", children: formattedName }) }),
|
|
35498
36543
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
35499
36544
|
"select",
|
|
35500
36545
|
{
|
|
@@ -36235,6 +37280,17 @@ var TargetsView = ({
|
|
|
36235
37280
|
router.push("/");
|
|
36236
37281
|
}
|
|
36237
37282
|
};
|
|
37283
|
+
const handleUpdateWorkspaceDisplayName = React19.useCallback(async (workspaceId, displayName) => {
|
|
37284
|
+
try {
|
|
37285
|
+
await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
37286
|
+
await forceRefreshWorkspaceDisplayNames();
|
|
37287
|
+
sonner.toast.success("Workspace name updated successfully");
|
|
37288
|
+
} catch (error) {
|
|
37289
|
+
console.error("Error updating workspace display name:", error);
|
|
37290
|
+
sonner.toast.error("Failed to update workspace name");
|
|
37291
|
+
throw error;
|
|
37292
|
+
}
|
|
37293
|
+
}, []);
|
|
36238
37294
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
36239
37295
|
TargetsViewUI_default,
|
|
36240
37296
|
{
|
|
@@ -36257,6 +37313,7 @@ var TargetsView = ({
|
|
|
36257
37313
|
onSaveLine: handleSaveLine,
|
|
36258
37314
|
onToggleBulkConfigure: handleToggleBulkConfigure,
|
|
36259
37315
|
onBulkConfigure: handleBulkConfigure,
|
|
37316
|
+
onUpdateWorkspaceDisplayName: handleUpdateWorkspaceDisplayName,
|
|
36260
37317
|
skuEnabled,
|
|
36261
37318
|
skus,
|
|
36262
37319
|
onUpdateSelectedSKU: updateSelectedSKU,
|
|
@@ -36355,6 +37412,14 @@ var WorkspaceDetailView = ({
|
|
|
36355
37412
|
const [usingFallbackData, setUsingFallbackData] = React19.useState(false);
|
|
36356
37413
|
const [showIdleTime, setShowIdleTime] = React19.useState(false);
|
|
36357
37414
|
const dashboardConfig = useDashboardConfig();
|
|
37415
|
+
const {
|
|
37416
|
+
workspace: workspaceHealth,
|
|
37417
|
+
loading: healthLoading,
|
|
37418
|
+
error: healthError
|
|
37419
|
+
} = useWorkspaceHealthById(workspaceId, {
|
|
37420
|
+
enableRealtime: true,
|
|
37421
|
+
refreshInterval: 3e4
|
|
37422
|
+
});
|
|
36358
37423
|
const {
|
|
36359
37424
|
status: prefetchStatus,
|
|
36360
37425
|
data: prefetchData,
|
|
@@ -36663,15 +37728,13 @@ var WorkspaceDetailView = ({
|
|
|
36663
37728
|
"Error: ",
|
|
36664
37729
|
error.message
|
|
36665
37730
|
] }),
|
|
36666
|
-
/* @__PURE__ */ jsxRuntime.
|
|
36667
|
-
|
|
37731
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
37732
|
+
BackButton,
|
|
36668
37733
|
{
|
|
36669
37734
|
onClick: () => onNavigate && onNavigate("/"),
|
|
36670
|
-
|
|
36671
|
-
|
|
36672
|
-
|
|
36673
|
-
"Return to Dashboard"
|
|
36674
|
-
]
|
|
37735
|
+
text: "Return to Dashboard",
|
|
37736
|
+
size: "default",
|
|
37737
|
+
"aria-label": "Return to dashboard"
|
|
36675
37738
|
}
|
|
36676
37739
|
)
|
|
36677
37740
|
] });
|
|
@@ -36679,15 +37742,13 @@ var WorkspaceDetailView = ({
|
|
|
36679
37742
|
if (!workspace) {
|
|
36680
37743
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen p-8 bg-slate-50", children: [
|
|
36681
37744
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-xl text-gray-600", children: "Workspace not found" }),
|
|
36682
|
-
/* @__PURE__ */ jsxRuntime.
|
|
36683
|
-
|
|
37745
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
37746
|
+
BackButton,
|
|
36684
37747
|
{
|
|
36685
37748
|
onClick: () => onNavigate && onNavigate("/"),
|
|
36686
|
-
|
|
36687
|
-
|
|
36688
|
-
|
|
36689
|
-
"Return to Dashboard"
|
|
36690
|
-
]
|
|
37749
|
+
text: "Return to Dashboard",
|
|
37750
|
+
size: "default",
|
|
37751
|
+
"aria-label": "Return to dashboard"
|
|
36691
37752
|
}
|
|
36692
37753
|
)
|
|
36693
37754
|
] });
|
|
@@ -36702,21 +37763,16 @@ var WorkspaceDetailView = ({
|
|
|
36702
37763
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen w-full flex flex-col bg-slate-50", children: [
|
|
36703
37764
|
/* @__PURE__ */ jsxRuntime.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: [
|
|
36704
37765
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
|
|
36705
|
-
/* @__PURE__ */ jsxRuntime.
|
|
36706
|
-
|
|
37766
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
37767
|
+
BackButtonMinimal,
|
|
36707
37768
|
{
|
|
36708
37769
|
onClick: handleBackNavigation,
|
|
36709
|
-
|
|
36710
|
-
|
|
36711
|
-
|
|
36712
|
-
/* @__PURE__ */ jsxRuntime.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" })
|
|
36713
|
-
]
|
|
37770
|
+
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",
|
|
37771
|
+
size: "default",
|
|
37772
|
+
"aria-label": "Navigate back to previous page"
|
|
36714
37773
|
}
|
|
36715
|
-
),
|
|
36716
|
-
/* @__PURE__ */ jsxRuntime.
|
|
36717
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: formattedWorkspaceName }),
|
|
36718
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
36719
|
-
] }),
|
|
37774
|
+
) }),
|
|
37775
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: formattedWorkspaceName }) }),
|
|
36720
37776
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
|
|
36721
37777
|
] }),
|
|
36722
37778
|
activeTab !== "monthly_history" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 bg-blue-50 px-3 py-2 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-4", children: [
|
|
@@ -36744,6 +37800,19 @@ var WorkspaceDetailView = ({
|
|
|
36744
37800
|
workspace.shift_type,
|
|
36745
37801
|
" Shift"
|
|
36746
37802
|
] })
|
|
37803
|
+
] }),
|
|
37804
|
+
workspaceHealth && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
37805
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
37806
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
37807
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx(
|
|
37808
|
+
"h-1.5 w-1.5 rounded-full",
|
|
37809
|
+
workspaceHealth.status === "healthy" ? "bg-green-600" : workspaceHealth.status === "unhealthy" ? "bg-red-600" : workspaceHealth.status === "warning" ? "bg-amber-600" : "bg-gray-500"
|
|
37810
|
+
) }),
|
|
37811
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-blue-700", children: [
|
|
37812
|
+
"Last update: ",
|
|
37813
|
+
workspaceHealth.timeSinceLastUpdate
|
|
37814
|
+
] })
|
|
37815
|
+
] })
|
|
36747
37816
|
] })
|
|
36748
37817
|
] }) }),
|
|
36749
37818
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 sm:mt-1.5 lg:mt-2 flex items-center justify-between", children: [
|
|
@@ -37220,17 +38289,15 @@ var SKUManagementView = () => {
|
|
|
37220
38289
|
}
|
|
37221
38290
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-slate-50", children: [
|
|
37222
38291
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200/80 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 sm:px-8 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center relative", children: [
|
|
37223
|
-
/* @__PURE__ */ jsxRuntime.
|
|
37224
|
-
|
|
38292
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
38293
|
+
BackButtonMinimal,
|
|
37225
38294
|
{
|
|
37226
38295
|
onClick: handleBack,
|
|
37227
|
-
|
|
37228
|
-
|
|
37229
|
-
|
|
37230
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Back" })
|
|
37231
|
-
]
|
|
38296
|
+
text: "Back",
|
|
38297
|
+
size: "default",
|
|
38298
|
+
"aria-label": "Navigate back to previous page"
|
|
37232
38299
|
}
|
|
37233
|
-
),
|
|
38300
|
+
) }),
|
|
37234
38301
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 text-center mx-auto", children: [
|
|
37235
38302
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "SKU Management" }),
|
|
37236
38303
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Manage Stock Keeping Units (SKUs) for production planning" })
|
|
@@ -37282,6 +38349,255 @@ var SKUManagementView = () => {
|
|
|
37282
38349
|
)
|
|
37283
38350
|
] });
|
|
37284
38351
|
};
|
|
38352
|
+
var WorkspaceHealthView = ({
|
|
38353
|
+
lineId,
|
|
38354
|
+
companyId,
|
|
38355
|
+
onNavigate,
|
|
38356
|
+
className = ""
|
|
38357
|
+
}) => {
|
|
38358
|
+
const router$1 = router.useRouter();
|
|
38359
|
+
const [viewMode, setViewMode] = React19.useState("grid");
|
|
38360
|
+
const [groupBy, setGroupBy] = React19.useState("line");
|
|
38361
|
+
const operationalDate = getOperationalDate();
|
|
38362
|
+
const currentHour = (/* @__PURE__ */ new Date()).getHours();
|
|
38363
|
+
const isNightShift = currentHour >= 18 || currentHour < 6;
|
|
38364
|
+
const shiftType = isNightShift ? "Night" : "Day";
|
|
38365
|
+
const formatDate = (date) => {
|
|
38366
|
+
const d = new Date(date);
|
|
38367
|
+
return d.toLocaleDateString("en-IN", {
|
|
38368
|
+
month: "long",
|
|
38369
|
+
day: "numeric",
|
|
38370
|
+
year: "numeric",
|
|
38371
|
+
timeZone: "Asia/Kolkata"
|
|
38372
|
+
});
|
|
38373
|
+
};
|
|
38374
|
+
const getShiftIcon = (shift) => {
|
|
38375
|
+
return shift === "Night" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Moon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sun, { className: "h-4 w-4" });
|
|
38376
|
+
};
|
|
38377
|
+
const {
|
|
38378
|
+
workspaces,
|
|
38379
|
+
summary,
|
|
38380
|
+
loading,
|
|
38381
|
+
error,
|
|
38382
|
+
refetch
|
|
38383
|
+
} = useWorkspaceHealth({
|
|
38384
|
+
lineId,
|
|
38385
|
+
companyId,
|
|
38386
|
+
enableRealtime: true,
|
|
38387
|
+
refreshInterval: 1e4
|
|
38388
|
+
// Refresh every 10 seconds for more responsive updates
|
|
38389
|
+
});
|
|
38390
|
+
const handleWorkspaceClick = React19.useCallback(
|
|
38391
|
+
(workspace) => {
|
|
38392
|
+
const url = `/workspace/${workspace.workspace_id}`;
|
|
38393
|
+
if (onNavigate) {
|
|
38394
|
+
onNavigate(url);
|
|
38395
|
+
} else {
|
|
38396
|
+
router$1.push(url);
|
|
38397
|
+
}
|
|
38398
|
+
},
|
|
38399
|
+
[router$1, onNavigate]
|
|
38400
|
+
);
|
|
38401
|
+
const handleExport = React19.useCallback(() => {
|
|
38402
|
+
const csv = [
|
|
38403
|
+
["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
|
|
38404
|
+
...workspaces.map((w) => [
|
|
38405
|
+
w.workspace_display_name || "",
|
|
38406
|
+
w.line_name || "",
|
|
38407
|
+
w.company_name || "",
|
|
38408
|
+
w.status,
|
|
38409
|
+
w.last_heartbeat,
|
|
38410
|
+
w.consecutive_misses?.toString() || "0"
|
|
38411
|
+
])
|
|
38412
|
+
].map((row) => row.join(",")).join("\n");
|
|
38413
|
+
const blob = new Blob([csv], { type: "text/csv" });
|
|
38414
|
+
const url = window.URL.createObjectURL(blob);
|
|
38415
|
+
const a = document.createElement("a");
|
|
38416
|
+
a.href = url;
|
|
38417
|
+
a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
|
|
38418
|
+
document.body.appendChild(a);
|
|
38419
|
+
a.click();
|
|
38420
|
+
document.body.removeChild(a);
|
|
38421
|
+
window.URL.revokeObjectURL(url);
|
|
38422
|
+
}, [workspaces]);
|
|
38423
|
+
const getStatusIcon = (status) => {
|
|
38424
|
+
switch (status) {
|
|
38425
|
+
case "healthy":
|
|
38426
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle, { className: "h-5 w-5 text-green-500" });
|
|
38427
|
+
case "unhealthy":
|
|
38428
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "h-5 w-5 text-red-500" });
|
|
38429
|
+
case "warning":
|
|
38430
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-5 w-5 text-yellow-500" });
|
|
38431
|
+
default:
|
|
38432
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-5 w-5 text-gray-400" });
|
|
38433
|
+
}
|
|
38434
|
+
};
|
|
38435
|
+
const getUptimeColor = (percentage) => {
|
|
38436
|
+
if (percentage >= 99) return "text-green-600 dark:text-green-400";
|
|
38437
|
+
if (percentage >= 95) return "text-yellow-600 dark:text-yellow-400";
|
|
38438
|
+
return "text-red-600 dark:text-red-400";
|
|
38439
|
+
};
|
|
38440
|
+
if (loading && !summary) {
|
|
38441
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 p-4", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingState, {}) });
|
|
38442
|
+
}
|
|
38443
|
+
if (error) {
|
|
38444
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 p-4", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-7xl mx-auto", children: /* @__PURE__ */ jsxRuntime.jsx(Card2, { className: "border-red-200 dark:border-red-800", children: /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { className: "p-8 text-center", children: [
|
|
38445
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "h-12 w-12 text-red-500 mx-auto mb-4" }),
|
|
38446
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2", children: "Error Loading Health Status" }),
|
|
38447
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 dark:text-gray-400 mb-4", children: error.message || "Unable to load workspace health status" }),
|
|
38448
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
38449
|
+
"button",
|
|
38450
|
+
{
|
|
38451
|
+
onClick: () => refetch(),
|
|
38452
|
+
className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors",
|
|
38453
|
+
children: "Try Again"
|
|
38454
|
+
}
|
|
38455
|
+
)
|
|
38456
|
+
] }) }) }) });
|
|
38457
|
+
}
|
|
38458
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
|
|
38459
|
+
/* @__PURE__ */ jsxRuntime.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: [
|
|
38460
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
|
|
38461
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
38462
|
+
BackButtonMinimal,
|
|
38463
|
+
{
|
|
38464
|
+
onClick: () => router$1.push("/"),
|
|
38465
|
+
text: "Back",
|
|
38466
|
+
size: "default",
|
|
38467
|
+
"aria-label": "Navigate back to dashboard"
|
|
38468
|
+
}
|
|
38469
|
+
) }),
|
|
38470
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
38471
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "System Health" }),
|
|
38472
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
|
|
38473
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
|
|
38474
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
|
|
38475
|
+
] })
|
|
38476
|
+
] }) }),
|
|
38477
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 flex gap-2", children: [
|
|
38478
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
38479
|
+
"button",
|
|
38480
|
+
{
|
|
38481
|
+
onClick: () => {
|
|
38482
|
+
refetch();
|
|
38483
|
+
},
|
|
38484
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
38485
|
+
"aria-label": "Refresh",
|
|
38486
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
|
|
38487
|
+
}
|
|
38488
|
+
),
|
|
38489
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
38490
|
+
"button",
|
|
38491
|
+
{
|
|
38492
|
+
onClick: handleExport,
|
|
38493
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
38494
|
+
"aria-label": "Export CSV",
|
|
38495
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
|
|
38496
|
+
}
|
|
38497
|
+
)
|
|
38498
|
+
] }),
|
|
38499
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
|
|
38500
|
+
] }),
|
|
38501
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 bg-blue-50 px-3 py-2 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-4", children: [
|
|
38502
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsxRuntime.jsx(LiveTimer, {}) }),
|
|
38503
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
38504
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
|
|
38505
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
38506
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
38507
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
|
|
38508
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-base font-medium text-blue-600", children: [
|
|
38509
|
+
shiftType,
|
|
38510
|
+
" Shift"
|
|
38511
|
+
] })
|
|
38512
|
+
] })
|
|
38513
|
+
] }) })
|
|
38514
|
+
] }),
|
|
38515
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
38516
|
+
summary && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
38517
|
+
motion.div,
|
|
38518
|
+
{
|
|
38519
|
+
initial: { opacity: 0, y: 20 },
|
|
38520
|
+
animate: { opacity: 1, y: 0 },
|
|
38521
|
+
transition: { duration: 0.3, delay: 0.1 },
|
|
38522
|
+
className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
|
|
38523
|
+
children: [
|
|
38524
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2", children: [
|
|
38525
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "Overall System Status" }) }),
|
|
38526
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
38527
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [
|
|
38528
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
|
|
38529
|
+
summary.uptimePercentage.toFixed(1),
|
|
38530
|
+
"%"
|
|
38531
|
+
] }),
|
|
38532
|
+
summary.uptimePercentage >= 99 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingUp, { className: "h-5 w-5 text-green-500" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingDown, { className: "h-5 w-5 text-red-500" })
|
|
38533
|
+
] }),
|
|
38534
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: [
|
|
38535
|
+
summary.healthyWorkspaces,
|
|
38536
|
+
" of ",
|
|
38537
|
+
summary.totalWorkspaces,
|
|
38538
|
+
" workspaces healthy"
|
|
38539
|
+
] })
|
|
38540
|
+
] })
|
|
38541
|
+
] }),
|
|
38542
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { children: [
|
|
38543
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38544
|
+
getStatusIcon("healthy"),
|
|
38545
|
+
"Healthy"
|
|
38546
|
+
] }) }),
|
|
38547
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
38548
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-green-600 dark:text-green-400", children: summary.healthyWorkspaces }),
|
|
38549
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Operating normally" })
|
|
38550
|
+
] })
|
|
38551
|
+
] }),
|
|
38552
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { children: [
|
|
38553
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38554
|
+
getStatusIcon("warning"),
|
|
38555
|
+
"Warning"
|
|
38556
|
+
] }) }),
|
|
38557
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
38558
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-yellow-600 dark:text-yellow-400", children: summary.warningWorkspaces }),
|
|
38559
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Delayed updates" })
|
|
38560
|
+
] })
|
|
38561
|
+
] }),
|
|
38562
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { children: [
|
|
38563
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38564
|
+
getStatusIcon("unhealthy"),
|
|
38565
|
+
"Unhealthy"
|
|
38566
|
+
] }) }),
|
|
38567
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
38568
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-red-600 dark:text-red-400", children: summary.unhealthyWorkspaces }),
|
|
38569
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
|
|
38570
|
+
] })
|
|
38571
|
+
] })
|
|
38572
|
+
]
|
|
38573
|
+
}
|
|
38574
|
+
),
|
|
38575
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
38576
|
+
motion.div,
|
|
38577
|
+
{
|
|
38578
|
+
initial: { opacity: 0, y: 20 },
|
|
38579
|
+
animate: { opacity: 1, y: 0 },
|
|
38580
|
+
transition: { duration: 0.3, delay: 0.2 },
|
|
38581
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
38582
|
+
HealthStatusGrid,
|
|
38583
|
+
{
|
|
38584
|
+
workspaces,
|
|
38585
|
+
onWorkspaceClick: handleWorkspaceClick,
|
|
38586
|
+
viewMode,
|
|
38587
|
+
showFilters: true,
|
|
38588
|
+
groupBy
|
|
38589
|
+
}
|
|
38590
|
+
)
|
|
38591
|
+
}
|
|
38592
|
+
)
|
|
38593
|
+
] })
|
|
38594
|
+
] });
|
|
38595
|
+
};
|
|
38596
|
+
var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
|
|
38597
|
+
redirectTo: "/login",
|
|
38598
|
+
requireAuth: true
|
|
38599
|
+
});
|
|
38600
|
+
var AuthenticatedWorkspaceHealthView = WorkspaceHealthView;
|
|
37285
38601
|
var S3Service = class {
|
|
37286
38602
|
constructor(config) {
|
|
37287
38603
|
this.s3Client = null;
|
|
@@ -37748,6 +39064,9 @@ exports.AuthenticatedHelpView = AuthenticatedHelpView;
|
|
|
37748
39064
|
exports.AuthenticatedHomeView = AuthenticatedHomeView;
|
|
37749
39065
|
exports.AuthenticatedShiftsView = AuthenticatedShiftsView;
|
|
37750
39066
|
exports.AuthenticatedTargetsView = AuthenticatedTargetsView;
|
|
39067
|
+
exports.AuthenticatedWorkspaceHealthView = AuthenticatedWorkspaceHealthView;
|
|
39068
|
+
exports.BackButton = BackButton;
|
|
39069
|
+
exports.BackButtonMinimal = BackButtonMinimal;
|
|
37751
39070
|
exports.BarChart = BarChart;
|
|
37752
39071
|
exports.BaseHistoryCalendar = BaseHistoryCalendar;
|
|
37753
39072
|
exports.BottlenecksContent = BottlenecksContent;
|
|
@@ -37759,6 +39078,7 @@ exports.CardDescription = CardDescription2;
|
|
|
37759
39078
|
exports.CardFooter = CardFooter2;
|
|
37760
39079
|
exports.CardHeader = CardHeader2;
|
|
37761
39080
|
exports.CardTitle = CardTitle2;
|
|
39081
|
+
exports.CompactWorkspaceHealthCard = CompactWorkspaceHealthCard;
|
|
37762
39082
|
exports.CongratulationsOverlay = CongratulationsOverlay;
|
|
37763
39083
|
exports.CycleTimeChart = CycleTimeChart;
|
|
37764
39084
|
exports.CycleTimeOverTimeChart = CycleTimeOverTimeChart;
|
|
@@ -37782,6 +39102,7 @@ exports.DateDisplay = DateDisplay_default;
|
|
|
37782
39102
|
exports.DateTimeDisplay = DateTimeDisplay;
|
|
37783
39103
|
exports.DebugAuth = DebugAuth;
|
|
37784
39104
|
exports.DebugAuthView = DebugAuthView_default;
|
|
39105
|
+
exports.DetailedHealthStatus = DetailedHealthStatus;
|
|
37785
39106
|
exports.EmptyStateMessage = EmptyStateMessage;
|
|
37786
39107
|
exports.EncouragementOverlay = EncouragementOverlay;
|
|
37787
39108
|
exports.FactoryView = FactoryView_default;
|
|
@@ -37789,10 +39110,13 @@ exports.GaugeChart = GaugeChart;
|
|
|
37789
39110
|
exports.GridComponentsPlaceholder = GridComponentsPlaceholder;
|
|
37790
39111
|
exports.HamburgerButton = HamburgerButton;
|
|
37791
39112
|
exports.Header = Header;
|
|
39113
|
+
exports.HealthStatusGrid = HealthStatusGrid;
|
|
39114
|
+
exports.HealthStatusIndicator = HealthStatusIndicator;
|
|
37792
39115
|
exports.HelpView = HelpView_default;
|
|
37793
39116
|
exports.HomeView = HomeView_default;
|
|
37794
39117
|
exports.HourlyOutputChart = HourlyOutputChart2;
|
|
37795
39118
|
exports.ISTTimer = ISTTimer_default;
|
|
39119
|
+
exports.InlineEditableText = InlineEditableText;
|
|
37796
39120
|
exports.KPICard = KPICard;
|
|
37797
39121
|
exports.KPIDetailView = KPIDetailView_default;
|
|
37798
39122
|
exports.KPIGrid = KPIGrid;
|
|
@@ -37834,6 +39158,7 @@ exports.PrefetchStatus = PrefetchStatus;
|
|
|
37834
39158
|
exports.PrefetchTimeoutError = PrefetchTimeoutError;
|
|
37835
39159
|
exports.ProfileView = ProfileView_default;
|
|
37836
39160
|
exports.RegistryProvider = RegistryProvider;
|
|
39161
|
+
exports.S3ClipsService = S3ClipsService;
|
|
37837
39162
|
exports.S3Service = S3Service;
|
|
37838
39163
|
exports.SKUManagementView = SKUManagementView;
|
|
37839
39164
|
exports.SOPComplianceChart = SOPComplianceChart;
|
|
@@ -37874,6 +39199,8 @@ exports.WorkspaceDetailView = WorkspaceDetailView_default;
|
|
|
37874
39199
|
exports.WorkspaceDisplayNameExample = WorkspaceDisplayNameExample;
|
|
37875
39200
|
exports.WorkspaceGrid = WorkspaceGrid;
|
|
37876
39201
|
exports.WorkspaceGridItem = WorkspaceGridItem;
|
|
39202
|
+
exports.WorkspaceHealthCard = WorkspaceHealthCard;
|
|
39203
|
+
exports.WorkspaceHealthView = WorkspaceHealthView_default;
|
|
37877
39204
|
exports.WorkspaceHistoryCalendar = WorkspaceHistoryCalendar;
|
|
37878
39205
|
exports.WorkspaceMetricCards = WorkspaceMetricCards;
|
|
37879
39206
|
exports.WorkspaceMonthlyDataFetcher = WorkspaceMonthlyDataFetcher;
|
|
@@ -37958,6 +39285,7 @@ exports.isWorkspaceDisplayNamesLoading = isWorkspaceDisplayNamesLoading;
|
|
|
37958
39285
|
exports.mergeWithDefaultConfig = mergeWithDefaultConfig;
|
|
37959
39286
|
exports.migrateLegacyConfiguration = migrateLegacyConfiguration;
|
|
37960
39287
|
exports.optifyeAgentClient = optifyeAgentClient;
|
|
39288
|
+
exports.parseS3Uri = parseS3Uri;
|
|
37961
39289
|
exports.preInitializeWorkspaceDisplayNames = preInitializeWorkspaceDisplayNames;
|
|
37962
39290
|
exports.preloadS3Video = preloadS3Video;
|
|
37963
39291
|
exports.preloadS3VideoUrl = preloadS3VideoUrl;
|
|
@@ -37971,6 +39299,7 @@ exports.resetCoreMixpanel = resetCoreMixpanel;
|
|
|
37971
39299
|
exports.resetFailedUrl = resetFailedUrl;
|
|
37972
39300
|
exports.resetSubscriptionManager = resetSubscriptionManager;
|
|
37973
39301
|
exports.s3VideoPreloader = s3VideoPreloader;
|
|
39302
|
+
exports.shuffleArray = shuffleArray;
|
|
37974
39303
|
exports.skuService = skuService;
|
|
37975
39304
|
exports.startCoreSessionRecording = startCoreSessionRecording;
|
|
37976
39305
|
exports.stopCoreSessionRecording = stopCoreSessionRecording;
|
|
@@ -38037,6 +39366,8 @@ exports.useWorkspaceDetailedMetrics = useWorkspaceDetailedMetrics;
|
|
|
38037
39366
|
exports.useWorkspaceDisplayName = useWorkspaceDisplayName;
|
|
38038
39367
|
exports.useWorkspaceDisplayNames = useWorkspaceDisplayNames;
|
|
38039
39368
|
exports.useWorkspaceDisplayNamesMap = useWorkspaceDisplayNamesMap;
|
|
39369
|
+
exports.useWorkspaceHealth = useWorkspaceHealth;
|
|
39370
|
+
exports.useWorkspaceHealthById = useWorkspaceHealthById;
|
|
38040
39371
|
exports.useWorkspaceMetrics = useWorkspaceMetrics;
|
|
38041
39372
|
exports.useWorkspaceNavigation = useWorkspaceNavigation;
|
|
38042
39373
|
exports.useWorkspaceOperators = useWorkspaceOperators;
|
|
@@ -38045,4 +39376,5 @@ exports.videoPreloader = videoPreloader;
|
|
|
38045
39376
|
exports.whatsappService = whatsappService;
|
|
38046
39377
|
exports.withAuth = withAuth;
|
|
38047
39378
|
exports.withRegistry = withRegistry;
|
|
39379
|
+
exports.workspaceHealthService = workspaceHealthService;
|
|
38048
39380
|
exports.workspaceService = workspaceService;
|