@optifye/dashboard-core 6.10.1 → 6.10.2
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 +6 -8
- package/dist/index.d.mts +67 -7
- package/dist/index.d.ts +67 -7
- package/dist/index.js +1186 -557
- package/dist/index.mjs +1181 -559
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1847,22 +1847,112 @@ var qualityService = {
|
|
|
1847
1847
|
}
|
|
1848
1848
|
};
|
|
1849
1849
|
|
|
1850
|
-
// src/lib/services/
|
|
1850
|
+
// src/lib/services/backendClient.ts
|
|
1851
|
+
var ACCESS_TOKEN_REFRESH_BUFFER_MS = 6e4;
|
|
1852
|
+
var cachedAccessToken = null;
|
|
1853
|
+
var cachedAccessTokenExpiresAtMs = null;
|
|
1854
|
+
var cachedUserId = null;
|
|
1855
|
+
var inFlightRequests = /* @__PURE__ */ new Map();
|
|
1856
|
+
var authListenerSubscription = null;
|
|
1857
|
+
var authListenerSupabase = null;
|
|
1858
|
+
var clearBackendClientCaches = () => {
|
|
1859
|
+
cachedAccessToken = null;
|
|
1860
|
+
cachedAccessTokenExpiresAtMs = null;
|
|
1861
|
+
cachedUserId = null;
|
|
1862
|
+
inFlightRequests.clear();
|
|
1863
|
+
};
|
|
1864
|
+
var initBackendClientAuthListener = (supabase) => {
|
|
1865
|
+
if (authListenerSupabase === supabase && authListenerSubscription) return;
|
|
1866
|
+
if (authListenerSubscription) {
|
|
1867
|
+
authListenerSubscription.unsubscribe();
|
|
1868
|
+
authListenerSubscription = null;
|
|
1869
|
+
}
|
|
1870
|
+
authListenerSupabase = supabase;
|
|
1871
|
+
const { data } = supabase.auth.onAuthStateChange(() => {
|
|
1872
|
+
clearBackendClientCaches();
|
|
1873
|
+
});
|
|
1874
|
+
authListenerSubscription = data.subscription;
|
|
1875
|
+
};
|
|
1851
1876
|
var getBackendUrl = () => {
|
|
1852
1877
|
const url = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
1853
1878
|
if (!url) {
|
|
1854
1879
|
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
1855
1880
|
}
|
|
1856
|
-
return url;
|
|
1881
|
+
return url.replace(/\/$/, "");
|
|
1857
1882
|
};
|
|
1858
|
-
var getAuthToken = async () => {
|
|
1859
|
-
const
|
|
1860
|
-
if (
|
|
1861
|
-
|
|
1883
|
+
var getAuthToken = async (supabase) => {
|
|
1884
|
+
const now2 = Date.now();
|
|
1885
|
+
if (cachedAccessToken && cachedAccessTokenExpiresAtMs && now2 < cachedAccessTokenExpiresAtMs - ACCESS_TOKEN_REFRESH_BUFFER_MS) {
|
|
1886
|
+
return cachedAccessToken;
|
|
1887
|
+
}
|
|
1888
|
+
const {
|
|
1889
|
+
data: { session }
|
|
1890
|
+
} = await supabase.auth.getSession();
|
|
1862
1891
|
if (!session?.access_token) {
|
|
1892
|
+
clearBackendClientCaches();
|
|
1863
1893
|
throw new Error("No authentication token available. Please log in.");
|
|
1864
1894
|
}
|
|
1865
|
-
|
|
1895
|
+
cachedAccessToken = session.access_token;
|
|
1896
|
+
cachedUserId = session.user?.id || null;
|
|
1897
|
+
const expiresAtSeconds = session?.expires_at;
|
|
1898
|
+
cachedAccessTokenExpiresAtMs = typeof expiresAtSeconds === "number" ? expiresAtSeconds * 1e3 : now2 + 5 * 60 * 1e3;
|
|
1899
|
+
return cachedAccessToken;
|
|
1900
|
+
};
|
|
1901
|
+
var defaultDedupeKey = (method, url, body) => {
|
|
1902
|
+
const bodyKey = body === void 0 ? "" : typeof body === "string" ? body : JSON.stringify(body);
|
|
1903
|
+
return `${cachedUserId || "anon"}::${method.toUpperCase()}::${url}::${bodyKey}`;
|
|
1904
|
+
};
|
|
1905
|
+
var fetchBackendJson = async (supabase, endpoint, options = {}) => {
|
|
1906
|
+
const baseUrl = getBackendUrl();
|
|
1907
|
+
const url = endpoint.startsWith("http") ? endpoint : `${baseUrl}${endpoint.startsWith("/") ? "" : "/"}${endpoint}`;
|
|
1908
|
+
const method = (options.method || "GET").toString();
|
|
1909
|
+
const bodyForKey = options.body;
|
|
1910
|
+
const dedupeKey = options.dedupeKey || defaultDedupeKey(method, url, bodyForKey);
|
|
1911
|
+
const existing = inFlightRequests.get(dedupeKey);
|
|
1912
|
+
if (existing) {
|
|
1913
|
+
return existing;
|
|
1914
|
+
}
|
|
1915
|
+
const requestPromise = (async () => {
|
|
1916
|
+
const headers = new Headers(options.headers || {});
|
|
1917
|
+
if (!options.skipAuth) {
|
|
1918
|
+
const token = await getAuthToken(supabase);
|
|
1919
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
1920
|
+
}
|
|
1921
|
+
if (!headers.has("Content-Type") && options.body !== void 0) {
|
|
1922
|
+
headers.set("Content-Type", "application/json");
|
|
1923
|
+
}
|
|
1924
|
+
const response = await fetch(url, {
|
|
1925
|
+
...options,
|
|
1926
|
+
headers
|
|
1927
|
+
});
|
|
1928
|
+
if (!response.ok) {
|
|
1929
|
+
const errorText = await response.text();
|
|
1930
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
1931
|
+
}
|
|
1932
|
+
if (response.status === 204) return void 0;
|
|
1933
|
+
const text = await response.text();
|
|
1934
|
+
return text ? JSON.parse(text) : void 0;
|
|
1935
|
+
})();
|
|
1936
|
+
inFlightRequests.set(dedupeKey, requestPromise);
|
|
1937
|
+
try {
|
|
1938
|
+
return await requestPromise;
|
|
1939
|
+
} finally {
|
|
1940
|
+
inFlightRequests.delete(dedupeKey);
|
|
1941
|
+
}
|
|
1942
|
+
};
|
|
1943
|
+
|
|
1944
|
+
// src/lib/services/workspaceService.ts
|
|
1945
|
+
var getBackendUrl2 = () => {
|
|
1946
|
+
const url = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
1947
|
+
if (!url) {
|
|
1948
|
+
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
1949
|
+
}
|
|
1950
|
+
return url;
|
|
1951
|
+
};
|
|
1952
|
+
var getAuthToken2 = async () => {
|
|
1953
|
+
const supabase = _getSupabaseInstance();
|
|
1954
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1955
|
+
return getAuthToken(supabase);
|
|
1866
1956
|
};
|
|
1867
1957
|
var workspaceService = {
|
|
1868
1958
|
// Cache for workspace display names to avoid repeated API calls
|
|
@@ -1870,26 +1960,56 @@ var workspaceService = {
|
|
|
1870
1960
|
_cacheTimestamp: 0,
|
|
1871
1961
|
_cacheExpiryMs: 5 * 60 * 1e3,
|
|
1872
1962
|
// 5 minutes cache
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1963
|
+
// Cache for workspace lists to avoid repeated API calls (line configuration changes infrequently)
|
|
1964
|
+
_workspacesCache: /* @__PURE__ */ new Map(),
|
|
1965
|
+
_workspacesInFlight: /* @__PURE__ */ new Map(),
|
|
1966
|
+
_workspacesCacheExpiryMs: 10 * 60 * 1e3,
|
|
1967
|
+
// 10 minutes cache
|
|
1968
|
+
async getWorkspaces(lineId, options) {
|
|
1969
|
+
const enabledOnly = options?.enabledOnly ?? false;
|
|
1970
|
+
const force = options?.force ?? false;
|
|
1971
|
+
const cacheKey = `${lineId}::enabledOnly=${enabledOnly}`;
|
|
1972
|
+
const now2 = Date.now();
|
|
1973
|
+
const cached = this._workspacesCache.get(cacheKey);
|
|
1974
|
+
if (!force && cached && now2 - cached.timestamp < this._workspacesCacheExpiryMs) {
|
|
1975
|
+
return cached.workspaces;
|
|
1976
|
+
}
|
|
1977
|
+
const inFlight = this._workspacesInFlight.get(cacheKey);
|
|
1978
|
+
if (!force && inFlight) {
|
|
1979
|
+
return inFlight;
|
|
1980
|
+
}
|
|
1981
|
+
const fetchPromise = (async () => {
|
|
1982
|
+
try {
|
|
1983
|
+
const token = await getAuthToken2();
|
|
1984
|
+
const apiUrl = getBackendUrl2();
|
|
1985
|
+
const params = new URLSearchParams({ line_id: lineId });
|
|
1986
|
+
if (enabledOnly) params.set("enabled_only", "true");
|
|
1987
|
+
const response = await fetch(`${apiUrl}/api/workspaces?${params.toString()}`, {
|
|
1988
|
+
headers: {
|
|
1989
|
+
"Authorization": `Bearer ${token}`,
|
|
1990
|
+
"Content-Type": "application/json"
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
if (!response.ok) {
|
|
1994
|
+
const errorText = await response.text();
|
|
1995
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
1881
1996
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1997
|
+
const data = await response.json();
|
|
1998
|
+
const workspaces = data.workspaces || [];
|
|
1999
|
+
this._workspacesCache.set(cacheKey, { workspaces, timestamp: Date.now() });
|
|
2000
|
+
return workspaces;
|
|
2001
|
+
} catch (error) {
|
|
2002
|
+
console.error("Error fetching workspaces:", error);
|
|
2003
|
+
throw error;
|
|
2004
|
+
} finally {
|
|
2005
|
+
this._workspacesInFlight.delete(cacheKey);
|
|
1886
2006
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
}
|
|
2007
|
+
})();
|
|
2008
|
+
this._workspacesInFlight.set(cacheKey, fetchPromise);
|
|
2009
|
+
return fetchPromise;
|
|
2010
|
+
},
|
|
2011
|
+
async getEnabledWorkspaces(lineId, options) {
|
|
2012
|
+
return this.getWorkspaces(lineId, { enabledOnly: true, force: options?.force });
|
|
1893
2013
|
},
|
|
1894
2014
|
/**
|
|
1895
2015
|
* Fetches workspace display names from the database
|
|
@@ -1897,8 +2017,8 @@ var workspaceService = {
|
|
|
1897
2017
|
*/
|
|
1898
2018
|
async getWorkspaceDisplayNames(companyId, lineId) {
|
|
1899
2019
|
try {
|
|
1900
|
-
const token = await
|
|
1901
|
-
const apiUrl =
|
|
2020
|
+
const token = await getAuthToken2();
|
|
2021
|
+
const apiUrl = getBackendUrl2();
|
|
1902
2022
|
const params = new URLSearchParams();
|
|
1903
2023
|
if (companyId) params.append("company_id", companyId);
|
|
1904
2024
|
if (lineId) params.append("line_id", lineId);
|
|
@@ -1957,16 +2077,39 @@ var workspaceService = {
|
|
|
1957
2077
|
this._workspaceDisplayNamesCache.clear();
|
|
1958
2078
|
this._cacheTimestamp = 0;
|
|
1959
2079
|
},
|
|
2080
|
+
clearWorkspacesCache() {
|
|
2081
|
+
this._workspacesCache.clear();
|
|
2082
|
+
this._workspacesInFlight.clear();
|
|
2083
|
+
},
|
|
2084
|
+
invalidateWorkspacesCacheForLine(lineId) {
|
|
2085
|
+
const prefix = `${lineId}::`;
|
|
2086
|
+
Array.from(this._workspacesCache.keys()).forEach((key) => {
|
|
2087
|
+
if (key.startsWith(prefix)) this._workspacesCache.delete(key);
|
|
2088
|
+
});
|
|
2089
|
+
Array.from(this._workspacesInFlight.keys()).forEach((key) => {
|
|
2090
|
+
if (key.startsWith(prefix)) this._workspacesInFlight.delete(key);
|
|
2091
|
+
});
|
|
2092
|
+
},
|
|
2093
|
+
_patchCachedWorkspacesForLine(lineId, workspaceRowId, patch) {
|
|
2094
|
+
const prefix = `${lineId}::`;
|
|
2095
|
+
Array.from(this._workspacesCache.entries()).forEach(([key, entry]) => {
|
|
2096
|
+
if (!key.startsWith(prefix)) return;
|
|
2097
|
+
const nextWorkspaces = (entry.workspaces || []).map(
|
|
2098
|
+
(ws) => ws?.id === workspaceRowId ? { ...ws, ...patch } : ws
|
|
2099
|
+
);
|
|
2100
|
+
this._workspacesCache.set(key, { workspaces: nextWorkspaces, timestamp: Date.now() });
|
|
2101
|
+
});
|
|
2102
|
+
},
|
|
1960
2103
|
/**
|
|
1961
2104
|
* Updates the display name for a workspace
|
|
1962
2105
|
* @param workspaceId - The workspace UUID
|
|
1963
2106
|
* @param displayName - The new display name
|
|
1964
|
-
* @returns
|
|
2107
|
+
* @returns Updated workspace record from backend
|
|
1965
2108
|
*/
|
|
1966
2109
|
async updateWorkspaceDisplayName(workspaceId, displayName) {
|
|
1967
2110
|
try {
|
|
1968
|
-
const token = await
|
|
1969
|
-
const apiUrl =
|
|
2111
|
+
const token = await getAuthToken2();
|
|
2112
|
+
const apiUrl = getBackendUrl2();
|
|
1970
2113
|
const response = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/display-name`, {
|
|
1971
2114
|
method: "PATCH",
|
|
1972
2115
|
headers: {
|
|
@@ -1979,7 +2122,15 @@ var workspaceService = {
|
|
|
1979
2122
|
const errorText = await response.text();
|
|
1980
2123
|
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
1981
2124
|
}
|
|
2125
|
+
const data = await response.json();
|
|
1982
2126
|
this.clearWorkspaceDisplayNamesCache();
|
|
2127
|
+
const updatedWorkspace = data?.workspace || null;
|
|
2128
|
+
if (updatedWorkspace?.line_id && updatedWorkspace?.id) {
|
|
2129
|
+
this._patchCachedWorkspacesForLine(updatedWorkspace.line_id, updatedWorkspace.id, {
|
|
2130
|
+
display_name: updatedWorkspace.display_name
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
return updatedWorkspace;
|
|
1983
2134
|
} catch (error) {
|
|
1984
2135
|
console.error(`Error updating workspace display name for ${workspaceId}:`, error);
|
|
1985
2136
|
throw error;
|
|
@@ -1987,8 +2138,8 @@ var workspaceService = {
|
|
|
1987
2138
|
},
|
|
1988
2139
|
async updateWorkspaceAction(updates) {
|
|
1989
2140
|
try {
|
|
1990
|
-
const token = await
|
|
1991
|
-
const apiUrl =
|
|
2141
|
+
const token = await getAuthToken2();
|
|
2142
|
+
const apiUrl = getBackendUrl2();
|
|
1992
2143
|
const response = await fetch(`${apiUrl}/api/workspaces/actions/update`, {
|
|
1993
2144
|
method: "POST",
|
|
1994
2145
|
headers: {
|
|
@@ -2001,6 +2152,7 @@ var workspaceService = {
|
|
|
2001
2152
|
const errorText = await response.text();
|
|
2002
2153
|
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
2003
2154
|
}
|
|
2155
|
+
this.clearWorkspacesCache();
|
|
2004
2156
|
} catch (error) {
|
|
2005
2157
|
console.error("Error updating workspace actions:", error);
|
|
2006
2158
|
throw error;
|
|
@@ -2008,8 +2160,8 @@ var workspaceService = {
|
|
|
2008
2160
|
},
|
|
2009
2161
|
async updateActionThresholds(thresholds) {
|
|
2010
2162
|
try {
|
|
2011
|
-
const token = await
|
|
2012
|
-
const apiUrl =
|
|
2163
|
+
const token = await getAuthToken2();
|
|
2164
|
+
const apiUrl = getBackendUrl2();
|
|
2013
2165
|
const response = await fetch(`${apiUrl}/api/workspaces/action-thresholds/update`, {
|
|
2014
2166
|
method: "POST",
|
|
2015
2167
|
headers: {
|
|
@@ -2029,8 +2181,8 @@ var workspaceService = {
|
|
|
2029
2181
|
},
|
|
2030
2182
|
async getActionThresholds(lineId, date, shiftId = 0) {
|
|
2031
2183
|
try {
|
|
2032
|
-
const token = await
|
|
2033
|
-
const apiUrl =
|
|
2184
|
+
const token = await getAuthToken2();
|
|
2185
|
+
const apiUrl = getBackendUrl2();
|
|
2034
2186
|
const response = await fetch(
|
|
2035
2187
|
`${apiUrl}/api/workspaces/action-thresholds?line_id=${lineId}&date=${date}&shift_id=${shiftId}`,
|
|
2036
2188
|
{
|
|
@@ -2085,8 +2237,8 @@ var workspaceService = {
|
|
|
2085
2237
|
const totalPPH = outputWorkspaces.reduce((sum, ws) => sum + (ws.action_pph_threshold || 0), 0);
|
|
2086
2238
|
const operationalDate = getOperationalDate(defaultTimezone || "UTC");
|
|
2087
2239
|
try {
|
|
2088
|
-
const token = await
|
|
2089
|
-
const apiUrl =
|
|
2240
|
+
const token = await getAuthToken2();
|
|
2241
|
+
const apiUrl = getBackendUrl2();
|
|
2090
2242
|
const response = await fetch(
|
|
2091
2243
|
`${apiUrl}/api/workspaces/line-thresholds?line_id=${lineId}`,
|
|
2092
2244
|
{
|
|
@@ -2117,8 +2269,8 @@ var workspaceService = {
|
|
|
2117
2269
|
// Returns ShiftConfiguration array, which is the logical representation.
|
|
2118
2270
|
async getShiftConfigurations(lineId) {
|
|
2119
2271
|
try {
|
|
2120
|
-
const token = await
|
|
2121
|
-
const apiUrl =
|
|
2272
|
+
const token = await getAuthToken2();
|
|
2273
|
+
const apiUrl = getBackendUrl2();
|
|
2122
2274
|
const response = await fetch(
|
|
2123
2275
|
`${apiUrl}/api/workspaces/shift-configurations?line_id=${lineId}`,
|
|
2124
2276
|
{
|
|
@@ -2148,8 +2300,8 @@ var workspaceService = {
|
|
|
2148
2300
|
},
|
|
2149
2301
|
async updateShiftConfigurations(shiftConfig) {
|
|
2150
2302
|
try {
|
|
2151
|
-
const token = await
|
|
2152
|
-
const apiUrl =
|
|
2303
|
+
const token = await getAuthToken2();
|
|
2304
|
+
const apiUrl = getBackendUrl2();
|
|
2153
2305
|
const response = await fetch(
|
|
2154
2306
|
`${apiUrl}/api/workspaces/shift-configurations`,
|
|
2155
2307
|
{
|
|
@@ -2186,8 +2338,8 @@ var workspaceService = {
|
|
|
2186
2338
|
*/
|
|
2187
2339
|
async fetchBulkTargets(params) {
|
|
2188
2340
|
try {
|
|
2189
|
-
const token = await
|
|
2190
|
-
const apiUrl =
|
|
2341
|
+
const token = await getAuthToken2();
|
|
2342
|
+
const apiUrl = getBackendUrl2();
|
|
2191
2343
|
const {
|
|
2192
2344
|
companyId,
|
|
2193
2345
|
lineIds,
|
|
@@ -2373,7 +2525,17 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2373
2525
|
}
|
|
2374
2526
|
}
|
|
2375
2527
|
const dataWithUptime = processedData.map((workspace) => {
|
|
2376
|
-
const
|
|
2528
|
+
const mapKey = this.getUptimeMapKey(workspace.line_id, workspace.workspace_id);
|
|
2529
|
+
const uptimeDetails = uptimeMap.get(mapKey);
|
|
2530
|
+
console.log(`[getWorkspaceHealthStatus] Lookup:`, {
|
|
2531
|
+
mapKey,
|
|
2532
|
+
workspaceLineId: workspace.line_id,
|
|
2533
|
+
workspaceId: workspace.workspace_id,
|
|
2534
|
+
workspaceName: workspace.workspace_display_name,
|
|
2535
|
+
found: !!uptimeDetails,
|
|
2536
|
+
uptime: uptimeDetails?.percentage,
|
|
2537
|
+
downtime: uptimeDetails ? Math.max(0, uptimeDetails.expectedMinutes - uptimeDetails.actualMinutes) : null
|
|
2538
|
+
});
|
|
2377
2539
|
if (uptimeDetails) {
|
|
2378
2540
|
return {
|
|
2379
2541
|
...workspace,
|
|
@@ -2734,6 +2896,13 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2734
2896
|
clearCache() {
|
|
2735
2897
|
this.cache.clear();
|
|
2736
2898
|
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Generate a composite key for the uptime map that includes line_id
|
|
2901
|
+
* This prevents data collision when multiple lines have workspaces with the same workspace_id
|
|
2902
|
+
*/
|
|
2903
|
+
getUptimeMapKey(lineId, workspaceId) {
|
|
2904
|
+
return lineId ? `${lineId}::${workspaceId}` : workspaceId;
|
|
2905
|
+
}
|
|
2737
2906
|
async calculateWorkspaceUptime(companyId, passedShiftConfig, timezone, lineShiftConfigs, overrideDate, overrideShiftId) {
|
|
2738
2907
|
const supabase = _getSupabaseInstance();
|
|
2739
2908
|
if (!supabase) throw new Error("Supabase client not initialized");
|
|
@@ -2763,7 +2932,7 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2763
2932
|
const queryShiftId = overrideShiftId ?? currentShiftId;
|
|
2764
2933
|
const tableName = `performance_metrics_${companyId.replace(/-/g, "_")}`;
|
|
2765
2934
|
try {
|
|
2766
|
-
const { data: queryData, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array").eq("date", queryDate).eq("shift_id", queryShiftId);
|
|
2935
|
+
const { data: queryData, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array, line_id").eq("date", queryDate).eq("shift_id", queryShiftId);
|
|
2767
2936
|
if (error) {
|
|
2768
2937
|
console.error("Error fetching performance metrics:", error);
|
|
2769
2938
|
return /* @__PURE__ */ new Map();
|
|
@@ -2809,7 +2978,8 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2809
2978
|
}
|
|
2810
2979
|
const completedWindow = uptimeMinutes + downtimeMinutes;
|
|
2811
2980
|
const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
2812
|
-
|
|
2981
|
+
const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
|
|
2982
|
+
uptimeMap.set(mapKey, {
|
|
2813
2983
|
expectedMinutes: completedMinutes,
|
|
2814
2984
|
actualMinutes: uptimeMinutes,
|
|
2815
2985
|
percentage,
|
|
@@ -2851,6 +3021,17 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2851
3021
|
uniqueQueries.get(key).lineConfigs.push({ lineId, shiftStartDate, completedMinutes });
|
|
2852
3022
|
});
|
|
2853
3023
|
console.log(`[calculateWorkspaceUptimeMultiLine] Querying ${uniqueQueries.size} unique date/shift combinations for ${lineShiftConfigs.size} lines`);
|
|
3024
|
+
uniqueQueries.forEach((queryConfig, key) => {
|
|
3025
|
+
console.log(`[calculateWorkspaceUptimeMultiLine] Query batch ${key}:`, {
|
|
3026
|
+
date: queryConfig.date,
|
|
3027
|
+
shiftId: queryConfig.shiftId,
|
|
3028
|
+
lineConfigs: queryConfig.lineConfigs.map((lc) => ({
|
|
3029
|
+
lineId: lc.lineId,
|
|
3030
|
+
completedMinutes: lc.completedMinutes,
|
|
3031
|
+
shiftStartDate: lc.shiftStartDate.toISOString()
|
|
3032
|
+
}))
|
|
3033
|
+
});
|
|
3034
|
+
});
|
|
2854
3035
|
const queryPromises = Array.from(uniqueQueries.entries()).map(async ([key, { date, shiftId, lineConfigs }]) => {
|
|
2855
3036
|
try {
|
|
2856
3037
|
const { data, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array, line_id").eq("date", date).eq("shift_id", shiftId);
|
|
@@ -2867,8 +3048,14 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2867
3048
|
const results = await Promise.all(queryPromises);
|
|
2868
3049
|
for (const { records, lineConfigs } of results) {
|
|
2869
3050
|
for (const record of records) {
|
|
2870
|
-
const lineConfig = lineConfigs.find((lc) =>
|
|
2871
|
-
|
|
3051
|
+
const lineConfig = lineConfigs.find((lc) => lc.lineId === record.line_id);
|
|
3052
|
+
console.log(`[calculateWorkspaceUptimeMultiLine] Record match:`, {
|
|
3053
|
+
recordLineId: record.line_id,
|
|
3054
|
+
recordWorkspaceId: record.workspace_id,
|
|
3055
|
+
recordWorkspaceDisplayName: record.workspace_display_name,
|
|
3056
|
+
availableLineIds: lineConfigs.map((lc) => lc.lineId),
|
|
3057
|
+
matchFound: !!lineConfig,
|
|
3058
|
+
matchedLineId: lineConfig?.lineId || "FALLBACK to " + lineConfigs[0]?.lineId
|
|
2872
3059
|
});
|
|
2873
3060
|
const effectiveLineConfig = lineConfig || lineConfigs[0];
|
|
2874
3061
|
if (!effectiveLineConfig) continue;
|
|
@@ -2911,12 +3098,22 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2911
3098
|
}
|
|
2912
3099
|
const completedWindow = uptimeMinutes + downtimeMinutes;
|
|
2913
3100
|
const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
2914
|
-
|
|
3101
|
+
const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
|
|
3102
|
+
uptimeMap.set(mapKey, {
|
|
2915
3103
|
expectedMinutes: completedMinutes,
|
|
2916
3104
|
actualMinutes: uptimeMinutes,
|
|
2917
3105
|
percentage,
|
|
2918
3106
|
lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2919
3107
|
});
|
|
3108
|
+
console.log(`[calculateWorkspaceUptimeMultiLine] Storing uptime:`, {
|
|
3109
|
+
mapKey,
|
|
3110
|
+
lineId: record.line_id,
|
|
3111
|
+
workspaceId: record.workspace_id,
|
|
3112
|
+
workspaceDisplayName: record.workspace_display_name,
|
|
3113
|
+
expectedMinutes: completedMinutes,
|
|
3114
|
+
actualMinutes: uptimeMinutes,
|
|
3115
|
+
percentage
|
|
3116
|
+
});
|
|
2920
3117
|
}
|
|
2921
3118
|
}
|
|
2922
3119
|
console.log(`[calculateWorkspaceUptimeMultiLine] Calculated uptime for ${uptimeMap.size} workspaces`);
|
|
@@ -4564,7 +4761,7 @@ var getSupabaseClient = () => {
|
|
|
4564
4761
|
}
|
|
4565
4762
|
return supabaseJs.createClient(url, key);
|
|
4566
4763
|
};
|
|
4567
|
-
var
|
|
4764
|
+
var getAuthToken3 = async () => {
|
|
4568
4765
|
try {
|
|
4569
4766
|
const supabase = getSupabaseClient();
|
|
4570
4767
|
const { data: { session } } = await supabase.auth.getSession();
|
|
@@ -4598,7 +4795,7 @@ var S3ClipsSupabaseService = class {
|
|
|
4598
4795
|
* Fetch with authentication and error handling
|
|
4599
4796
|
*/
|
|
4600
4797
|
async fetchWithAuth(endpoint, body) {
|
|
4601
|
-
const token = await
|
|
4798
|
+
const token = await getAuthToken3();
|
|
4602
4799
|
if (!token) {
|
|
4603
4800
|
throw new Error("Authentication required");
|
|
4604
4801
|
}
|
|
@@ -6308,7 +6505,7 @@ var IDLE_TIME_REASON_COLORS = {
|
|
|
6308
6505
|
bg: "bg-amber-50",
|
|
6309
6506
|
border: "border-amber-200"
|
|
6310
6507
|
},
|
|
6311
|
-
"Machine
|
|
6508
|
+
"Machine Downtime": {
|
|
6312
6509
|
hex: "#3b82f6",
|
|
6313
6510
|
// blue-500 - Scheduled/Technical
|
|
6314
6511
|
text: "text-blue-600",
|
|
@@ -6744,6 +6941,7 @@ var SupabaseProvider = ({ client, children }) => {
|
|
|
6744
6941
|
_setSupabaseInstance(client);
|
|
6745
6942
|
React24.useEffect(() => {
|
|
6746
6943
|
_setSupabaseInstance(client);
|
|
6944
|
+
initBackendClientAuthListener(client);
|
|
6747
6945
|
}, [client]);
|
|
6748
6946
|
const contextValue = React24.useMemo(() => ({ supabase: client }), [client]);
|
|
6749
6947
|
return /* @__PURE__ */ jsxRuntime.jsx(SupabaseContext.Provider, { value: contextValue, children });
|
|
@@ -7658,33 +7856,10 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
7658
7856
|
console.log("[useWorkspaceDetailedMetrics] Using shift ID:", queryShiftId, "from input shift:", shiftId);
|
|
7659
7857
|
console.log("[useWorkspaceDetailedMetrics] Using date:", queryDate, "from input date:", date);
|
|
7660
7858
|
console.log(`[useWorkspaceDetailedMetrics] Fetching from backend API for workspace: ${workspaceId}, date: ${queryDate}, shift: ${queryShiftId}`);
|
|
7661
|
-
const
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
}
|
|
7665
|
-
const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
7666
|
-
const response = await fetch(
|
|
7667
|
-
`${apiUrl}/api/dashboard/workspace/${workspaceId}/metrics?date=${queryDate}&shift_id=${queryShiftId}&company_id=${companyId}`,
|
|
7668
|
-
{
|
|
7669
|
-
headers: {
|
|
7670
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
7671
|
-
"Content-Type": "application/json"
|
|
7672
|
-
}
|
|
7673
|
-
}
|
|
7859
|
+
const backendData = await fetchBackendJson(
|
|
7860
|
+
supabase,
|
|
7861
|
+
`/api/dashboard/workspace/${workspaceId}/metrics?date=${queryDate}&shift_id=${queryShiftId}&company_id=${companyId}`
|
|
7674
7862
|
);
|
|
7675
|
-
if (!response.ok) {
|
|
7676
|
-
const errorText = await response.text();
|
|
7677
|
-
console.error("[useWorkspaceDetailedMetrics] Backend API error response:", errorText);
|
|
7678
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
7679
|
-
}
|
|
7680
|
-
const responseText = await response.text();
|
|
7681
|
-
let backendData;
|
|
7682
|
-
try {
|
|
7683
|
-
backendData = JSON.parse(responseText);
|
|
7684
|
-
} catch (parseError) {
|
|
7685
|
-
console.error("[useWorkspaceDetailedMetrics] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
|
|
7686
|
-
throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
|
|
7687
|
-
}
|
|
7688
7863
|
const data = backendData.metrics;
|
|
7689
7864
|
if (data && options?.shiftConfig) {
|
|
7690
7865
|
const dynamicShiftName = getShiftNameById(
|
|
@@ -7698,20 +7873,10 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
7698
7873
|
}
|
|
7699
7874
|
if (!data && !date && shiftId === void 0) {
|
|
7700
7875
|
console.log("[useWorkspaceDetailedMetrics] No data found for current date/shift, attempting to find most recent data...");
|
|
7701
|
-
const
|
|
7702
|
-
|
|
7703
|
-
{
|
|
7704
|
-
headers: {
|
|
7705
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
7706
|
-
"Content-Type": "application/json"
|
|
7707
|
-
}
|
|
7708
|
-
}
|
|
7876
|
+
const fallbackData = await fetchBackendJson(
|
|
7877
|
+
supabase,
|
|
7878
|
+
`/api/dashboard/workspace/${workspaceId}/metrics?company_id=${companyId}&latest=true`
|
|
7709
7879
|
);
|
|
7710
|
-
if (!fallbackResponse.ok) {
|
|
7711
|
-
const errorText = await fallbackResponse.text();
|
|
7712
|
-
throw new Error(`Backend API error (${fallbackResponse.status}): ${errorText}`);
|
|
7713
|
-
}
|
|
7714
|
-
const fallbackData = await fallbackResponse.json();
|
|
7715
7880
|
const recentData = fallbackData.metrics;
|
|
7716
7881
|
if (recentData) {
|
|
7717
7882
|
console.log(`[useWorkspaceDetailedMetrics] Found fallback data from date: ${recentData.date}, shift: ${recentData.shift_id}`);
|
|
@@ -8273,15 +8438,171 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
8273
8438
|
// Force refresh without cache
|
|
8274
8439
|
};
|
|
8275
8440
|
};
|
|
8441
|
+
|
|
8442
|
+
// src/lib/stores/shiftConfigStore.ts
|
|
8443
|
+
var shiftConfigsByLineId = /* @__PURE__ */ new Map();
|
|
8444
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
8445
|
+
var inFlightFetchesByLineId = /* @__PURE__ */ new Map();
|
|
8446
|
+
var subscriptionsByLineId = /* @__PURE__ */ new Map();
|
|
8447
|
+
var calculateBreakDuration = (startTime, endTime) => {
|
|
8448
|
+
const [sh, sm] = startTime.split(":").map(Number);
|
|
8449
|
+
const [eh, em] = endTime.split(":").map(Number);
|
|
8450
|
+
let startMinutes = sh * 60 + sm;
|
|
8451
|
+
let endMinutes = eh * 60 + em;
|
|
8452
|
+
if (endMinutes < startMinutes) {
|
|
8453
|
+
endMinutes += 24 * 60;
|
|
8454
|
+
}
|
|
8455
|
+
return endMinutes - startMinutes;
|
|
8456
|
+
};
|
|
8457
|
+
var stripSeconds = (timeStr) => {
|
|
8458
|
+
if (!timeStr) return timeStr;
|
|
8459
|
+
return timeStr.substring(0, 5);
|
|
8460
|
+
};
|
|
8461
|
+
var buildShiftConfigFromOperatingHoursRows = (rows, fallback) => {
|
|
8462
|
+
const mapped = (rows || []).map((row) => ({
|
|
8463
|
+
shiftId: row.shift_id,
|
|
8464
|
+
shiftName: row.shift_name || `Shift ${row.shift_id}`,
|
|
8465
|
+
startTime: stripSeconds(row.start_time),
|
|
8466
|
+
endTime: stripSeconds(row.end_time),
|
|
8467
|
+
breaks: (() => {
|
|
8468
|
+
const raw = Array.isArray(row.breaks) ? row.breaks : Array.isArray(row.breaks?.breaks) ? row.breaks.breaks : [];
|
|
8469
|
+
return raw.map((b) => ({
|
|
8470
|
+
startTime: stripSeconds(b.start || b.startTime || "00:00"),
|
|
8471
|
+
endTime: stripSeconds(b.end || b.endTime || "00:00"),
|
|
8472
|
+
duration: calculateBreakDuration(
|
|
8473
|
+
stripSeconds(b.start || b.startTime || "00:00"),
|
|
8474
|
+
stripSeconds(b.end || b.endTime || "00:00")
|
|
8475
|
+
),
|
|
8476
|
+
remarks: b.remarks || b.name || ""
|
|
8477
|
+
}));
|
|
8478
|
+
})(),
|
|
8479
|
+
timezone: row.timezone || void 0
|
|
8480
|
+
}));
|
|
8481
|
+
const day = mapped.find((s) => s.shiftId === 0);
|
|
8482
|
+
const night = mapped.find((s) => s.shiftId === 1);
|
|
8483
|
+
return {
|
|
8484
|
+
shifts: mapped,
|
|
8485
|
+
timezone: mapped[0]?.timezone || fallback?.timezone,
|
|
8486
|
+
dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallback?.dayShift,
|
|
8487
|
+
nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallback?.nightShift,
|
|
8488
|
+
transitionPeriodMinutes: fallback?.transitionPeriodMinutes || 0
|
|
8489
|
+
};
|
|
8490
|
+
};
|
|
8491
|
+
var shiftConfigStore = {
|
|
8492
|
+
get(lineId) {
|
|
8493
|
+
return shiftConfigsByLineId.get(lineId);
|
|
8494
|
+
},
|
|
8495
|
+
set(lineId, config) {
|
|
8496
|
+
shiftConfigsByLineId.set(lineId, config);
|
|
8497
|
+
listeners.forEach((listener) => listener(lineId));
|
|
8498
|
+
},
|
|
8499
|
+
setFromOperatingHoursRows(lineId, rows, fallback) {
|
|
8500
|
+
const config = buildShiftConfigFromOperatingHoursRows(rows, fallback);
|
|
8501
|
+
this.set(lineId, config);
|
|
8502
|
+
return config;
|
|
8503
|
+
},
|
|
8504
|
+
getMany(lineIds) {
|
|
8505
|
+
const map = /* @__PURE__ */ new Map();
|
|
8506
|
+
lineIds.forEach((lineId) => {
|
|
8507
|
+
const config = shiftConfigsByLineId.get(lineId);
|
|
8508
|
+
if (config) map.set(lineId, config);
|
|
8509
|
+
});
|
|
8510
|
+
return map;
|
|
8511
|
+
},
|
|
8512
|
+
subscribe(listener) {
|
|
8513
|
+
listeners.add(listener);
|
|
8514
|
+
return () => listeners.delete(listener);
|
|
8515
|
+
},
|
|
8516
|
+
clear() {
|
|
8517
|
+
shiftConfigsByLineId.clear();
|
|
8518
|
+
listeners.forEach((listener) => listener("*"));
|
|
8519
|
+
}
|
|
8520
|
+
};
|
|
8521
|
+
var fetchAndStoreShiftConfig = async (supabase, lineId, fallback) => {
|
|
8522
|
+
const existing = inFlightFetchesByLineId.get(lineId);
|
|
8523
|
+
if (existing) return existing;
|
|
8524
|
+
const promise = (async () => {
|
|
8525
|
+
try {
|
|
8526
|
+
const { data, error } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").eq("line_id", lineId);
|
|
8527
|
+
if (error) {
|
|
8528
|
+
throw new Error(`Failed to fetch shift config: ${error.message}`);
|
|
8529
|
+
}
|
|
8530
|
+
if (!data || data.length === 0) {
|
|
8531
|
+
if (fallback) {
|
|
8532
|
+
shiftConfigStore.set(lineId, fallback);
|
|
8533
|
+
return fallback;
|
|
8534
|
+
}
|
|
8535
|
+
return null;
|
|
8536
|
+
}
|
|
8537
|
+
return shiftConfigStore.setFromOperatingHoursRows(lineId, data, fallback);
|
|
8538
|
+
} finally {
|
|
8539
|
+
inFlightFetchesByLineId.delete(lineId);
|
|
8540
|
+
}
|
|
8541
|
+
})();
|
|
8542
|
+
inFlightFetchesByLineId.set(lineId, promise);
|
|
8543
|
+
return promise;
|
|
8544
|
+
};
|
|
8545
|
+
var ensureShiftConfigSubscription = (supabase, lineId, fallback) => {
|
|
8546
|
+
const existing = subscriptionsByLineId.get(lineId);
|
|
8547
|
+
if (existing && existing.supabase === supabase) {
|
|
8548
|
+
existing.refCount += 1;
|
|
8549
|
+
return () => {
|
|
8550
|
+
const current = subscriptionsByLineId.get(lineId);
|
|
8551
|
+
if (!current) return;
|
|
8552
|
+
current.refCount -= 1;
|
|
8553
|
+
if (current.refCount <= 0) {
|
|
8554
|
+
current.channel.unsubscribe();
|
|
8555
|
+
subscriptionsByLineId.delete(lineId);
|
|
8556
|
+
}
|
|
8557
|
+
};
|
|
8558
|
+
}
|
|
8559
|
+
if (existing) {
|
|
8560
|
+
existing.channel.unsubscribe();
|
|
8561
|
+
subscriptionsByLineId.delete(lineId);
|
|
8562
|
+
}
|
|
8563
|
+
const channel = supabase.channel(`shift_config_${lineId}`).on(
|
|
8564
|
+
"postgres_changes",
|
|
8565
|
+
{
|
|
8566
|
+
event: "*",
|
|
8567
|
+
schema: "public",
|
|
8568
|
+
table: "line_operating_hours",
|
|
8569
|
+
filter: `line_id=eq.${lineId}`
|
|
8570
|
+
},
|
|
8571
|
+
() => {
|
|
8572
|
+
fetchAndStoreShiftConfig(supabase, lineId, fallback).catch((err) => {
|
|
8573
|
+
console.error("[shiftConfigStore] Failed to refresh shift config", { lineId, err });
|
|
8574
|
+
});
|
|
8575
|
+
}
|
|
8576
|
+
).subscribe();
|
|
8577
|
+
subscriptionsByLineId.set(lineId, { supabase, channel, refCount: 1 });
|
|
8578
|
+
return () => {
|
|
8579
|
+
const current = subscriptionsByLineId.get(lineId);
|
|
8580
|
+
if (!current) return;
|
|
8581
|
+
current.refCount -= 1;
|
|
8582
|
+
if (current.refCount <= 0) {
|
|
8583
|
+
current.channel.unsubscribe();
|
|
8584
|
+
subscriptionsByLineId.delete(lineId);
|
|
8585
|
+
}
|
|
8586
|
+
};
|
|
8587
|
+
};
|
|
8588
|
+
|
|
8589
|
+
// src/lib/hooks/useLineShiftConfig.ts
|
|
8276
8590
|
var useLineShiftConfig = (lineId, fallbackConfig) => {
|
|
8277
|
-
const [shiftConfig, setShiftConfig] = React24.useState(
|
|
8278
|
-
|
|
8591
|
+
const [shiftConfig, setShiftConfig] = React24.useState(() => {
|
|
8592
|
+
if (!lineId || lineId === "factory" || lineId === "all") return fallbackConfig || null;
|
|
8593
|
+
return shiftConfigStore.get(lineId) || fallbackConfig || null;
|
|
8594
|
+
});
|
|
8595
|
+
const [isLoading, setIsLoading] = React24.useState(() => {
|
|
8596
|
+
if (!lineId || lineId === "factory" || lineId === "all") return false;
|
|
8597
|
+
return !shiftConfigStore.get(lineId);
|
|
8598
|
+
});
|
|
8279
8599
|
const [error, setError] = React24.useState(null);
|
|
8280
8600
|
const supabase = useSupabase();
|
|
8281
8601
|
React24.useEffect(() => {
|
|
8282
8602
|
if (!lineId || lineId === "factory" || lineId === "all") {
|
|
8283
8603
|
setShiftConfig(fallbackConfig || null);
|
|
8284
8604
|
setIsLoading(false);
|
|
8605
|
+
setError(null);
|
|
8285
8606
|
return;
|
|
8286
8607
|
}
|
|
8287
8608
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -8289,98 +8610,49 @@ var useLineShiftConfig = (lineId, fallbackConfig) => {
|
|
|
8289
8610
|
console.warn(`[useShiftConfig] Invalid line ID format: ${lineId}, using fallback`);
|
|
8290
8611
|
setShiftConfig(fallbackConfig || null);
|
|
8291
8612
|
setIsLoading(false);
|
|
8613
|
+
setError(null);
|
|
8292
8614
|
return;
|
|
8293
8615
|
}
|
|
8294
8616
|
let mounted = true;
|
|
8295
|
-
const
|
|
8296
|
-
const
|
|
8297
|
-
|
|
8298
|
-
let startMinutes = sh * 60 + sm;
|
|
8299
|
-
let endMinutes = eh * 60 + em;
|
|
8300
|
-
if (endMinutes < startMinutes) {
|
|
8301
|
-
endMinutes += 24 * 60;
|
|
8302
|
-
}
|
|
8303
|
-
return endMinutes - startMinutes;
|
|
8617
|
+
const syncFromStore = () => {
|
|
8618
|
+
const stored = shiftConfigStore.get(lineId);
|
|
8619
|
+
setShiftConfig(stored || fallbackConfig || null);
|
|
8304
8620
|
};
|
|
8305
|
-
|
|
8621
|
+
syncFromStore();
|
|
8622
|
+
const unsubscribeStore = shiftConfigStore.subscribe((changedLineId) => {
|
|
8623
|
+
if (!mounted) return;
|
|
8624
|
+
if (changedLineId === "*" || changedLineId === lineId) {
|
|
8625
|
+
syncFromStore();
|
|
8626
|
+
}
|
|
8627
|
+
});
|
|
8628
|
+
const unsubscribeRealtime = ensureShiftConfigSubscription(supabase, lineId, fallbackConfig);
|
|
8629
|
+
const ensureLoaded = async () => {
|
|
8306
8630
|
try {
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
const { data: shiftsData, error: shiftsError } = await supabase.from("line_operating_hours").select("shift_id, shift_name, start_time, end_time, breaks, timezone").eq("line_id", lineId);
|
|
8311
|
-
if (shiftsError) {
|
|
8312
|
-
console.error(`[useLineShiftConfig] \u274C Error fetching shifts:`, shiftsError);
|
|
8313
|
-
throw new Error(`Failed to fetch shift config: ${shiftsError.message}`);
|
|
8314
|
-
}
|
|
8315
|
-
if (!shiftsData || shiftsData.length === 0) {
|
|
8316
|
-
console.warn(`[useLineShiftConfig] \u26A0\uFE0F No shift config found for line ${lineId}, using fallback`);
|
|
8317
|
-
if (mounted) {
|
|
8318
|
-
setShiftConfig(fallbackConfig || null);
|
|
8319
|
-
setIsLoading(false);
|
|
8320
|
-
}
|
|
8631
|
+
const existing = shiftConfigStore.get(lineId);
|
|
8632
|
+
if (existing) {
|
|
8633
|
+
if (mounted) setIsLoading(false);
|
|
8321
8634
|
return;
|
|
8322
8635
|
}
|
|
8323
|
-
const stripSeconds = (timeStr) => {
|
|
8324
|
-
if (!timeStr) return timeStr;
|
|
8325
|
-
return timeStr.substring(0, 5);
|
|
8326
|
-
};
|
|
8327
|
-
const mapped = shiftsData.map((shift) => ({
|
|
8328
|
-
shiftId: shift.shift_id,
|
|
8329
|
-
shiftName: shift.shift_name || `Shift ${shift.shift_id}`,
|
|
8330
|
-
startTime: stripSeconds(shift.start_time),
|
|
8331
|
-
endTime: stripSeconds(shift.end_time),
|
|
8332
|
-
breaks: Array.isArray(shift.breaks) ? shift.breaks.map((b) => ({
|
|
8333
|
-
startTime: b.start || b.startTime || "00:00",
|
|
8334
|
-
endTime: b.end || b.endTime || "00:00",
|
|
8335
|
-
duration: calculateBreakDuration2(
|
|
8336
|
-
b.start || b.startTime || "00:00",
|
|
8337
|
-
b.end || b.endTime || "00:00"
|
|
8338
|
-
),
|
|
8339
|
-
remarks: b.remarks || b.name || ""
|
|
8340
|
-
})) : [],
|
|
8341
|
-
timezone: shift.timezone
|
|
8342
|
-
}));
|
|
8343
|
-
const day = mapped.find((s) => s.shiftId === 0);
|
|
8344
|
-
const night = mapped.find((s) => s.shiftId === 1);
|
|
8345
|
-
const config = {
|
|
8346
|
-
shifts: mapped,
|
|
8347
|
-
timezone: mapped[0]?.timezone,
|
|
8348
|
-
dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallbackConfig?.dayShift,
|
|
8349
|
-
nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallbackConfig?.nightShift,
|
|
8350
|
-
transitionPeriodMinutes: fallbackConfig?.transitionPeriodMinutes || 0
|
|
8351
|
-
};
|
|
8352
|
-
console.log(`[useLineShiftConfig] \u2705 Built config from DB:`, config);
|
|
8353
8636
|
if (mounted) {
|
|
8354
|
-
|
|
8355
|
-
|
|
8637
|
+
setIsLoading(true);
|
|
8638
|
+
setError(null);
|
|
8356
8639
|
}
|
|
8640
|
+
await fetchAndStoreShiftConfig(supabase, lineId, fallbackConfig);
|
|
8357
8641
|
} catch (err) {
|
|
8358
8642
|
console.error("[useShiftConfig] Error fetching shift config:", err);
|
|
8359
8643
|
if (mounted) {
|
|
8360
8644
|
setError(err instanceof Error ? err.message : "Unknown error occurred");
|
|
8361
|
-
setShiftConfig(fallbackConfig || null);
|
|
8362
|
-
setIsLoading(false);
|
|
8645
|
+
setShiftConfig(shiftConfigStore.get(lineId) || fallbackConfig || null);
|
|
8363
8646
|
}
|
|
8647
|
+
} finally {
|
|
8648
|
+
if (mounted) setIsLoading(false);
|
|
8364
8649
|
}
|
|
8365
8650
|
};
|
|
8366
|
-
|
|
8367
|
-
const subscription = supabase.channel(`shift_config_${lineId}`).on(
|
|
8368
|
-
"postgres_changes",
|
|
8369
|
-
{
|
|
8370
|
-
event: "*",
|
|
8371
|
-
// Listen to all events (INSERT, UPDATE, DELETE)
|
|
8372
|
-
schema: "public",
|
|
8373
|
-
table: "line_operating_hours",
|
|
8374
|
-
filter: `line_id=eq.${lineId}`
|
|
8375
|
-
},
|
|
8376
|
-
(payload) => {
|
|
8377
|
-
console.log("[useShiftConfig] Real-time update received:", payload);
|
|
8378
|
-
fetchShiftConfig();
|
|
8379
|
-
}
|
|
8380
|
-
).subscribe();
|
|
8651
|
+
ensureLoaded();
|
|
8381
8652
|
return () => {
|
|
8382
8653
|
mounted = false;
|
|
8383
|
-
|
|
8654
|
+
unsubscribeStore();
|
|
8655
|
+
unsubscribeRealtime();
|
|
8384
8656
|
};
|
|
8385
8657
|
}, [lineId, supabase]);
|
|
8386
8658
|
return {
|
|
@@ -8471,8 +8743,8 @@ var useLineWorkspaceMetrics = (lineId, options) => {
|
|
|
8471
8743
|
queryShiftId,
|
|
8472
8744
|
metricsTable
|
|
8473
8745
|
});
|
|
8474
|
-
const
|
|
8475
|
-
const enabledWorkspaceIds =
|
|
8746
|
+
const enabledWorkspaces = await workspaceService.getEnabledWorkspaces(lineId);
|
|
8747
|
+
const enabledWorkspaceIds = enabledWorkspaces.map((ws) => ws.id);
|
|
8476
8748
|
if (enabledWorkspaceIds.length === 0) {
|
|
8477
8749
|
setWorkspaces([]);
|
|
8478
8750
|
setInitialized(true);
|
|
@@ -8570,14 +8842,6 @@ var useHistoricWorkspaceMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
8570
8842
|
isFetchingRef.current = true;
|
|
8571
8843
|
setIsLoading(true);
|
|
8572
8844
|
setError(null);
|
|
8573
|
-
const { data: { session } } = await supabase.auth.getSession();
|
|
8574
|
-
if (!session?.access_token) {
|
|
8575
|
-
throw new Error("No authentication token available");
|
|
8576
|
-
}
|
|
8577
|
-
const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
8578
|
-
if (!apiUrl) {
|
|
8579
|
-
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
8580
|
-
}
|
|
8581
8845
|
const params = new URLSearchParams({
|
|
8582
8846
|
company_id: entityConfig.companyId || "",
|
|
8583
8847
|
date
|
|
@@ -8585,20 +8849,10 @@ var useHistoricWorkspaceMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
8585
8849
|
if (shiftId !== void 0) {
|
|
8586
8850
|
params.append("shift_id", shiftId.toString());
|
|
8587
8851
|
}
|
|
8588
|
-
const
|
|
8589
|
-
|
|
8590
|
-
{
|
|
8591
|
-
headers: {
|
|
8592
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
8593
|
-
"Content-Type": "application/json"
|
|
8594
|
-
}
|
|
8595
|
-
}
|
|
8852
|
+
const data = await fetchBackendJson(
|
|
8853
|
+
supabase,
|
|
8854
|
+
`/api/dashboard/workspace/${workspaceId}/metrics?${params.toString()}`
|
|
8596
8855
|
);
|
|
8597
|
-
if (!response.ok) {
|
|
8598
|
-
const errorText = await response.text();
|
|
8599
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
8600
|
-
}
|
|
8601
|
-
const data = await response.json();
|
|
8602
8856
|
const fetchedMetrics = data.metrics;
|
|
8603
8857
|
if (!fetchedMetrics) {
|
|
8604
8858
|
setMetrics(null);
|
|
@@ -8818,14 +9072,6 @@ var useLeaderboardMetrics = (date, shiftId, limit = 10, filter2 = "all") => {
|
|
|
8818
9072
|
const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
|
|
8819
9073
|
const queryDate = date || currentShift.date;
|
|
8820
9074
|
const queryShiftId = shiftId !== void 0 ? shiftId : currentShift.shiftId;
|
|
8821
|
-
const { data: { session } } = await supabase.auth.getSession();
|
|
8822
|
-
if (!session?.access_token) {
|
|
8823
|
-
throw new Error("No authentication token available");
|
|
8824
|
-
}
|
|
8825
|
-
const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
8826
|
-
if (!apiUrl) {
|
|
8827
|
-
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
8828
|
-
}
|
|
8829
9075
|
const params = new URLSearchParams({
|
|
8830
9076
|
date: queryDate,
|
|
8831
9077
|
shift_id: queryShiftId.toString(),
|
|
@@ -8833,20 +9079,10 @@ var useLeaderboardMetrics = (date, shiftId, limit = 10, filter2 = "all") => {
|
|
|
8833
9079
|
limit: limit.toString(),
|
|
8834
9080
|
filter: filter2
|
|
8835
9081
|
});
|
|
8836
|
-
const
|
|
8837
|
-
|
|
8838
|
-
{
|
|
8839
|
-
headers: {
|
|
8840
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
8841
|
-
"Content-Type": "application/json"
|
|
8842
|
-
}
|
|
8843
|
-
}
|
|
9082
|
+
const data = await fetchBackendJson(
|
|
9083
|
+
supabase,
|
|
9084
|
+
`/api/dashboard/leaderboard?${params.toString()}`
|
|
8844
9085
|
);
|
|
8845
|
-
if (!response.ok) {
|
|
8846
|
-
const errorText = await response.text();
|
|
8847
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
8848
|
-
}
|
|
8849
|
-
const data = await response.json();
|
|
8850
9086
|
setLeaderboard(data.leaderboard || []);
|
|
8851
9087
|
} catch (err) {
|
|
8852
9088
|
console.error("[useLeaderboardMetrics] Error fetching leaderboard:", err);
|
|
@@ -8872,11 +9108,12 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
|
|
|
8872
9108
|
const [isLoading, setIsLoading] = React24.useState(true);
|
|
8873
9109
|
const [error, setError] = React24.useState(null);
|
|
8874
9110
|
const supabase = useSupabase();
|
|
8875
|
-
const lineIdsKey = React24.useMemo(() => lineIds.sort().join(","), [lineIds]);
|
|
9111
|
+
const lineIdsKey = React24.useMemo(() => lineIds.slice().sort().join(","), [lineIds]);
|
|
8876
9112
|
React24.useEffect(() => {
|
|
8877
9113
|
if (!lineIds || lineIds.length === 0) {
|
|
8878
9114
|
setShiftConfigMap(/* @__PURE__ */ new Map());
|
|
8879
9115
|
setIsLoading(false);
|
|
9116
|
+
setError(null);
|
|
8880
9117
|
return;
|
|
8881
9118
|
}
|
|
8882
9119
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -8885,56 +9122,56 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
|
|
|
8885
9122
|
console.warn("[useMultiLineShiftConfigs] No valid line IDs provided");
|
|
8886
9123
|
setShiftConfigMap(/* @__PURE__ */ new Map());
|
|
8887
9124
|
setIsLoading(false);
|
|
9125
|
+
setError(null);
|
|
8888
9126
|
return;
|
|
8889
9127
|
}
|
|
8890
9128
|
let mounted = true;
|
|
8891
|
-
const
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
const buildShiftConfigFromRows = (shifts, fallback) => {
|
|
8906
|
-
const mapped = shifts.map((shift) => ({
|
|
8907
|
-
shiftId: shift.shift_id,
|
|
8908
|
-
shiftName: shift.shift_name || `Shift ${shift.shift_id}`,
|
|
8909
|
-
startTime: stripSeconds(shift.start_time),
|
|
8910
|
-
endTime: stripSeconds(shift.end_time),
|
|
8911
|
-
breaks: Array.isArray(shift.breaks) ? shift.breaks.map((b) => ({
|
|
8912
|
-
startTime: b.start || b.startTime || "00:00",
|
|
8913
|
-
endTime: b.end || b.endTime || "00:00",
|
|
8914
|
-
duration: calculateBreakDuration2(
|
|
8915
|
-
b.start || b.startTime || "00:00",
|
|
8916
|
-
b.end || b.endTime || "00:00"
|
|
8917
|
-
),
|
|
8918
|
-
remarks: b.remarks || b.name || ""
|
|
8919
|
-
})) : [],
|
|
8920
|
-
timezone: shift.timezone
|
|
8921
|
-
}));
|
|
8922
|
-
const day = mapped.find((s) => s.shiftId === 0);
|
|
8923
|
-
const night = mapped.find((s) => s.shiftId === 1);
|
|
8924
|
-
return {
|
|
8925
|
-
shifts: mapped,
|
|
8926
|
-
timezone: mapped[0]?.timezone,
|
|
8927
|
-
dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallback?.dayShift,
|
|
8928
|
-
nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallback?.nightShift,
|
|
8929
|
-
transitionPeriodMinutes: fallback?.transitionPeriodMinutes || 0
|
|
8930
|
-
};
|
|
9129
|
+
const syncFromStore = (changedLineId) => {
|
|
9130
|
+
setShiftConfigMap((prev) => {
|
|
9131
|
+
const next = new Map(prev);
|
|
9132
|
+
const updateLine = (lineId) => {
|
|
9133
|
+
const config = shiftConfigStore.get(lineId) || fallbackConfig;
|
|
9134
|
+
if (config) next.set(lineId, config);
|
|
9135
|
+
};
|
|
9136
|
+
if (!changedLineId || changedLineId === "*") {
|
|
9137
|
+
validLineIds.forEach(updateLine);
|
|
9138
|
+
} else if (validLineIds.includes(changedLineId)) {
|
|
9139
|
+
updateLine(changedLineId);
|
|
9140
|
+
}
|
|
9141
|
+
return next;
|
|
9142
|
+
});
|
|
8931
9143
|
};
|
|
9144
|
+
const initialMap = /* @__PURE__ */ new Map();
|
|
9145
|
+
validLineIds.forEach((lineId) => {
|
|
9146
|
+
const config = shiftConfigStore.get(lineId) || fallbackConfig;
|
|
9147
|
+
if (config) initialMap.set(lineId, config);
|
|
9148
|
+
});
|
|
9149
|
+
setShiftConfigMap(initialMap);
|
|
9150
|
+
const unsubscribeStore = shiftConfigStore.subscribe((changedLineId) => {
|
|
9151
|
+
if (!mounted) return;
|
|
9152
|
+
if (changedLineId === "*" || validLineIds.includes(changedLineId)) {
|
|
9153
|
+
syncFromStore(changedLineId);
|
|
9154
|
+
}
|
|
9155
|
+
});
|
|
9156
|
+
const unsubscribeRealtimeList = validLineIds.map(
|
|
9157
|
+
(lineId) => ensureShiftConfigSubscription(supabase, lineId, fallbackConfig)
|
|
9158
|
+
);
|
|
8932
9159
|
const fetchAllConfigs = async () => {
|
|
8933
9160
|
try {
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
9161
|
+
const missingLineIds = validLineIds.filter((lineId) => !shiftConfigStore.get(lineId));
|
|
9162
|
+
if (missingLineIds.length === 0) {
|
|
9163
|
+
if (mounted) {
|
|
9164
|
+
setIsLoading(false);
|
|
9165
|
+
setError(null);
|
|
9166
|
+
}
|
|
9167
|
+
return;
|
|
9168
|
+
}
|
|
9169
|
+
if (mounted) {
|
|
9170
|
+
setIsLoading(true);
|
|
9171
|
+
setError(null);
|
|
9172
|
+
}
|
|
9173
|
+
console.log(`[useMultiLineShiftConfigs] Fetching shift configs for ${missingLineIds.length} lines`);
|
|
9174
|
+
const { data, error: fetchError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").in("line_id", missingLineIds);
|
|
8938
9175
|
if (fetchError) {
|
|
8939
9176
|
console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
|
|
8940
9177
|
throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
|
|
@@ -8946,33 +9183,23 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
|
|
|
8946
9183
|
}
|
|
8947
9184
|
lineShiftsMap.get(row.line_id).push(row);
|
|
8948
9185
|
});
|
|
8949
|
-
const configMap = /* @__PURE__ */ new Map();
|
|
8950
9186
|
lineShiftsMap.forEach((shifts, lineId) => {
|
|
8951
|
-
|
|
8952
|
-
configMap.set(lineId, config);
|
|
9187
|
+
shiftConfigStore.setFromOperatingHoursRows(lineId, shifts, fallbackConfig);
|
|
8953
9188
|
});
|
|
8954
|
-
|
|
8955
|
-
if (!
|
|
9189
|
+
missingLineIds.forEach((lineId) => {
|
|
9190
|
+
if (!lineShiftsMap.has(lineId) && fallbackConfig) {
|
|
8956
9191
|
console.warn(`[useMultiLineShiftConfigs] No config found for line ${lineId}, using fallback`);
|
|
8957
|
-
|
|
9192
|
+
shiftConfigStore.set(lineId, fallbackConfig);
|
|
8958
9193
|
}
|
|
8959
9194
|
});
|
|
8960
|
-
console.log(`[useMultiLineShiftConfigs]
|
|
9195
|
+
console.log(`[useMultiLineShiftConfigs] Stored configs for ${lineShiftsMap.size} lines`);
|
|
8961
9196
|
if (mounted) {
|
|
8962
|
-
setShiftConfigMap(configMap);
|
|
8963
9197
|
setIsLoading(false);
|
|
8964
9198
|
}
|
|
8965
9199
|
} catch (err) {
|
|
8966
9200
|
console.error("[useMultiLineShiftConfigs] Error:", err);
|
|
8967
9201
|
if (mounted) {
|
|
8968
9202
|
setError(err instanceof Error ? err.message : "Failed to fetch shift configs");
|
|
8969
|
-
if (fallbackConfig) {
|
|
8970
|
-
const fallbackMap = /* @__PURE__ */ new Map();
|
|
8971
|
-
validLineIds.forEach((lineId) => {
|
|
8972
|
-
fallbackMap.set(lineId, fallbackConfig);
|
|
8973
|
-
});
|
|
8974
|
-
setShiftConfigMap(fallbackMap);
|
|
8975
|
-
}
|
|
8976
9203
|
setIsLoading(false);
|
|
8977
9204
|
}
|
|
8978
9205
|
}
|
|
@@ -8980,6 +9207,8 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
|
|
|
8980
9207
|
fetchAllConfigs();
|
|
8981
9208
|
return () => {
|
|
8982
9209
|
mounted = false;
|
|
9210
|
+
unsubscribeStore();
|
|
9211
|
+
unsubscribeRealtimeList.forEach((unsub) => unsub());
|
|
8983
9212
|
};
|
|
8984
9213
|
}, [lineIdsKey, supabase]);
|
|
8985
9214
|
return {
|
|
@@ -9322,8 +9551,10 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
9322
9551
|
const operationalDateForSubscription = currentShiftDetails.date;
|
|
9323
9552
|
const targetLineIds = [currentLineIdToUse];
|
|
9324
9553
|
if (targetLineIds.length === 0) return;
|
|
9325
|
-
const
|
|
9326
|
-
const
|
|
9554
|
+
const baseFilterParts = `date=eq.${operationalDateForSubscription},shift_id=eq.${currentShiftDetails.shiftId}`;
|
|
9555
|
+
const lineIdFilterPart = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
9556
|
+
const wsMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9557
|
+
const lineMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9327
9558
|
const createSubscription = (table, filter2, channelNameBase, callback) => {
|
|
9328
9559
|
const channelName = `${channelNameBase}-${Date.now()}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9329
9560
|
const channel = supabase.channel(channelName).on(
|
|
@@ -9800,12 +10031,12 @@ var useRealtimeLineMetrics = ({
|
|
|
9800
10031
|
const companyId = entityConfig.companyId;
|
|
9801
10032
|
const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
|
|
9802
10033
|
const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
10034
|
+
const enabledWorkspaceLists = await Promise.all(
|
|
10035
|
+
targetLineIds.map((lineId2) => workspaceService.getEnabledWorkspaces(lineId2))
|
|
10036
|
+
);
|
|
10037
|
+
const enabledWorkspaceIds = Array.from(
|
|
10038
|
+
new Set(enabledWorkspaceLists.flatMap((workspaces) => workspaces.map((ws) => ws.id)))
|
|
10039
|
+
);
|
|
9809
10040
|
const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
|
|
9810
10041
|
line_id,
|
|
9811
10042
|
workspace_id,
|
|
@@ -9865,8 +10096,8 @@ var useRealtimeLineMetrics = ({
|
|
|
9865
10096
|
const companyId = entityConfig.companyId;
|
|
9866
10097
|
const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
|
|
9867
10098
|
const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
|
|
9868
|
-
const
|
|
9869
|
-
const enabledWorkspaceIds =
|
|
10099
|
+
const enabledWorkspaces = await workspaceService.getEnabledWorkspaces(lineIdRef.current);
|
|
10100
|
+
const enabledWorkspaceIds = enabledWorkspaces.map((ws) => ws.id);
|
|
9870
10101
|
const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
|
|
9871
10102
|
workspace_id,
|
|
9872
10103
|
workspace_name,
|
|
@@ -9986,20 +10217,23 @@ var useRealtimeLineMetrics = ({
|
|
|
9986
10217
|
const companyId = entityConfig.companyId;
|
|
9987
10218
|
const metricsTablePrefix = getMetricsTablePrefix();
|
|
9988
10219
|
const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
|
|
10220
|
+
const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
|
|
10221
|
+
const baseFilterParts = `date=eq.${currentDate},shift_id=eq.${shiftId}`;
|
|
10222
|
+
const lineIdFilterPart = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
10223
|
+
const filter2 = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9989
10224
|
const lineMetricsChannel = supabase.channel(`line-metrics-${timestamp}`).on(
|
|
9990
10225
|
"postgres_changes",
|
|
9991
10226
|
{
|
|
9992
10227
|
event: "*",
|
|
9993
10228
|
schema: "public",
|
|
9994
10229
|
table: "line_metrics",
|
|
9995
|
-
filter:
|
|
10230
|
+
filter: filter2
|
|
9996
10231
|
},
|
|
9997
10232
|
async (payload) => {
|
|
9998
10233
|
const payloadData = payload.new;
|
|
9999
10234
|
if (process.env.NODE_ENV === "development") {
|
|
10000
10235
|
console.log("Line metrics update received:", payloadData);
|
|
10001
10236
|
}
|
|
10002
|
-
const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
|
|
10003
10237
|
if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
|
|
10004
10238
|
queueUpdate();
|
|
10005
10239
|
}
|
|
@@ -10015,14 +10249,13 @@ var useRealtimeLineMetrics = ({
|
|
|
10015
10249
|
event: "*",
|
|
10016
10250
|
schema: "public",
|
|
10017
10251
|
table: metricsTable,
|
|
10018
|
-
filter:
|
|
10252
|
+
filter: filter2
|
|
10019
10253
|
},
|
|
10020
10254
|
async (payload) => {
|
|
10021
10255
|
const payloadData = payload.new;
|
|
10022
10256
|
if (process.env.NODE_ENV === "development") {
|
|
10023
10257
|
console.log(`${metricsTablePrefix} update received:`, payloadData);
|
|
10024
10258
|
}
|
|
10025
|
-
const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
|
|
10026
10259
|
if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
|
|
10027
10260
|
queueUpdate();
|
|
10028
10261
|
}
|
|
@@ -10033,7 +10266,7 @@ var useRealtimeLineMetrics = ({
|
|
|
10033
10266
|
}
|
|
10034
10267
|
});
|
|
10035
10268
|
channelsRef.current = [lineMetricsChannel, metricsChannel];
|
|
10036
|
-
}, [supabase, queueUpdate, urlDate, shiftId, entityConfig, dateTimeConfig.defaultTimezone]);
|
|
10269
|
+
}, [supabase, queueUpdate, urlDate, shiftId, entityConfig, timezone, dateTimeConfig.defaultTimezone]);
|
|
10037
10270
|
const prevShiftIdRef = React24.useRef(void 0);
|
|
10038
10271
|
React24.useEffect(() => {
|
|
10039
10272
|
if (!lineId) return;
|
|
@@ -10812,34 +11045,16 @@ var useFactoryOverviewMetrics = (date, shiftId) => {
|
|
|
10812
11045
|
if (lineIds.length === 0) {
|
|
10813
11046
|
throw new Error("No lines configured in entityConfig");
|
|
10814
11047
|
}
|
|
10815
|
-
const { data: { session } } = await supabase.auth.getSession();
|
|
10816
|
-
if (!session?.access_token) {
|
|
10817
|
-
throw new Error("No authentication token available");
|
|
10818
|
-
}
|
|
10819
|
-
const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
10820
|
-
if (!apiUrl) {
|
|
10821
|
-
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
10822
|
-
}
|
|
10823
11048
|
const params = new URLSearchParams({
|
|
10824
11049
|
line_ids: lineIds.join(","),
|
|
10825
11050
|
date: queryDate,
|
|
10826
11051
|
shift_id: queryShiftId.toString(),
|
|
10827
11052
|
company_id: entityConfig.companyId || ""
|
|
10828
11053
|
});
|
|
10829
|
-
const
|
|
10830
|
-
|
|
10831
|
-
{
|
|
10832
|
-
headers: {
|
|
10833
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
10834
|
-
"Content-Type": "application/json"
|
|
10835
|
-
}
|
|
10836
|
-
}
|
|
11054
|
+
const data = await fetchBackendJson(
|
|
11055
|
+
supabase,
|
|
11056
|
+
`/api/dashboard/factory-overview?${params.toString()}`
|
|
10837
11057
|
);
|
|
10838
|
-
if (!response.ok) {
|
|
10839
|
-
const errorText = await response.text();
|
|
10840
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
10841
|
-
}
|
|
10842
|
-
const data = await response.json();
|
|
10843
11058
|
setMetrics(data);
|
|
10844
11059
|
} catch (err) {
|
|
10845
11060
|
console.error("[useFactoryOverviewMetrics] Error fetching factory overview:", err);
|
|
@@ -10868,6 +11083,46 @@ var isInitialized = false;
|
|
|
10868
11083
|
var isInitializing = false;
|
|
10869
11084
|
var initializedWithLineIds = [];
|
|
10870
11085
|
var initializationPromise = null;
|
|
11086
|
+
var workspaceDisplayNamesListeners = /* @__PURE__ */ new Set();
|
|
11087
|
+
var notifyWorkspaceDisplayNamesListeners = (changedLineId) => {
|
|
11088
|
+
workspaceDisplayNamesListeners.forEach((listener) => {
|
|
11089
|
+
try {
|
|
11090
|
+
listener(changedLineId);
|
|
11091
|
+
} catch (err) {
|
|
11092
|
+
console.error("[workspaceDisplayNames] Listener error", err);
|
|
11093
|
+
}
|
|
11094
|
+
});
|
|
11095
|
+
};
|
|
11096
|
+
var subscribeWorkspaceDisplayNames = (listener) => {
|
|
11097
|
+
workspaceDisplayNamesListeners.add(listener);
|
|
11098
|
+
return () => workspaceDisplayNamesListeners.delete(listener);
|
|
11099
|
+
};
|
|
11100
|
+
var getAllWorkspaceDisplayNamesSnapshot = (lineId) => {
|
|
11101
|
+
if (lineId && runtimeWorkspaceDisplayNames[lineId]) {
|
|
11102
|
+
return { ...runtimeWorkspaceDisplayNames[lineId] };
|
|
11103
|
+
}
|
|
11104
|
+
if (lineId) return {};
|
|
11105
|
+
const allNames = {};
|
|
11106
|
+
Object.entries(runtimeWorkspaceDisplayNames).forEach(([cachedLineId, lineNames]) => {
|
|
11107
|
+
Object.entries(lineNames).forEach(([workspaceId, displayName]) => {
|
|
11108
|
+
allNames[`${cachedLineId}_${workspaceId}`] = displayName;
|
|
11109
|
+
});
|
|
11110
|
+
});
|
|
11111
|
+
return allNames;
|
|
11112
|
+
};
|
|
11113
|
+
var upsertWorkspaceDisplayNameInCache = (params) => {
|
|
11114
|
+
const { lineId, workspaceId, displayName, enabled } = params;
|
|
11115
|
+
if (!lineId || !workspaceId) return;
|
|
11116
|
+
if (!runtimeWorkspaceDisplayNames[lineId]) {
|
|
11117
|
+
runtimeWorkspaceDisplayNames[lineId] = {};
|
|
11118
|
+
}
|
|
11119
|
+
if (enabled === false || !displayName) {
|
|
11120
|
+
delete runtimeWorkspaceDisplayNames[lineId][workspaceId];
|
|
11121
|
+
} else {
|
|
11122
|
+
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
11123
|
+
}
|
|
11124
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
11125
|
+
};
|
|
10871
11126
|
function getCurrentLineIds() {
|
|
10872
11127
|
try {
|
|
10873
11128
|
const config = _getDashboardConfigInstance();
|
|
@@ -10907,15 +11162,20 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
|
|
|
10907
11162
|
console.log("\u{1F504} Target line IDs for workspace filtering:", targetLineIds);
|
|
10908
11163
|
runtimeWorkspaceDisplayNames = {};
|
|
10909
11164
|
if (targetLineIds.length > 0) {
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
11165
|
+
const results = await Promise.all(
|
|
11166
|
+
targetLineIds.map(async (lineId) => {
|
|
11167
|
+
console.log(`\u{1F504} Fetching workspaces for line: ${lineId}`);
|
|
11168
|
+
const lineDisplayNamesMap = await workspaceService.getWorkspaceDisplayNames(void 0, lineId);
|
|
11169
|
+
return { lineId, lineDisplayNamesMap };
|
|
11170
|
+
})
|
|
11171
|
+
);
|
|
11172
|
+
results.forEach(({ lineId, lineDisplayNamesMap }) => {
|
|
10913
11173
|
runtimeWorkspaceDisplayNames[lineId] = {};
|
|
10914
11174
|
lineDisplayNamesMap.forEach((displayName, workspaceId) => {
|
|
10915
11175
|
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
10916
11176
|
});
|
|
10917
11177
|
console.log(`\u2705 Stored ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
|
|
10918
|
-
}
|
|
11178
|
+
});
|
|
10919
11179
|
} else {
|
|
10920
11180
|
console.warn("\u26A0\uFE0F No line IDs found, fetching all workspaces (less efficient)");
|
|
10921
11181
|
const allWorkspacesMap = await workspaceService.getWorkspaceDisplayNames();
|
|
@@ -10928,6 +11188,7 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
|
|
|
10928
11188
|
initializedWithLineIds = targetLineIds;
|
|
10929
11189
|
console.log("\u2705 Workspace display names initialized from Supabase:", runtimeWorkspaceDisplayNames);
|
|
10930
11190
|
console.log("\u2705 Initialized with line IDs:", initializedWithLineIds);
|
|
11191
|
+
notifyWorkspaceDisplayNamesListeners(explicitLineId);
|
|
10931
11192
|
} catch (error) {
|
|
10932
11193
|
console.error("\u274C Failed to initialize workspace display names from Supabase:", error);
|
|
10933
11194
|
} finally {
|
|
@@ -10950,6 +11211,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
|
|
|
10950
11211
|
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
10951
11212
|
});
|
|
10952
11213
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
|
|
11214
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
10953
11215
|
} catch (error) {
|
|
10954
11216
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
10955
11217
|
}
|
|
@@ -10968,6 +11230,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
|
|
|
10968
11230
|
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
10969
11231
|
});
|
|
10970
11232
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
|
|
11233
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
10971
11234
|
} catch (error) {
|
|
10972
11235
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
10973
11236
|
}
|
|
@@ -11007,6 +11270,7 @@ var getWorkspaceDisplayName = (workspaceId, lineId) => {
|
|
|
11007
11270
|
runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
|
|
11008
11271
|
});
|
|
11009
11272
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
|
|
11273
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
11010
11274
|
}).catch((error) => {
|
|
11011
11275
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
11012
11276
|
});
|
|
@@ -11047,6 +11311,7 @@ var getShortWorkspaceDisplayName = (workspaceId, lineId) => {
|
|
|
11047
11311
|
runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
|
|
11048
11312
|
});
|
|
11049
11313
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
|
|
11314
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
11050
11315
|
}).catch((error) => {
|
|
11051
11316
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
11052
11317
|
});
|
|
@@ -11088,6 +11353,9 @@ var getAllWorkspaceDisplayNamesAsync = async (companyId, lineId) => {
|
|
|
11088
11353
|
while (isInitializing) {
|
|
11089
11354
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
11090
11355
|
}
|
|
11356
|
+
if (lineId && isInitialized && !runtimeWorkspaceDisplayNames[lineId]) {
|
|
11357
|
+
await preInitializeWorkspaceDisplayNames(lineId);
|
|
11358
|
+
}
|
|
11091
11359
|
if (lineId && runtimeWorkspaceDisplayNames[lineId]) {
|
|
11092
11360
|
return { ...runtimeWorkspaceDisplayNames[lineId] };
|
|
11093
11361
|
}
|
|
@@ -11120,6 +11388,7 @@ var refreshWorkspaceDisplayNames = async (companyId) => {
|
|
|
11120
11388
|
runtimeWorkspaceDisplayNames = {};
|
|
11121
11389
|
isInitialized = false;
|
|
11122
11390
|
await initializeWorkspaceDisplayNames();
|
|
11391
|
+
notifyWorkspaceDisplayNamesListeners();
|
|
11123
11392
|
};
|
|
11124
11393
|
var clearWorkspaceDisplayNamesCache = () => {
|
|
11125
11394
|
workspaceService.clearWorkspaceDisplayNamesCache();
|
|
@@ -11128,6 +11397,7 @@ var clearWorkspaceDisplayNamesCache = () => {
|
|
|
11128
11397
|
isInitializing = false;
|
|
11129
11398
|
initializedWithLineIds = [];
|
|
11130
11399
|
initializationPromise = null;
|
|
11400
|
+
notifyWorkspaceDisplayNamesListeners();
|
|
11131
11401
|
};
|
|
11132
11402
|
|
|
11133
11403
|
// src/lib/hooks/useWorkspaceDisplayNames.ts
|
|
@@ -11150,6 +11420,23 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
|
|
|
11150
11420
|
React24.useEffect(() => {
|
|
11151
11421
|
fetchDisplayNames();
|
|
11152
11422
|
}, [fetchDisplayNames]);
|
|
11423
|
+
React24.useEffect(() => {
|
|
11424
|
+
const snapshot = getAllWorkspaceDisplayNamesSnapshot(lineId);
|
|
11425
|
+
if (Object.keys(snapshot).length > 0) {
|
|
11426
|
+
setDisplayNames(snapshot);
|
|
11427
|
+
setLoading(false);
|
|
11428
|
+
}
|
|
11429
|
+
const unsubscribe = subscribeWorkspaceDisplayNames((changedLineId) => {
|
|
11430
|
+
if (!lineId) {
|
|
11431
|
+
setDisplayNames(getAllWorkspaceDisplayNamesSnapshot());
|
|
11432
|
+
return;
|
|
11433
|
+
}
|
|
11434
|
+
if (!changedLineId || changedLineId === "*" || changedLineId === lineId) {
|
|
11435
|
+
setDisplayNames(getAllWorkspaceDisplayNamesSnapshot(lineId));
|
|
11436
|
+
}
|
|
11437
|
+
});
|
|
11438
|
+
return unsubscribe;
|
|
11439
|
+
}, [lineId]);
|
|
11153
11440
|
return {
|
|
11154
11441
|
displayNames,
|
|
11155
11442
|
loading,
|
|
@@ -11158,7 +11445,7 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
|
|
|
11158
11445
|
};
|
|
11159
11446
|
};
|
|
11160
11447
|
var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
|
|
11161
|
-
const [displayName, setDisplayName] = React24.useState(workspaceId);
|
|
11448
|
+
const [displayName, setDisplayName] = React24.useState(() => getWorkspaceDisplayName(workspaceId, lineId));
|
|
11162
11449
|
const [loading, setLoading] = React24.useState(true);
|
|
11163
11450
|
const [error, setError] = React24.useState(null);
|
|
11164
11451
|
const fetchDisplayName = React24.useCallback(async () => {
|
|
@@ -11177,6 +11464,18 @@ var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
|
|
|
11177
11464
|
React24.useEffect(() => {
|
|
11178
11465
|
fetchDisplayName();
|
|
11179
11466
|
}, [fetchDisplayName]);
|
|
11467
|
+
React24.useEffect(() => {
|
|
11468
|
+
const unsubscribe = subscribeWorkspaceDisplayNames((changedLineId) => {
|
|
11469
|
+
if (!lineId) {
|
|
11470
|
+
setDisplayName(getWorkspaceDisplayName(workspaceId));
|
|
11471
|
+
return;
|
|
11472
|
+
}
|
|
11473
|
+
if (!changedLineId || changedLineId === "*" || changedLineId === lineId) {
|
|
11474
|
+
setDisplayName(getWorkspaceDisplayName(workspaceId, lineId));
|
|
11475
|
+
}
|
|
11476
|
+
});
|
|
11477
|
+
return unsubscribe;
|
|
11478
|
+
}, [workspaceId, lineId]);
|
|
11180
11479
|
return {
|
|
11181
11480
|
displayName,
|
|
11182
11481
|
loading,
|
|
@@ -11207,6 +11506,18 @@ var useWorkspaceDisplayNamesMap = (workspaceIds, lineId, companyId) => {
|
|
|
11207
11506
|
React24.useEffect(() => {
|
|
11208
11507
|
fetchDisplayNames();
|
|
11209
11508
|
}, [fetchDisplayNames]);
|
|
11509
|
+
React24.useEffect(() => {
|
|
11510
|
+
const unsubscribe = subscribeWorkspaceDisplayNames((changedLineId) => {
|
|
11511
|
+
if (lineId && changedLineId && changedLineId !== "*" && changedLineId !== lineId) return;
|
|
11512
|
+
const snapshot = getAllWorkspaceDisplayNamesSnapshot(lineId);
|
|
11513
|
+
const next = {};
|
|
11514
|
+
workspaceIds.forEach((id3) => {
|
|
11515
|
+
if (snapshot[id3]) next[id3] = snapshot[id3];
|
|
11516
|
+
});
|
|
11517
|
+
setDisplayNames(next);
|
|
11518
|
+
});
|
|
11519
|
+
return unsubscribe;
|
|
11520
|
+
}, [workspaceIds, lineId]);
|
|
11210
11521
|
return {
|
|
11211
11522
|
displayNames,
|
|
11212
11523
|
loading,
|
|
@@ -11453,13 +11764,19 @@ var useAllWorkspaceMetrics = (options) => {
|
|
|
11453
11764
|
setError(null);
|
|
11454
11765
|
try {
|
|
11455
11766
|
const lineWorkspaceMap = /* @__PURE__ */ new Map();
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
11459
|
-
|
|
11767
|
+
const allEnabledWorkspaceIdSet = /* @__PURE__ */ new Set();
|
|
11768
|
+
const perLineEnabledIds = await Promise.all(
|
|
11769
|
+
configuredLineIds.map(async (lineId) => {
|
|
11770
|
+
const enabledWorkspaces = await workspaceService.getEnabledWorkspaces(lineId);
|
|
11771
|
+
const enabledIds = enabledWorkspaces.map((ws) => ws.id);
|
|
11772
|
+
return { lineId, enabledIds };
|
|
11773
|
+
})
|
|
11774
|
+
);
|
|
11775
|
+
perLineEnabledIds.forEach(({ lineId, enabledIds }) => {
|
|
11460
11776
|
lineWorkspaceMap.set(lineId, enabledIds);
|
|
11461
|
-
|
|
11462
|
-
}
|
|
11777
|
+
enabledIds.forEach((id3) => allEnabledWorkspaceIdSet.add(id3));
|
|
11778
|
+
});
|
|
11779
|
+
const allEnabledWorkspaceIds = Array.from(allEnabledWorkspaceIdSet);
|
|
11463
11780
|
if (allEnabledWorkspaceIds.length === 0) {
|
|
11464
11781
|
setWorkspaces([]);
|
|
11465
11782
|
setInitialized(true);
|
|
@@ -11580,43 +11897,61 @@ var useAllWorkspaceMetrics = (options) => {
|
|
|
11580
11897
|
});
|
|
11581
11898
|
}
|
|
11582
11899
|
const setupSubscription = () => {
|
|
11583
|
-
|
|
11584
|
-
|
|
11900
|
+
if (!metricsTable || configuredLineIds.length === 0) return [];
|
|
11901
|
+
const groupsToSubscribe = hasSpecificDateShift || shiftGroups.length === 0 ? [
|
|
11585
11902
|
{
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
},
|
|
11590
|
-
async (payload) => {
|
|
11591
|
-
const data = payload.new || payload.old;
|
|
11592
|
-
const dataKey = `${data?.date}-${data?.shift_id}`;
|
|
11593
|
-
if (!validDateShiftCombos.has(dataKey)) {
|
|
11594
|
-
return;
|
|
11595
|
-
}
|
|
11596
|
-
if (fetchTimeoutRef.current) {
|
|
11597
|
-
clearTimeout(fetchTimeoutRef.current);
|
|
11598
|
-
}
|
|
11599
|
-
fetchTimeoutRef.current = setTimeout(async () => {
|
|
11600
|
-
if (!isFetchingRef.current) {
|
|
11601
|
-
await fetchWorkspaceMetrics();
|
|
11602
|
-
}
|
|
11603
|
-
fetchTimeoutRef.current = null;
|
|
11604
|
-
}, 300);
|
|
11903
|
+
date: fallbackQueryDate,
|
|
11904
|
+
shiftId: fallbackQueryShiftId,
|
|
11905
|
+
lineIds: configuredLineIds
|
|
11605
11906
|
}
|
|
11606
|
-
|
|
11607
|
-
|
|
11907
|
+
] : shiftGroups.map((group) => ({
|
|
11908
|
+
date: group.date,
|
|
11909
|
+
shiftId: group.shiftId,
|
|
11910
|
+
lineIds: group.lineIds
|
|
11911
|
+
}));
|
|
11912
|
+
return groupsToSubscribe.map((group, index) => {
|
|
11913
|
+
const lineIdFilterPart = `line_id=in.(${group.lineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
11914
|
+
const filter2 = `date=eq.${group.date},shift_id=eq.${group.shiftId},${lineIdFilterPart}`;
|
|
11915
|
+
const channelName = `all-workspace-metrics-g${index}-${group.date}-${group.shiftId}-${Date.now()}`.replace(
|
|
11916
|
+
/[^a-zA-Z0-9_-]/g,
|
|
11917
|
+
""
|
|
11918
|
+
);
|
|
11919
|
+
return supabase.channel(channelName).on(
|
|
11920
|
+
"postgres_changes",
|
|
11921
|
+
{
|
|
11922
|
+
event: "*",
|
|
11923
|
+
schema,
|
|
11924
|
+
table: metricsTable,
|
|
11925
|
+
filter: filter2
|
|
11926
|
+
},
|
|
11927
|
+
async (payload) => {
|
|
11928
|
+
const data = payload.new || payload.old;
|
|
11929
|
+
const dataKey = `${data?.date}-${data?.shift_id}`;
|
|
11930
|
+
if (!validDateShiftCombos.has(dataKey)) {
|
|
11931
|
+
return;
|
|
11932
|
+
}
|
|
11933
|
+
if (fetchTimeoutRef.current) {
|
|
11934
|
+
clearTimeout(fetchTimeoutRef.current);
|
|
11935
|
+
}
|
|
11936
|
+
fetchTimeoutRef.current = setTimeout(async () => {
|
|
11937
|
+
if (!isFetchingRef.current) {
|
|
11938
|
+
await fetchWorkspaceMetrics();
|
|
11939
|
+
}
|
|
11940
|
+
fetchTimeoutRef.current = null;
|
|
11941
|
+
}, 300);
|
|
11942
|
+
}
|
|
11943
|
+
).subscribe();
|
|
11944
|
+
});
|
|
11608
11945
|
};
|
|
11609
|
-
const
|
|
11946
|
+
const channels = setupSubscription();
|
|
11610
11947
|
return () => {
|
|
11611
11948
|
if (fetchTimeoutRef.current) {
|
|
11612
11949
|
clearTimeout(fetchTimeoutRef.current);
|
|
11613
11950
|
fetchTimeoutRef.current = null;
|
|
11614
11951
|
}
|
|
11615
|
-
|
|
11616
|
-
supabase.removeChannel(channel);
|
|
11617
|
-
}
|
|
11952
|
+
channels.forEach((channel) => supabase.removeChannel(channel));
|
|
11618
11953
|
};
|
|
11619
|
-
}, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId]);
|
|
11954
|
+
}, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId, configuredLineIds]);
|
|
11620
11955
|
React24.useEffect(() => {
|
|
11621
11956
|
setInitialized(false);
|
|
11622
11957
|
}, [fallbackQueryDate, fallbackQueryShiftId, shiftGroups]);
|
|
@@ -13054,6 +13389,85 @@ function useLineSupervisor(lineId) {
|
|
|
13054
13389
|
error
|
|
13055
13390
|
};
|
|
13056
13391
|
}
|
|
13392
|
+
var useSupervisorsByLineIds = (lineIds, options) => {
|
|
13393
|
+
const supabase = useSupabase();
|
|
13394
|
+
const enabled = options?.enabled ?? true;
|
|
13395
|
+
const lineIdsKey = React24.useMemo(() => (lineIds || []).slice().sort().join(","), [lineIds]);
|
|
13396
|
+
const targetLineIdSet = React24.useMemo(() => new Set(lineIds || []), [lineIdsKey]);
|
|
13397
|
+
const [supervisorsByLineId, setSupervisorsByLineId] = React24.useState(/* @__PURE__ */ new Map());
|
|
13398
|
+
const [supervisorNamesByLineId, setSupervisorNamesByLineId] = React24.useState(/* @__PURE__ */ new Map());
|
|
13399
|
+
const [isLoading, setIsLoading] = React24.useState(true);
|
|
13400
|
+
const [error, setError] = React24.useState(null);
|
|
13401
|
+
const fetchSupervisors = React24.useCallback(async () => {
|
|
13402
|
+
if (!enabled || !supabase) {
|
|
13403
|
+
setIsLoading(false);
|
|
13404
|
+
return;
|
|
13405
|
+
}
|
|
13406
|
+
try {
|
|
13407
|
+
setIsLoading(true);
|
|
13408
|
+
setError(null);
|
|
13409
|
+
const { data, error: fetchError } = await supabase.from("user_roles").select("user_id, email, properties").eq("role_level", "supervisor");
|
|
13410
|
+
if (fetchError) {
|
|
13411
|
+
throw fetchError;
|
|
13412
|
+
}
|
|
13413
|
+
const nextSupervisorsByLineId = /* @__PURE__ */ new Map();
|
|
13414
|
+
(data || []).forEach((row) => {
|
|
13415
|
+
const assignedLineIds = row?.properties?.line_id || row?.properties?.line_ids || [];
|
|
13416
|
+
if (!Array.isArray(assignedLineIds) || assignedLineIds.length === 0) return;
|
|
13417
|
+
const email = row.email || "";
|
|
13418
|
+
const displayName = (typeof email === "string" && email.includes("@") ? email.split("@")[0] : email) || email;
|
|
13419
|
+
const supervisor = {
|
|
13420
|
+
userId: row.user_id,
|
|
13421
|
+
email,
|
|
13422
|
+
displayName
|
|
13423
|
+
};
|
|
13424
|
+
assignedLineIds.forEach((lineId) => {
|
|
13425
|
+
if (typeof lineId !== "string") return;
|
|
13426
|
+
if (targetLineIdSet.size > 0 && !targetLineIdSet.has(lineId)) return;
|
|
13427
|
+
const existing = nextSupervisorsByLineId.get(lineId) || [];
|
|
13428
|
+
existing.push(supervisor);
|
|
13429
|
+
nextSupervisorsByLineId.set(lineId, existing);
|
|
13430
|
+
});
|
|
13431
|
+
});
|
|
13432
|
+
const nextSupervisorNamesByLineId = /* @__PURE__ */ new Map();
|
|
13433
|
+
nextSupervisorsByLineId.forEach((supervisors, lineId) => {
|
|
13434
|
+
nextSupervisorNamesByLineId.set(lineId, supervisors.map((s) => s.displayName).join(", "));
|
|
13435
|
+
});
|
|
13436
|
+
setSupervisorsByLineId(nextSupervisorsByLineId);
|
|
13437
|
+
setSupervisorNamesByLineId(nextSupervisorNamesByLineId);
|
|
13438
|
+
} catch (err) {
|
|
13439
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch supervisors"));
|
|
13440
|
+
setSupervisorsByLineId(/* @__PURE__ */ new Map());
|
|
13441
|
+
setSupervisorNamesByLineId(/* @__PURE__ */ new Map());
|
|
13442
|
+
} finally {
|
|
13443
|
+
setIsLoading(false);
|
|
13444
|
+
}
|
|
13445
|
+
}, [enabled, supabase, targetLineIdSet]);
|
|
13446
|
+
React24.useEffect(() => {
|
|
13447
|
+
fetchSupervisors();
|
|
13448
|
+
}, [fetchSupervisors, lineIdsKey]);
|
|
13449
|
+
React24.useEffect(() => {
|
|
13450
|
+
if (!enabled || !supabase) return;
|
|
13451
|
+
let channel = null;
|
|
13452
|
+
channel = supabase.channel("supervisors-by-line").on(
|
|
13453
|
+
"postgres_changes",
|
|
13454
|
+
{ event: "*", schema: "public", table: "user_roles", filter: "role_level=eq.supervisor" },
|
|
13455
|
+
() => {
|
|
13456
|
+
fetchSupervisors();
|
|
13457
|
+
}
|
|
13458
|
+
).subscribe();
|
|
13459
|
+
return () => {
|
|
13460
|
+
if (channel) supabase.removeChannel(channel);
|
|
13461
|
+
};
|
|
13462
|
+
}, [enabled, supabase, fetchSupervisors]);
|
|
13463
|
+
return {
|
|
13464
|
+
supervisorNamesByLineId,
|
|
13465
|
+
supervisorsByLineId,
|
|
13466
|
+
isLoading,
|
|
13467
|
+
error,
|
|
13468
|
+
refetch: fetchSupervisors
|
|
13469
|
+
};
|
|
13470
|
+
};
|
|
13057
13471
|
function useIdleTimeReasons({
|
|
13058
13472
|
workspaceId,
|
|
13059
13473
|
lineId,
|
|
@@ -13459,6 +13873,102 @@ var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig, li
|
|
|
13459
13873
|
return fullName;
|
|
13460
13874
|
};
|
|
13461
13875
|
|
|
13876
|
+
// src/lib/utils/kpis.ts
|
|
13877
|
+
var toNumber = (value) => {
|
|
13878
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
13879
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
13880
|
+
const parsed = Number(value);
|
|
13881
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
13882
|
+
}
|
|
13883
|
+
return 0;
|
|
13884
|
+
};
|
|
13885
|
+
var createDefaultKPIs = () => ({
|
|
13886
|
+
underperformingWorkers: { current: 0, total: 0, change: 0 },
|
|
13887
|
+
efficiency: { value: 0, change: 0 },
|
|
13888
|
+
outputProgress: { current: 0, target: 0, idealOutput: 0, change: 0 },
|
|
13889
|
+
avgCycleTime: { value: 0, change: 0 },
|
|
13890
|
+
qualityCompliance: { value: 95, change: 0 }
|
|
13891
|
+
});
|
|
13892
|
+
var buildKPIsFromLineMetricsRow = (row) => {
|
|
13893
|
+
if (!row) return createDefaultKPIs();
|
|
13894
|
+
const avgEfficiency = toNumber(row.avg_efficiency);
|
|
13895
|
+
const avgCycleTime = toNumber(row.avg_cycle_time);
|
|
13896
|
+
const currentOutput = toNumber(row.current_output);
|
|
13897
|
+
const lineThreshold = toNumber(row.line_threshold);
|
|
13898
|
+
const idealOutput = toNumber(row.ideal_output) || lineThreshold || 0;
|
|
13899
|
+
const underperformingWorkspaces = toNumber(row.underperforming_workspaces);
|
|
13900
|
+
const totalWorkspaces = toNumber(row.total_workspaces);
|
|
13901
|
+
return {
|
|
13902
|
+
underperformingWorkers: {
|
|
13903
|
+
current: underperformingWorkspaces,
|
|
13904
|
+
total: totalWorkspaces,
|
|
13905
|
+
change: 0
|
|
13906
|
+
},
|
|
13907
|
+
efficiency: {
|
|
13908
|
+
value: avgEfficiency,
|
|
13909
|
+
change: 0
|
|
13910
|
+
},
|
|
13911
|
+
outputProgress: {
|
|
13912
|
+
current: currentOutput,
|
|
13913
|
+
target: lineThreshold,
|
|
13914
|
+
idealOutput,
|
|
13915
|
+
change: 0
|
|
13916
|
+
},
|
|
13917
|
+
avgCycleTime: {
|
|
13918
|
+
value: avgCycleTime,
|
|
13919
|
+
change: 0
|
|
13920
|
+
},
|
|
13921
|
+
qualityCompliance: {
|
|
13922
|
+
value: 95,
|
|
13923
|
+
change: 0
|
|
13924
|
+
}
|
|
13925
|
+
};
|
|
13926
|
+
};
|
|
13927
|
+
var aggregateKPIsFromLineMetricsRows = (rows) => {
|
|
13928
|
+
if (!rows || rows.length === 0) return createDefaultKPIs();
|
|
13929
|
+
const currentOutputSum = rows.reduce((sum, row) => sum + toNumber(row.current_output), 0);
|
|
13930
|
+
const lineThresholdSum = rows.reduce((sum, row) => sum + toNumber(row.line_threshold), 0);
|
|
13931
|
+
const idealOutputSum = rows.reduce(
|
|
13932
|
+
(sum, row) => sum + (toNumber(row.ideal_output) || toNumber(row.line_threshold)),
|
|
13933
|
+
0
|
|
13934
|
+
);
|
|
13935
|
+
const totalWorkspacesAll = rows.reduce((sum, row) => sum + toNumber(row.total_workspaces), 0);
|
|
13936
|
+
const weightedEfficiencySum = rows.reduce(
|
|
13937
|
+
(sum, row) => sum + toNumber(row.avg_efficiency) * toNumber(row.total_workspaces),
|
|
13938
|
+
0
|
|
13939
|
+
);
|
|
13940
|
+
const avgEfficiency = totalWorkspacesAll > 0 ? weightedEfficiencySum / totalWorkspacesAll : 0;
|
|
13941
|
+
const numLines = rows.length;
|
|
13942
|
+
const avgCycleTime = numLines > 0 ? rows.reduce((sum, row) => sum + toNumber(row.avg_cycle_time), 0) / numLines : 0;
|
|
13943
|
+
const totalUnderperforming = rows.reduce((sum, row) => sum + toNumber(row.underperforming_workspaces), 0);
|
|
13944
|
+
const totalWorkspaces = rows.reduce((sum, row) => sum + toNumber(row.total_workspaces), 0);
|
|
13945
|
+
return {
|
|
13946
|
+
underperformingWorkers: {
|
|
13947
|
+
current: totalUnderperforming,
|
|
13948
|
+
total: totalWorkspaces,
|
|
13949
|
+
change: 0
|
|
13950
|
+
},
|
|
13951
|
+
efficiency: {
|
|
13952
|
+
value: avgEfficiency,
|
|
13953
|
+
change: 0
|
|
13954
|
+
},
|
|
13955
|
+
outputProgress: {
|
|
13956
|
+
current: currentOutputSum,
|
|
13957
|
+
target: lineThresholdSum,
|
|
13958
|
+
idealOutput: idealOutputSum,
|
|
13959
|
+
change: 0
|
|
13960
|
+
},
|
|
13961
|
+
avgCycleTime: {
|
|
13962
|
+
value: avgCycleTime,
|
|
13963
|
+
change: 0
|
|
13964
|
+
},
|
|
13965
|
+
qualityCompliance: {
|
|
13966
|
+
value: 95,
|
|
13967
|
+
change: 0
|
|
13968
|
+
}
|
|
13969
|
+
};
|
|
13970
|
+
};
|
|
13971
|
+
|
|
13462
13972
|
// ../../node_modules/clsx/dist/clsx.mjs
|
|
13463
13973
|
function r(e) {
|
|
13464
13974
|
var t, f, n = "";
|
|
@@ -28495,6 +29005,7 @@ if (typeof document !== "undefined") {
|
|
|
28495
29005
|
}
|
|
28496
29006
|
}
|
|
28497
29007
|
var BASE_HLS_CONFIG = {
|
|
29008
|
+
// Keep buffer small to reduce wasted downloads on slow links
|
|
28498
29009
|
maxBufferLength: 3,
|
|
28499
29010
|
maxMaxBufferLength: 8,
|
|
28500
29011
|
maxBufferSize: 50 * 1e3 * 1e3,
|
|
@@ -28502,10 +29013,10 @@ var BASE_HLS_CONFIG = {
|
|
|
28502
29013
|
manifestLoadingTimeOut: 15e3,
|
|
28503
29014
|
manifestLoadingMaxRetry: 3,
|
|
28504
29015
|
manifestLoadingRetryDelay: 500,
|
|
28505
|
-
levelLoadingTimeOut:
|
|
29016
|
+
levelLoadingTimeOut: 2e4,
|
|
28506
29017
|
levelLoadingMaxRetry: 5,
|
|
28507
29018
|
levelLoadingRetryDelay: 500,
|
|
28508
|
-
fragLoadingTimeOut:
|
|
29019
|
+
fragLoadingTimeOut: 2e4,
|
|
28509
29020
|
fragLoadingMaxRetry: 5,
|
|
28510
29021
|
fragLoadingRetryDelay: 500,
|
|
28511
29022
|
startPosition: -1,
|
|
@@ -28518,7 +29029,10 @@ var BASE_HLS_CONFIG = {
|
|
|
28518
29029
|
abrBandWidthFactor: 0.95,
|
|
28519
29030
|
abrBandWidthUpFactor: 0.7,
|
|
28520
29031
|
abrMaxWithRealBitrate: false,
|
|
28521
|
-
|
|
29032
|
+
// Favor a conservative first rendition on constrained networks
|
|
29033
|
+
abrEwmaDefaultEstimate: 1e6,
|
|
29034
|
+
startLevel: 0,
|
|
29035
|
+
capLevelToPlayerSize: true
|
|
28522
29036
|
};
|
|
28523
29037
|
var HlsVideoPlayer = React24.forwardRef(({
|
|
28524
29038
|
src,
|
|
@@ -29602,7 +30116,7 @@ var getSupabaseClient2 = () => {
|
|
|
29602
30116
|
}
|
|
29603
30117
|
return supabaseJs.createClient(url, key);
|
|
29604
30118
|
};
|
|
29605
|
-
var
|
|
30119
|
+
var getAuthToken4 = async () => {
|
|
29606
30120
|
try {
|
|
29607
30121
|
const supabase = getSupabaseClient2();
|
|
29608
30122
|
const { data: { session } } = await supabase.auth.getSession();
|
|
@@ -29625,7 +30139,7 @@ function useWorkspaceCrop(workspaceId) {
|
|
|
29625
30139
|
setIsLoading(true);
|
|
29626
30140
|
setError(null);
|
|
29627
30141
|
try {
|
|
29628
|
-
const token = await
|
|
30142
|
+
const token = await getAuthToken4();
|
|
29629
30143
|
if (!token) {
|
|
29630
30144
|
throw new Error("Authentication required");
|
|
29631
30145
|
}
|
|
@@ -30674,7 +31188,7 @@ var FileManagerFilters = ({
|
|
|
30674
31188
|
const ROOT_CAUSE_OPTIONS = [
|
|
30675
31189
|
"Operator Absent",
|
|
30676
31190
|
"Operator Idle",
|
|
30677
|
-
"Machine
|
|
31191
|
+
"Machine Downtime",
|
|
30678
31192
|
"No Material"
|
|
30679
31193
|
];
|
|
30680
31194
|
const getIdleTimeRootCause = React24.useCallback((clipId) => {
|
|
@@ -30714,7 +31228,7 @@ var FileManagerFilters = ({
|
|
|
30714
31228
|
method: "POST",
|
|
30715
31229
|
headers: {
|
|
30716
31230
|
"Content-Type": "application/json",
|
|
30717
|
-
"Authorization": `Bearer ${await
|
|
31231
|
+
"Authorization": `Bearer ${await getAuthToken5()}`
|
|
30718
31232
|
},
|
|
30719
31233
|
body: JSON.stringify({
|
|
30720
31234
|
action: "clip-metadata",
|
|
@@ -30747,7 +31261,7 @@ var FileManagerFilters = ({
|
|
|
30747
31261
|
});
|
|
30748
31262
|
}
|
|
30749
31263
|
}, [workspaceId, date, shift]);
|
|
30750
|
-
const
|
|
31264
|
+
const getAuthToken5 = async () => {
|
|
30751
31265
|
try {
|
|
30752
31266
|
const { createClient: createClient5 } = await import('@supabase/supabase-js');
|
|
30753
31267
|
const supabase = createClient5(
|
|
@@ -30773,7 +31287,7 @@ var FileManagerFilters = ({
|
|
|
30773
31287
|
method: "POST",
|
|
30774
31288
|
headers: {
|
|
30775
31289
|
"Content-Type": "application/json",
|
|
30776
|
-
"Authorization": `Bearer ${await
|
|
31290
|
+
"Authorization": `Bearer ${await getAuthToken5()}`
|
|
30777
31291
|
},
|
|
30778
31292
|
body: JSON.stringify({
|
|
30779
31293
|
action: "percentile-clips",
|
|
@@ -32417,7 +32931,7 @@ var BottlenecksContent = ({
|
|
|
32417
32931
|
fetchClipCounts();
|
|
32418
32932
|
}
|
|
32419
32933
|
}, [workspaceId, effectiveDateString, effectiveShiftId, s3ClipsService, fetchClipCounts, isEffectiveShiftReady]);
|
|
32420
|
-
const
|
|
32934
|
+
const getAuthToken5 = React24.useCallback(async () => {
|
|
32421
32935
|
try {
|
|
32422
32936
|
const { createClient: createClient5 } = await import('@supabase/supabase-js');
|
|
32423
32937
|
const supabase = createClient5(
|
|
@@ -32436,7 +32950,7 @@ var BottlenecksContent = ({
|
|
|
32436
32950
|
const fetchTriageClips = async () => {
|
|
32437
32951
|
setIsLoadingTriageClips(true);
|
|
32438
32952
|
try {
|
|
32439
|
-
const token = await
|
|
32953
|
+
const token = await getAuthToken5();
|
|
32440
32954
|
if (!token) {
|
|
32441
32955
|
console.error("[BottlenecksContent] No auth token available");
|
|
32442
32956
|
return;
|
|
@@ -32484,7 +32998,7 @@ var BottlenecksContent = ({
|
|
|
32484
32998
|
}
|
|
32485
32999
|
};
|
|
32486
33000
|
fetchTriageClips();
|
|
32487
|
-
}, [triageMode, workspaceId, effectiveDateString, effectiveShiftId,
|
|
33001
|
+
}, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken5, isEffectiveShiftReady]);
|
|
32488
33002
|
React24.useEffect(() => {
|
|
32489
33003
|
if (!triageMode || triageClips.length === 0 || !session?.access_token) {
|
|
32490
33004
|
return;
|
|
@@ -32815,7 +33329,20 @@ var BottlenecksContent = ({
|
|
|
32815
33329
|
updateActiveFilter(categoryId);
|
|
32816
33330
|
}
|
|
32817
33331
|
try {
|
|
32818
|
-
|
|
33332
|
+
const metadataPromise = loadCategoryMetadata(categoryId, false).catch((err) => {
|
|
33333
|
+
console.warn(`[BottlenecksContent] Metadata fetch error (non-blocking):`, err);
|
|
33334
|
+
return null;
|
|
33335
|
+
});
|
|
33336
|
+
const video = await s3ClipsService.getClipById(clipId);
|
|
33337
|
+
if (video) {
|
|
33338
|
+
setPendingVideo(video);
|
|
33339
|
+
setCurrentClipId(clipId);
|
|
33340
|
+
setAllVideos([video]);
|
|
33341
|
+
setCurrentIndex(0);
|
|
33342
|
+
} else {
|
|
33343
|
+
throw new Error(`Failed to load video data for clip ${clipId}`);
|
|
33344
|
+
}
|
|
33345
|
+
await metadataPromise;
|
|
32819
33346
|
let metadataArray = categoryMetadataRef.current;
|
|
32820
33347
|
const clipExistsInMetadata = metadataArray.some((clip) => clip.clipId === clipId);
|
|
32821
33348
|
if (metadataArray.length === 0 || !clipExistsInMetadata) {
|
|
@@ -32832,16 +33359,7 @@ var BottlenecksContent = ({
|
|
|
32832
33359
|
}
|
|
32833
33360
|
setCurrentMetadataIndex(clickedClipIndex);
|
|
32834
33361
|
currentMetadataIndexRef.current = clickedClipIndex;
|
|
32835
|
-
|
|
32836
|
-
if (video) {
|
|
32837
|
-
setPendingVideo(video);
|
|
32838
|
-
setCurrentClipId(clipId);
|
|
32839
|
-
setAllVideos([video]);
|
|
32840
|
-
setCurrentIndex(0);
|
|
32841
|
-
console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
|
|
32842
|
-
} else {
|
|
32843
|
-
throw new Error(`Failed to load video data for clip ${clipId}`);
|
|
32844
|
-
}
|
|
33362
|
+
console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
|
|
32845
33363
|
} catch (error2) {
|
|
32846
33364
|
console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
|
|
32847
33365
|
if (isMountedRef.current) {
|
|
@@ -35728,7 +36246,7 @@ var STATIC_COLORS = {
|
|
|
35728
36246
|
// red-600 - Critical/Urgent
|
|
35729
36247
|
"No Material": "#f59e0b",
|
|
35730
36248
|
// amber-500 - Warning/Supply Chain
|
|
35731
|
-
"Machine
|
|
36249
|
+
"Machine Downtime": "#3b82f6",
|
|
35732
36250
|
// blue-500 - Scheduled/Technical
|
|
35733
36251
|
"Operator Idle": "#8b5cf6"
|
|
35734
36252
|
// violet-500 - Low Priority/Behavioral
|
|
@@ -38113,7 +38631,7 @@ var WorkspaceWhatsAppShareButton = ({
|
|
|
38113
38631
|
}
|
|
38114
38632
|
);
|
|
38115
38633
|
};
|
|
38116
|
-
var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
38634
|
+
var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
|
|
38117
38635
|
const [isGenerating, setIsGenerating] = React24.useState(false);
|
|
38118
38636
|
const entityConfig = useEntityConfig();
|
|
38119
38637
|
const generatePDF = async () => {
|
|
@@ -38188,8 +38706,10 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
38188
38706
|
doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
|
|
38189
38707
|
};
|
|
38190
38708
|
const perfOverviewStartY = 93;
|
|
38709
|
+
const hasIdleTimeReason = idleTimeReasons && idleTimeReasons.length > 0;
|
|
38710
|
+
const perfOverviewHeight = hasIdleTimeReason ? 80 : 70;
|
|
38191
38711
|
doc.setFillColor(245, 245, 245);
|
|
38192
|
-
doc.roundedRect(15, perfOverviewStartY, 180,
|
|
38712
|
+
doc.roundedRect(15, perfOverviewStartY, 180, perfOverviewHeight, 3, 3, "F");
|
|
38193
38713
|
doc.setFontSize(18);
|
|
38194
38714
|
doc.setFont("helvetica", "bold");
|
|
38195
38715
|
doc.setTextColor(40, 40, 40);
|
|
@@ -38213,32 +38733,68 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
38213
38733
|
doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 2);
|
|
38214
38734
|
doc.setFont("helvetica", "bold");
|
|
38215
38735
|
doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing * 2);
|
|
38736
|
+
createKPIBox(kpiStartY + kpiSpacing * 3);
|
|
38737
|
+
doc.setFont("helvetica", "normal");
|
|
38738
|
+
doc.text("Total Idle Time:", 25, kpiStartY + kpiSpacing * 3);
|
|
38739
|
+
doc.setFont("helvetica", "bold");
|
|
38740
|
+
const idleTimeFormatted = formatIdleTime(workspace.idle_time);
|
|
38741
|
+
doc.text(idleTimeFormatted, 120, kpiStartY + kpiSpacing * 3);
|
|
38742
|
+
if (hasIdleTimeReason) {
|
|
38743
|
+
createKPIBox(kpiStartY + kpiSpacing * 4);
|
|
38744
|
+
doc.setFont("helvetica", "normal");
|
|
38745
|
+
doc.text("Top Idle Reason:", 25, kpiStartY + kpiSpacing * 4);
|
|
38746
|
+
doc.setFont("helvetica", "bold");
|
|
38747
|
+
const topReason = idleTimeReasons[0];
|
|
38748
|
+
const reasonName = topReason.name.replace(/_/g, " ");
|
|
38749
|
+
const reasonText = `${reasonName} (${topReason.value.toFixed(1)}%)`;
|
|
38750
|
+
doc.text(reasonText, 120, kpiStartY + kpiSpacing * 4);
|
|
38751
|
+
}
|
|
38752
|
+
const separatorBeforeHourlyY = hasIdleTimeReason ? 183 : 173;
|
|
38216
38753
|
doc.setDrawColor(180, 180, 180);
|
|
38217
38754
|
doc.setLineWidth(0.8);
|
|
38218
|
-
doc.line(20,
|
|
38219
|
-
const hourlyPerfStartY =
|
|
38755
|
+
doc.line(20, separatorBeforeHourlyY, 190, separatorBeforeHourlyY);
|
|
38756
|
+
const hourlyPerfStartY = hasIdleTimeReason ? 188 : 178;
|
|
38220
38757
|
const hourlyData = workspace.hourly_action_counts || [];
|
|
38221
38758
|
const hourlyTarget = workspace.pph_threshold;
|
|
38222
|
-
const
|
|
38223
|
-
const
|
|
38759
|
+
const pageHeight = doc.internal.pageSize.height;
|
|
38760
|
+
const maxContentY = pageHeight - 15;
|
|
38761
|
+
const baseTableStartY = hasIdleTimeReason ? 219 : 209;
|
|
38762
|
+
let tableStartY = baseTableStartY;
|
|
38763
|
+
let rowHeight = 8;
|
|
38764
|
+
let headerFontSize = 11;
|
|
38765
|
+
let contentFontSize = 11;
|
|
38766
|
+
let titleFontSize = 18;
|
|
38224
38767
|
const bottomPadding = 8;
|
|
38768
|
+
const estimatedEndY = tableStartY + hourlyData.length * rowHeight + bottomPadding;
|
|
38769
|
+
if (estimatedEndY > maxContentY) {
|
|
38770
|
+
rowHeight = 6.5;
|
|
38771
|
+
headerFontSize = 9;
|
|
38772
|
+
contentFontSize = 9;
|
|
38773
|
+
titleFontSize = 16;
|
|
38774
|
+
tableStartY = hasIdleTimeReason ? 215 : 205;
|
|
38775
|
+
}
|
|
38225
38776
|
const backgroundHeight = tableStartY - hourlyPerfStartY + hourlyData.length * rowHeight + bottomPadding;
|
|
38226
38777
|
doc.setFillColor(245, 245, 245);
|
|
38227
38778
|
doc.roundedRect(15, hourlyPerfStartY, 180, backgroundHeight, 3, 3, "F");
|
|
38228
|
-
doc.setFontSize(
|
|
38779
|
+
doc.setFontSize(titleFontSize);
|
|
38229
38780
|
doc.setFont("helvetica", "bold");
|
|
38230
38781
|
doc.setTextColor(40, 40, 40);
|
|
38231
|
-
|
|
38782
|
+
const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
|
|
38783
|
+
doc.text("Hourly Performance", 20, hourlyTitleY);
|
|
38232
38784
|
doc.setTextColor(0, 0, 0);
|
|
38233
|
-
doc.setFontSize(
|
|
38785
|
+
doc.setFontSize(headerFontSize);
|
|
38234
38786
|
doc.setFont("helvetica", "bold");
|
|
38235
|
-
|
|
38236
|
-
|
|
38237
|
-
doc.text("
|
|
38238
|
-
doc.text("
|
|
38787
|
+
const baseHeaderY = hasIdleTimeReason ? 208 : 198;
|
|
38788
|
+
const headerY = titleFontSize === 16 ? hasIdleTimeReason ? 206 : 196 : baseHeaderY;
|
|
38789
|
+
doc.text("Time Range", 25, headerY);
|
|
38790
|
+
doc.text("Output", 95, headerY);
|
|
38791
|
+
doc.text("Target", 135, headerY);
|
|
38792
|
+
doc.text("Status", 170, headerY);
|
|
38239
38793
|
doc.setLineWidth(0.3);
|
|
38240
38794
|
doc.setDrawColor(200, 200, 200);
|
|
38241
|
-
|
|
38795
|
+
const separatorY = headerY + 3;
|
|
38796
|
+
doc.line(20, separatorY, 190, separatorY);
|
|
38797
|
+
doc.setFontSize(contentFontSize);
|
|
38242
38798
|
doc.setFont("helvetica", "normal");
|
|
38243
38799
|
let yPos = tableStartY;
|
|
38244
38800
|
const workspaceDate = new Date(workspace.date);
|
|
@@ -45690,23 +46246,6 @@ function HomeView({
|
|
|
45690
46246
|
loading: displayNamesLoading,
|
|
45691
46247
|
error: displayNamesError
|
|
45692
46248
|
} = useWorkspaceDisplayNames(displayNameLineId, void 0);
|
|
45693
|
-
React24.useCallback(() => {
|
|
45694
|
-
console.log("Refetching KPIs after line metrics update");
|
|
45695
|
-
}, []);
|
|
45696
|
-
const {
|
|
45697
|
-
kpis,
|
|
45698
|
-
isLoading: kpisLoading,
|
|
45699
|
-
error: kpisError,
|
|
45700
|
-
refetch: refetchKPIs
|
|
45701
|
-
} = useLineKPIs({
|
|
45702
|
-
lineId: selectedLineId
|
|
45703
|
-
});
|
|
45704
|
-
const onLineMetricsUpdate = React24.useCallback(() => {
|
|
45705
|
-
const timer = setTimeout(() => {
|
|
45706
|
-
refetchKPIs();
|
|
45707
|
-
}, 1e3);
|
|
45708
|
-
return () => clearTimeout(timer);
|
|
45709
|
-
}, [refetchKPIs]);
|
|
45710
46249
|
const {
|
|
45711
46250
|
workspaceMetrics,
|
|
45712
46251
|
lineMetrics,
|
|
@@ -45715,10 +46254,22 @@ function HomeView({
|
|
|
45715
46254
|
refetch: refetchMetrics
|
|
45716
46255
|
} = useDashboardMetrics({
|
|
45717
46256
|
lineId: selectedLineId,
|
|
45718
|
-
onLineMetricsUpdate,
|
|
45719
46257
|
userAccessibleLineIds: allLineIds
|
|
45720
46258
|
// Pass user's accessible lines for supervisor filtering
|
|
45721
46259
|
});
|
|
46260
|
+
const kpis = React24.useMemo(() => {
|
|
46261
|
+
const lineMetricsRows = lineMetrics || [];
|
|
46262
|
+
if (selectedLineId === factoryViewId) {
|
|
46263
|
+
if (metricsLoading && lineMetricsRows.length === 0) return null;
|
|
46264
|
+
return aggregateKPIsFromLineMetricsRows(lineMetricsRows);
|
|
46265
|
+
}
|
|
46266
|
+
const row = lineMetricsRows.find((r2) => r2?.line_id === selectedLineId);
|
|
46267
|
+
if (!row) {
|
|
46268
|
+
if (metricsLoading) return null;
|
|
46269
|
+
return buildKPIsFromLineMetricsRow(null);
|
|
46270
|
+
}
|
|
46271
|
+
return buildKPIsFromLineMetricsRow(row);
|
|
46272
|
+
}, [selectedLineId, factoryViewId, lineMetrics, metricsLoading]);
|
|
45722
46273
|
const {
|
|
45723
46274
|
activeBreaks: allActiveBreaks,
|
|
45724
46275
|
isLoading: breaksLoading,
|
|
@@ -46037,12 +46588,10 @@ function HomeView({
|
|
|
46037
46588
|
React24.useEffect(() => {
|
|
46038
46589
|
if (metricsError) {
|
|
46039
46590
|
setErrorMessage(metricsError.message);
|
|
46040
|
-
} else if (kpisError) {
|
|
46041
|
-
setErrorMessage(kpisError.message);
|
|
46042
46591
|
} else {
|
|
46043
46592
|
setErrorMessage(null);
|
|
46044
46593
|
}
|
|
46045
|
-
}, [metricsError
|
|
46594
|
+
}, [metricsError]);
|
|
46046
46595
|
const handleLineChange = React24.useCallback((value) => {
|
|
46047
46596
|
setIsChangingFilter(true);
|
|
46048
46597
|
setSelectedLineId(value);
|
|
@@ -46058,14 +46607,14 @@ function HomeView({
|
|
|
46058
46607
|
}
|
|
46059
46608
|
}, [LINE_FILTER_STORAGE_KEY]);
|
|
46060
46609
|
React24.useEffect(() => {
|
|
46061
|
-
if (!metricsLoading &&
|
|
46610
|
+
if (!metricsLoading && isChangingFilter) {
|
|
46062
46611
|
if (workspaceMetrics.length > 0 || selectedLineId === factoryViewId) {
|
|
46063
46612
|
setIsChangingFilter(false);
|
|
46064
46613
|
}
|
|
46065
46614
|
}
|
|
46066
|
-
}, [metricsLoading,
|
|
46615
|
+
}, [metricsLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
|
|
46067
46616
|
React24.useEffect(() => {
|
|
46068
|
-
if (!metricsLoading && !
|
|
46617
|
+
if (!metricsLoading && !hasInitialDataLoaded) {
|
|
46069
46618
|
setHasInitialDataLoaded(true);
|
|
46070
46619
|
trackCoreEvent("Home View Loaded", {
|
|
46071
46620
|
default_line_id: defaultLineId,
|
|
@@ -46073,7 +46622,7 @@ function HomeView({
|
|
|
46073
46622
|
is_supervisor: isSupervisor
|
|
46074
46623
|
});
|
|
46075
46624
|
}
|
|
46076
|
-
}, [metricsLoading,
|
|
46625
|
+
}, [metricsLoading, hasInitialDataLoaded, defaultLineId, factoryViewId, isSupervisor]);
|
|
46077
46626
|
const lineTitle = React24.useMemo(() => {
|
|
46078
46627
|
return factoryName;
|
|
46079
46628
|
}, [factoryName]);
|
|
@@ -46087,7 +46636,7 @@ function HomeView({
|
|
|
46087
46636
|
] });
|
|
46088
46637
|
}, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
|
|
46089
46638
|
const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
|
|
46090
|
-
const isDataLoading = metricsLoading
|
|
46639
|
+
const isDataLoading = metricsLoading;
|
|
46091
46640
|
if (isInitialLoading) {
|
|
46092
46641
|
return /* @__PURE__ */ jsxRuntime.jsx(LoadingPageCmp, { message: "Loading Dashboard..." });
|
|
46093
46642
|
}
|
|
@@ -46231,7 +46780,7 @@ function withWorkspaceDisplayNames(Component3, options = {}) {
|
|
|
46231
46780
|
const lineIdsKey = React24.useMemo(() => {
|
|
46232
46781
|
if (!props.lineIds) return "";
|
|
46233
46782
|
if (Array.isArray(props.lineIds)) {
|
|
46234
|
-
return props.lineIds.sort().join(",");
|
|
46783
|
+
return props.lineIds.slice().sort().join(",");
|
|
46235
46784
|
}
|
|
46236
46785
|
const values = Object.values(props.lineIds).filter(Boolean);
|
|
46237
46786
|
return values.sort().join(",");
|
|
@@ -47354,9 +47903,15 @@ var KPIDetailView = ({
|
|
|
47354
47903
|
};
|
|
47355
47904
|
var KPIDetailViewWithDisplayNames = withSelectedLineDisplayNames(KPIDetailView);
|
|
47356
47905
|
var KPIDetailView_default = KPIDetailViewWithDisplayNames;
|
|
47357
|
-
var LineCard = ({
|
|
47358
|
-
|
|
47359
|
-
|
|
47906
|
+
var LineCard = ({
|
|
47907
|
+
line,
|
|
47908
|
+
kpis,
|
|
47909
|
+
isLoading,
|
|
47910
|
+
error,
|
|
47911
|
+
onClick,
|
|
47912
|
+
supervisorEnabled = false,
|
|
47913
|
+
supervisorName
|
|
47914
|
+
}) => {
|
|
47360
47915
|
const isOnTrack = React24__namespace.default.useMemo(() => {
|
|
47361
47916
|
if (!kpis) return null;
|
|
47362
47917
|
return kpis.efficiency.value > 90;
|
|
@@ -47465,6 +48020,7 @@ var KPIsOverviewView = ({
|
|
|
47465
48020
|
const [error, setError] = React24.useState(null);
|
|
47466
48021
|
const supabase = useSupabase();
|
|
47467
48022
|
const dashboardConfig = useDashboardConfig();
|
|
48023
|
+
const entityConfig = useEntityConfig();
|
|
47468
48024
|
const navigation = useNavigation(navigate);
|
|
47469
48025
|
const dateTimeConfig = useDateTimeConfig();
|
|
47470
48026
|
const representativeLineId = lineIds?.[0] || lines[0]?.id;
|
|
@@ -47472,6 +48028,27 @@ var KPIsOverviewView = ({
|
|
|
47472
48028
|
const supervisorEnabled = dashboardConfig?.supervisorConfig?.enabled || false;
|
|
47473
48029
|
const dbTimezone = useAppTimezone();
|
|
47474
48030
|
const configuredTimezone = dbTimezone || dateTimeConfig.defaultTimezone || "UTC";
|
|
48031
|
+
const factoryViewId = entityConfig.factoryViewId || "factory";
|
|
48032
|
+
const {
|
|
48033
|
+
lineMetrics,
|
|
48034
|
+
isLoading: metricsLoading,
|
|
48035
|
+
error: metricsError
|
|
48036
|
+
} = useDashboardMetrics({
|
|
48037
|
+
lineId: factoryViewId,
|
|
48038
|
+
userAccessibleLineIds: lineIds
|
|
48039
|
+
});
|
|
48040
|
+
const defaultKPIs = React24__namespace.default.useMemo(() => createDefaultKPIs(), []);
|
|
48041
|
+
const kpisByLineId = React24__namespace.default.useMemo(() => {
|
|
48042
|
+
const map = /* @__PURE__ */ new Map();
|
|
48043
|
+
lineMetrics.forEach((row) => {
|
|
48044
|
+
if (row?.line_id) map.set(row.line_id, buildKPIsFromLineMetricsRow(row));
|
|
48045
|
+
});
|
|
48046
|
+
return map;
|
|
48047
|
+
}, [lineMetrics]);
|
|
48048
|
+
const visibleLineIds = React24__namespace.default.useMemo(() => lines.map((l) => l.id), [lines]);
|
|
48049
|
+
const { supervisorNamesByLineId } = useSupervisorsByLineIds(visibleLineIds, {
|
|
48050
|
+
enabled: supervisorEnabled && visibleLineIds.length > 0
|
|
48051
|
+
});
|
|
47475
48052
|
React24.useEffect(() => {
|
|
47476
48053
|
trackCorePageView("KPIs Overview");
|
|
47477
48054
|
}, []);
|
|
@@ -47689,8 +48266,12 @@ var KPIsOverviewView = ({
|
|
|
47689
48266
|
LineCard,
|
|
47690
48267
|
{
|
|
47691
48268
|
line,
|
|
48269
|
+
kpis: metricsError ? null : kpisByLineId.get(line.id) ?? (metricsLoading ? null : defaultKPIs),
|
|
48270
|
+
isLoading: metricsLoading,
|
|
48271
|
+
error: metricsError,
|
|
47692
48272
|
onClick: (kpis) => handleLineClick(line, kpis),
|
|
47693
|
-
supervisorEnabled
|
|
48273
|
+
supervisorEnabled,
|
|
48274
|
+
supervisorName: supervisorNamesByLineId.get(line.id) || null
|
|
47694
48275
|
},
|
|
47695
48276
|
line.id
|
|
47696
48277
|
)) }) })
|
|
@@ -48514,7 +49095,7 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
|
|
|
48514
49095
|
const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
|
|
48515
49096
|
return Number(hoursDiff.toFixed(1));
|
|
48516
49097
|
};
|
|
48517
|
-
var
|
|
49098
|
+
var calculateBreakDuration2 = (startTime, endTime) => {
|
|
48518
49099
|
const [startHour, startMinute] = startTime.split(":").map(Number);
|
|
48519
49100
|
const [endHour, endMinute] = endTime.split(":").map(Number);
|
|
48520
49101
|
let startMinutes = startHour * 60 + startMinute;
|
|
@@ -48530,7 +49111,7 @@ var parseBreaksFromDB = (dbBreaks) => {
|
|
|
48530
49111
|
return dbBreaks.map((breakItem) => ({
|
|
48531
49112
|
startTime: breakItem.start || breakItem.startTime || "00:00",
|
|
48532
49113
|
endTime: breakItem.end || breakItem.endTime || "00:00",
|
|
48533
|
-
duration:
|
|
49114
|
+
duration: calculateBreakDuration2(
|
|
48534
49115
|
breakItem.start || breakItem.startTime || "00:00",
|
|
48535
49116
|
breakItem.end || breakItem.endTime || "00:00"
|
|
48536
49117
|
),
|
|
@@ -48540,7 +49121,7 @@ var parseBreaksFromDB = (dbBreaks) => {
|
|
|
48540
49121
|
return dbBreaks.breaks.map((breakItem) => ({
|
|
48541
49122
|
startTime: breakItem.start || breakItem.startTime || "00:00",
|
|
48542
49123
|
endTime: breakItem.end || breakItem.endTime || "00:00",
|
|
48543
|
-
duration:
|
|
49124
|
+
duration: calculateBreakDuration2(
|
|
48544
49125
|
breakItem.start || breakItem.startTime || "00:00",
|
|
48545
49126
|
breakItem.end || breakItem.endTime || "00:00"
|
|
48546
49127
|
),
|
|
@@ -49177,6 +49758,13 @@ var ShiftsView = ({
|
|
|
49177
49758
|
if (nightResult.error) {
|
|
49178
49759
|
throw new Error(`Failed to save night shift: ${nightResult.error.message}`);
|
|
49179
49760
|
}
|
|
49761
|
+
const updatedRows = [
|
|
49762
|
+
...dayResult.data || [],
|
|
49763
|
+
...nightResult.data || []
|
|
49764
|
+
];
|
|
49765
|
+
if (updatedRows.length > 0) {
|
|
49766
|
+
shiftConfigStore.setFromOperatingHoursRows(lineId, updatedRows, shiftConfigStore.get(lineId));
|
|
49767
|
+
}
|
|
49180
49768
|
setLineConfigs((prev) => prev.map(
|
|
49181
49769
|
(config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
|
|
49182
49770
|
));
|
|
@@ -50146,6 +50734,7 @@ var TargetsViewUI = ({
|
|
|
50146
50734
|
onUpdateSelectedSKU,
|
|
50147
50735
|
skuRequired = false
|
|
50148
50736
|
}) => {
|
|
50737
|
+
const { displayNames: workspaceDisplayNames } = useWorkspaceDisplayNames();
|
|
50149
50738
|
if (isLoading) {
|
|
50150
50739
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading targets..." }) }) });
|
|
50151
50740
|
}
|
|
@@ -50293,7 +50882,7 @@ var TargetsViewUI = ({
|
|
|
50293
50882
|
] })
|
|
50294
50883
|
] }) }),
|
|
50295
50884
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-gray-100", children: line.workspaces.map((workspace) => {
|
|
50296
|
-
const formattedName = formatWorkspaceName(workspace.name, lineId);
|
|
50885
|
+
const formattedName = workspaceDisplayNames[`${lineId}_${workspace.name}`] || formatWorkspaceName(workspace.name, lineId);
|
|
50297
50886
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
50298
50887
|
"div",
|
|
50299
50888
|
{
|
|
@@ -51017,8 +51606,17 @@ var TargetsView = ({
|
|
|
51017
51606
|
};
|
|
51018
51607
|
const handleUpdateWorkspaceDisplayName = React24.useCallback(async (workspaceId, displayName) => {
|
|
51019
51608
|
try {
|
|
51020
|
-
await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
51021
|
-
|
|
51609
|
+
const updated = await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
51610
|
+
if (updated?.line_id && updated?.workspace_id) {
|
|
51611
|
+
upsertWorkspaceDisplayNameInCache({
|
|
51612
|
+
lineId: updated.line_id,
|
|
51613
|
+
workspaceId: updated.workspace_id,
|
|
51614
|
+
displayName: updated?.display_name || displayName,
|
|
51615
|
+
enabled: updated?.enable
|
|
51616
|
+
});
|
|
51617
|
+
} else {
|
|
51618
|
+
await forceRefreshWorkspaceDisplayNames();
|
|
51619
|
+
}
|
|
51022
51620
|
sonner.toast.success("Workspace name updated successfully");
|
|
51023
51621
|
} catch (error) {
|
|
51024
51622
|
console.error("Error updating workspace display name:", error);
|
|
@@ -51697,7 +52295,13 @@ var WorkspaceDetailView = ({
|
|
|
51697
52295
|
}
|
|
51698
52296
|
)
|
|
51699
52297
|
] }),
|
|
51700
|
-
activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
52298
|
+
activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
52299
|
+
WorkspacePdfGenerator,
|
|
52300
|
+
{
|
|
52301
|
+
workspace,
|
|
52302
|
+
idleTimeReasons: idleTimeChartData
|
|
52303
|
+
}
|
|
52304
|
+
) }),
|
|
51701
52305
|
activeTab === "monthly_history" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
51702
52306
|
WorkspaceMonthlyPdfGenerator,
|
|
51703
52307
|
{
|
|
@@ -52752,6 +53356,7 @@ var WorkspaceHealthView = ({
|
|
|
52752
53356
|
const [selectedWorkspace, setSelectedWorkspace] = React24.useState(null);
|
|
52753
53357
|
const [selectedDate, setSelectedDate] = React24.useState(void 0);
|
|
52754
53358
|
const [selectedShiftId, setSelectedShiftId] = React24.useState(void 0);
|
|
53359
|
+
const [showDatePicker, setShowDatePicker] = React24.useState(false);
|
|
52755
53360
|
const effectiveTimezone = timezone || "UTC";
|
|
52756
53361
|
const currentShiftDetails = getCurrentShift(effectiveTimezone, shiftConfig);
|
|
52757
53362
|
const operationalDate = currentShiftDetails.date;
|
|
@@ -52800,28 +53405,6 @@ var WorkspaceHealthView = ({
|
|
|
52800
53405
|
const handleCloseDetails = React24.useCallback(() => {
|
|
52801
53406
|
setSelectedWorkspace(null);
|
|
52802
53407
|
}, []);
|
|
52803
|
-
const handleExport = React24.useCallback(() => {
|
|
52804
|
-
const csv = [
|
|
52805
|
-
["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
|
|
52806
|
-
...workspaces.map((w) => [
|
|
52807
|
-
w.workspace_display_name || "",
|
|
52808
|
-
w.line_name || "",
|
|
52809
|
-
w.company_name || "",
|
|
52810
|
-
w.status,
|
|
52811
|
-
w.last_heartbeat,
|
|
52812
|
-
w.consecutive_misses?.toString() || "0"
|
|
52813
|
-
])
|
|
52814
|
-
].map((row) => row.join(",")).join("\n");
|
|
52815
|
-
const blob = new Blob([csv], { type: "text/csv" });
|
|
52816
|
-
const url = window.URL.createObjectURL(blob);
|
|
52817
|
-
const a = document.createElement("a");
|
|
52818
|
-
a.href = url;
|
|
52819
|
-
a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
|
|
52820
|
-
document.body.appendChild(a);
|
|
52821
|
-
a.click();
|
|
52822
|
-
document.body.removeChild(a);
|
|
52823
|
-
window.URL.revokeObjectURL(url);
|
|
52824
|
-
}, [workspaces]);
|
|
52825
53408
|
const getStatusIcon = (status) => {
|
|
52826
53409
|
switch (status) {
|
|
52827
53410
|
case "healthy":
|
|
@@ -52859,104 +53442,42 @@ var WorkspaceHealthView = ({
|
|
|
52859
53442
|
}
|
|
52860
53443
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
52861
53444
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
|
|
52862
|
-
/* @__PURE__ */ jsxRuntime.
|
|
52863
|
-
/* @__PURE__ */ jsxRuntime.
|
|
52864
|
-
|
|
52865
|
-
|
|
52866
|
-
|
|
52867
|
-
|
|
52868
|
-
|
|
52869
|
-
|
|
52870
|
-
|
|
52871
|
-
|
|
52872
|
-
|
|
52873
|
-
|
|
52874
|
-
|
|
52875
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52876
|
-
"button",
|
|
52877
|
-
{
|
|
52878
|
-
onClick: () => {
|
|
52879
|
-
refetch();
|
|
52880
|
-
},
|
|
52881
|
-
className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52882
|
-
"aria-label": "Refresh",
|
|
52883
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4" })
|
|
52884
|
-
}
|
|
52885
|
-
),
|
|
52886
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52887
|
-
"button",
|
|
52888
|
-
{
|
|
52889
|
-
onClick: handleExport,
|
|
52890
|
-
className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52891
|
-
"aria-label": "Export CSV",
|
|
52892
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" })
|
|
52893
|
-
}
|
|
52894
|
-
)
|
|
52895
|
-
] })
|
|
52896
|
-
] }),
|
|
52897
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2", children: [
|
|
52898
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-gray-900", children: "System Health" }),
|
|
52899
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2 w-2", children: [
|
|
52900
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
|
|
52901
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-emerald-500" })
|
|
52902
|
-
] })
|
|
52903
|
-
] }) })
|
|
53445
|
+
/* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
53446
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
53447
|
+
BackButtonMinimal,
|
|
53448
|
+
{
|
|
53449
|
+
onClick: () => router$1.push("/"),
|
|
53450
|
+
text: "Back",
|
|
53451
|
+
size: "default",
|
|
53452
|
+
"aria-label": "Navigate back to dashboard"
|
|
53453
|
+
}
|
|
53454
|
+
) }),
|
|
53455
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
|
|
53456
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "System Health" }),
|
|
53457
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center", children: "Monitor workspace connectivity and operational status" })
|
|
52904
53458
|
] }),
|
|
52905
|
-
/* @__PURE__ */ jsxRuntime.
|
|
52906
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52907
|
-
|
|
53459
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
53460
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53461
|
+
"button",
|
|
52908
53462
|
{
|
|
52909
|
-
onClick: () =>
|
|
52910
|
-
text: "
|
|
52911
|
-
|
|
52912
|
-
|
|
53463
|
+
onClick: () => setShowDatePicker(!showDatePicker),
|
|
53464
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
53465
|
+
"aria-label": "Select date",
|
|
53466
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "h-5 w-5" })
|
|
52913
53467
|
}
|
|
52914
|
-
)
|
|
52915
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52916
|
-
|
|
52917
|
-
|
|
52918
|
-
|
|
52919
|
-
|
|
52920
|
-
|
|
52921
|
-
|
|
52922
|
-
|
|
52923
|
-
|
|
52924
|
-
|
|
52925
|
-
|
|
52926
|
-
|
|
52927
|
-
refetch();
|
|
52928
|
-
},
|
|
52929
|
-
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52930
|
-
"aria-label": "Refresh",
|
|
52931
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
|
|
52932
|
-
}
|
|
52933
|
-
),
|
|
52934
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52935
|
-
"button",
|
|
52936
|
-
{
|
|
52937
|
-
onClick: handleExport,
|
|
52938
|
-
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52939
|
-
"aria-label": "Export CSV",
|
|
52940
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
|
|
52941
|
-
}
|
|
52942
|
-
)
|
|
52943
|
-
] }),
|
|
52944
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
|
|
52945
|
-
] }) }),
|
|
52946
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 sm:mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
52947
|
-
HealthDateShiftSelector,
|
|
52948
|
-
{
|
|
52949
|
-
selectedDate: selectedDate || operationalDate,
|
|
52950
|
-
selectedShiftId: selectedShiftId ?? currentShiftDetails.shiftId,
|
|
52951
|
-
shiftConfig,
|
|
52952
|
-
timezone: effectiveTimezone,
|
|
52953
|
-
onDateChange: setSelectedDate,
|
|
52954
|
-
onShiftChange: setSelectedShiftId,
|
|
52955
|
-
isCurrentShift: isViewingCurrentShift,
|
|
52956
|
-
onReturnToLive: handleReturnToLive
|
|
52957
|
-
}
|
|
52958
|
-
) })
|
|
52959
|
-
] }),
|
|
53468
|
+
),
|
|
53469
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53470
|
+
"button",
|
|
53471
|
+
{
|
|
53472
|
+
onClick: () => refetch(),
|
|
53473
|
+
disabled: loading,
|
|
53474
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
53475
|
+
"aria-label": "Refresh",
|
|
53476
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-5 w-5 ${loading ? "animate-spin" : ""}` })
|
|
53477
|
+
}
|
|
53478
|
+
)
|
|
53479
|
+
] })
|
|
53480
|
+
] }) }) }),
|
|
52960
53481
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
52961
53482
|
summary && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
52962
53483
|
motion.div,
|
|
@@ -53032,6 +53553,72 @@ var WorkspaceHealthView = ({
|
|
|
53032
53553
|
)
|
|
53033
53554
|
] })
|
|
53034
53555
|
] }),
|
|
53556
|
+
showDatePicker && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
53557
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53558
|
+
"div",
|
|
53559
|
+
{
|
|
53560
|
+
className: "fixed inset-0 bg-black/20 z-40",
|
|
53561
|
+
onClick: () => setShowDatePicker(false)
|
|
53562
|
+
}
|
|
53563
|
+
),
|
|
53564
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed top-20 right-4 md:right-8 z-50 bg-white rounded-lg shadow-xl border border-gray-200 p-4 w-80", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
53565
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
53566
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Date" }),
|
|
53567
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53568
|
+
"input",
|
|
53569
|
+
{
|
|
53570
|
+
type: "date",
|
|
53571
|
+
value: selectedDate || operationalDate,
|
|
53572
|
+
max: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
53573
|
+
onChange: (e) => {
|
|
53574
|
+
setSelectedDate(e.target.value);
|
|
53575
|
+
},
|
|
53576
|
+
className: "w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
53577
|
+
}
|
|
53578
|
+
)
|
|
53579
|
+
] }),
|
|
53580
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
53581
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Shift" }),
|
|
53582
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53583
|
+
"select",
|
|
53584
|
+
{
|
|
53585
|
+
value: selectedShiftId ?? currentShiftDetails.shiftId,
|
|
53586
|
+
onChange: (e) => setSelectedShiftId(Number(e.target.value)),
|
|
53587
|
+
className: "w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white",
|
|
53588
|
+
children: (shiftConfig?.shifts || []).map((shift) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: shift.shiftId, children: [
|
|
53589
|
+
shift.shiftName,
|
|
53590
|
+
" (",
|
|
53591
|
+
shift.startTime,
|
|
53592
|
+
" - ",
|
|
53593
|
+
shift.endTime,
|
|
53594
|
+
")"
|
|
53595
|
+
] }, shift.shiftId))
|
|
53596
|
+
}
|
|
53597
|
+
)
|
|
53598
|
+
] }),
|
|
53599
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-2", children: [
|
|
53600
|
+
!isViewingCurrentShift && /* @__PURE__ */ jsxRuntime.jsx(
|
|
53601
|
+
"button",
|
|
53602
|
+
{
|
|
53603
|
+
onClick: () => {
|
|
53604
|
+
handleReturnToLive();
|
|
53605
|
+
setShowDatePicker(false);
|
|
53606
|
+
},
|
|
53607
|
+
className: "flex-1 px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors",
|
|
53608
|
+
children: "Return to Live"
|
|
53609
|
+
}
|
|
53610
|
+
),
|
|
53611
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53612
|
+
"button",
|
|
53613
|
+
{
|
|
53614
|
+
onClick: () => setShowDatePicker(false),
|
|
53615
|
+
className: "flex-1 px-3 py-2 text-sm text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors",
|
|
53616
|
+
children: "Done"
|
|
53617
|
+
}
|
|
53618
|
+
)
|
|
53619
|
+
] })
|
|
53620
|
+
] }) })
|
|
53621
|
+
] }),
|
|
53035
53622
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53036
53623
|
WorkspaceUptimeDetailModal_default,
|
|
53037
53624
|
{
|
|
@@ -54181,7 +54768,7 @@ function DailyBarChart({
|
|
|
54181
54768
|
axisLine: false,
|
|
54182
54769
|
tick: (props) => {
|
|
54183
54770
|
const { x, y, payload } = props;
|
|
54184
|
-
if (payload.value === 0) return
|
|
54771
|
+
if (payload.value === 0) return /* @__PURE__ */ jsxRuntime.jsx("g", {});
|
|
54185
54772
|
const hours = Math.round(payload.value / (1e3 * 60 * 60) * 10) / 10;
|
|
54186
54773
|
return /* @__PURE__ */ jsxRuntime.jsxs("text", { x, y, dy: 4, textAnchor: "end", className: "text-xs fill-gray-400", children: [
|
|
54187
54774
|
hours,
|
|
@@ -54536,7 +55123,7 @@ var UserManagementTable = ({
|
|
|
54536
55123
|
}
|
|
54537
55124
|
),
|
|
54538
55125
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Assignments" }),
|
|
54539
|
-
showUsageStats && /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "
|
|
55126
|
+
showUsageStats && /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Average Daily usage" }),
|
|
54540
55127
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Actions" })
|
|
54541
55128
|
] }) }),
|
|
54542
55129
|
/* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: filteredAndSortedUsers.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: showUsageStats ? 5 : 4, className: "px-6 py-12", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center text-gray-400 gap-2", children: [
|
|
@@ -54548,12 +55135,25 @@ var UserManagementTable = ({
|
|
|
54548
55135
|
const canChangeRole = permissions.canChangeRole(user);
|
|
54549
55136
|
const canRemove = permissions.canRemoveUser(user);
|
|
54550
55137
|
const hasActions = canChangeRole || canRemove;
|
|
55138
|
+
const canShowUsageModal = showUsageStats && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55139
|
+
const handleRowClick = (e) => {
|
|
55140
|
+
const target = e.target;
|
|
55141
|
+
if (target.closest("button") || target.closest("select") || target.closest("input") || target.closest('[role="button"]') || target.closest("[data-interactive]")) {
|
|
55142
|
+
return;
|
|
55143
|
+
}
|
|
55144
|
+
if (canShowUsageModal) {
|
|
55145
|
+
setUsageDetailUserId(user.user_id);
|
|
55146
|
+
setShowUsageDetailModal(true);
|
|
55147
|
+
}
|
|
55148
|
+
};
|
|
54551
55149
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
54552
55150
|
"tr",
|
|
54553
55151
|
{
|
|
55152
|
+
onClick: handleRowClick,
|
|
54554
55153
|
className: cn(
|
|
54555
|
-
"
|
|
54556
|
-
isDeactivated && "opacity-60"
|
|
55154
|
+
"transition-all duration-150",
|
|
55155
|
+
isDeactivated && "opacity-60",
|
|
55156
|
+
canShowUsageModal ? "cursor-pointer hover:bg-blue-50/50 hover:shadow-sm" : "hover:bg-gray-50"
|
|
54557
55157
|
),
|
|
54558
55158
|
children: [
|
|
54559
55159
|
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
@@ -54619,10 +55219,7 @@ var UserManagementTable = ({
|
|
|
54619
55219
|
setShowUsageDetailModal(true);
|
|
54620
55220
|
},
|
|
54621
55221
|
className: "group flex items-center gap-3 hover:bg-gray-50 -mx-2 px-2 py-1.5 rounded-lg transition-all duration-200",
|
|
54622
|
-
children: isUsageLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse bg-gray-200 h-5 w-20 rounded" }) : /* @__PURE__ */ jsxRuntime.
|
|
54623
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-base font-semibold ${avgDailyUsage?.[user.user_id] && avgDailyUsage[user.user_id] > 0 ? "text-gray-900 group-hover:text-blue-600" : "text-gray-400"} transition-colors`, children: formatDuration3(avgDailyUsage?.[user.user_id] || 0) }),
|
|
54624
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "w-4 h-4 text-gray-400 group-hover:text-blue-600 group-hover:translate-x-0.5 transition-all" })
|
|
54625
|
-
] })
|
|
55222
|
+
children: isUsageLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse bg-gray-200 h-5 w-20 rounded" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-base font-semibold ${avgDailyUsage?.[user.user_id] && avgDailyUsage[user.user_id] > 0 ? "text-gray-900 group-hover:text-blue-600" : "text-gray-400"} transition-colors`, children: formatDuration3(avgDailyUsage?.[user.user_id] || 0) })
|
|
54626
55223
|
}
|
|
54627
55224
|
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-400", children: "-" }) }),
|
|
54628
55225
|
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right", children: hasActions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -54670,25 +55267,50 @@ var UserManagementTable = ({
|
|
|
54670
55267
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-1", children: [
|
|
54671
55268
|
(() => {
|
|
54672
55269
|
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
54673
|
-
const
|
|
54674
|
-
|
|
54675
|
-
return canChangeRole && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
55270
|
+
const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55271
|
+
return canShowUsage && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
54676
55272
|
"button",
|
|
54677
55273
|
{
|
|
54678
55274
|
onClick: () => {
|
|
54679
55275
|
if (user) {
|
|
54680
|
-
|
|
55276
|
+
setUsageDetailUserId(user.user_id);
|
|
55277
|
+
setShowUsageDetailModal(true);
|
|
55278
|
+
handleCloseActionMenu();
|
|
54681
55279
|
}
|
|
54682
55280
|
},
|
|
54683
|
-
className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2
|
|
54684
|
-
disabled: isCurrentUser,
|
|
55281
|
+
className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
|
|
54685
55282
|
children: [
|
|
54686
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
54687
|
-
"
|
|
55283
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.BarChart3, { className: "w-4 h-4" }),
|
|
55284
|
+
"View Detailed Usage"
|
|
54688
55285
|
]
|
|
54689
55286
|
}
|
|
54690
55287
|
);
|
|
54691
55288
|
})(),
|
|
55289
|
+
(() => {
|
|
55290
|
+
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
55291
|
+
const isCurrentUser = user?.user_id === currentUserId;
|
|
55292
|
+
const canChangeRole = user && permissions.canChangeRole(user);
|
|
55293
|
+
const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55294
|
+
return canChangeRole && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
55295
|
+
canShowUsage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-200 my-1" }),
|
|
55296
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
55297
|
+
"button",
|
|
55298
|
+
{
|
|
55299
|
+
onClick: () => {
|
|
55300
|
+
if (user) {
|
|
55301
|
+
handleChangeRole(user);
|
|
55302
|
+
}
|
|
55303
|
+
},
|
|
55304
|
+
className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
|
|
55305
|
+
disabled: isCurrentUser,
|
|
55306
|
+
children: [
|
|
55307
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserCog, { className: "w-4 h-4" }),
|
|
55308
|
+
"Change Role"
|
|
55309
|
+
]
|
|
55310
|
+
}
|
|
55311
|
+
)
|
|
55312
|
+
] });
|
|
55313
|
+
})(),
|
|
54692
55314
|
(() => {
|
|
54693
55315
|
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
54694
55316
|
const isCurrentUser = user?.user_id === currentUserId;
|
|
@@ -55230,7 +55852,7 @@ var TeamManagementView = ({
|
|
|
55230
55852
|
}, {});
|
|
55231
55853
|
}, [usageData, usageDateRange.daysElapsed]);
|
|
55232
55854
|
const pageTitle = user?.role_level === "optifye" ? "Platform Users" : "Team Management";
|
|
55233
|
-
const pageDescription = user?.role_level === "optifye" ? "Manage users across all companies" : user?.role_level === "owner" ? "Manage
|
|
55855
|
+
const pageDescription = user?.role_level === "optifye" ? "Manage users across all companies" : user?.role_level === "owner" ? "Manage team members and view their daily usage" : "Manage supervisors in your factory";
|
|
55234
55856
|
const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "plant_head"].includes(user.role_level) : false;
|
|
55235
55857
|
const loadData = React24.useCallback(async () => {
|
|
55236
55858
|
if (!supabase) {
|
|
@@ -57282,11 +57904,13 @@ exports.WorkspacePdfExportButton = WorkspacePdfExportButton;
|
|
|
57282
57904
|
exports.WorkspacePdfGenerator = WorkspacePdfGenerator;
|
|
57283
57905
|
exports.WorkspaceWhatsAppShareButton = WorkspaceWhatsAppShareButton;
|
|
57284
57906
|
exports.actionService = actionService;
|
|
57907
|
+
exports.aggregateKPIsFromLineMetricsRows = aggregateKPIsFromLineMetricsRows;
|
|
57285
57908
|
exports.apiUtils = apiUtils;
|
|
57286
57909
|
exports.areAllLinesOnSameShift = areAllLinesOnSameShift;
|
|
57287
57910
|
exports.authCoreService = authCoreService;
|
|
57288
57911
|
exports.authOTPService = authOTPService;
|
|
57289
57912
|
exports.authRateLimitService = authRateLimitService;
|
|
57913
|
+
exports.buildKPIsFromLineMetricsRow = buildKPIsFromLineMetricsRow;
|
|
57290
57914
|
exports.checkRateLimit = checkRateLimit2;
|
|
57291
57915
|
exports.clearAllRateLimits = clearAllRateLimits2;
|
|
57292
57916
|
exports.clearRateLimit = clearRateLimit2;
|
|
@@ -57294,6 +57918,7 @@ exports.clearS3VideoCache = clearS3VideoCache;
|
|
|
57294
57918
|
exports.clearS3VideoFromCache = clearS3VideoFromCache;
|
|
57295
57919
|
exports.clearWorkspaceDisplayNamesCache = clearWorkspaceDisplayNamesCache;
|
|
57296
57920
|
exports.cn = cn;
|
|
57921
|
+
exports.createDefaultKPIs = createDefaultKPIs;
|
|
57297
57922
|
exports.createInvitationService = createInvitationService;
|
|
57298
57923
|
exports.createLinesService = createLinesService;
|
|
57299
57924
|
exports.createSessionTracker = createSessionTracker;
|
|
@@ -57318,6 +57943,7 @@ exports.fromUrlFriendlyName = fromUrlFriendlyName;
|
|
|
57318
57943
|
exports.getAllLineDisplayNames = getAllLineDisplayNames;
|
|
57319
57944
|
exports.getAllThreadMessages = getAllThreadMessages;
|
|
57320
57945
|
exports.getAllWorkspaceDisplayNamesAsync = getAllWorkspaceDisplayNamesAsync;
|
|
57946
|
+
exports.getAllWorkspaceDisplayNamesSnapshot = getAllWorkspaceDisplayNamesSnapshot;
|
|
57321
57947
|
exports.getAnonClient = getAnonClient;
|
|
57322
57948
|
exports.getAvailableShiftIds = getAvailableShiftIds;
|
|
57323
57949
|
exports.getBrowserName = getBrowserName;
|
|
@@ -57402,12 +58028,14 @@ exports.startCoreSessionRecording = startCoreSessionRecording;
|
|
|
57402
58028
|
exports.stopCoreSessionRecording = stopCoreSessionRecording;
|
|
57403
58029
|
exports.storeWorkspaceMapping = storeWorkspaceMapping;
|
|
57404
58030
|
exports.streamProxyConfig = streamProxyConfig;
|
|
58031
|
+
exports.subscribeWorkspaceDisplayNames = subscribeWorkspaceDisplayNames;
|
|
57405
58032
|
exports.throttledReloadDashboard = throttledReloadDashboard;
|
|
57406
58033
|
exports.toUrlFriendlyName = toUrlFriendlyName;
|
|
57407
58034
|
exports.trackCoreEvent = trackCoreEvent;
|
|
57408
58035
|
exports.trackCorePageView = trackCorePageView;
|
|
57409
58036
|
exports.transformToChartData = transformToChartData;
|
|
57410
58037
|
exports.updateThreadTitle = updateThreadTitle;
|
|
58038
|
+
exports.upsertWorkspaceDisplayNameInCache = upsertWorkspaceDisplayNameInCache;
|
|
57411
58039
|
exports.useAccessControl = useAccessControl;
|
|
57412
58040
|
exports.useActiveBreaks = useActiveBreaks;
|
|
57413
58041
|
exports.useActiveLineId = useActiveLineId;
|
|
@@ -57471,6 +58099,7 @@ exports.useSubscriptionManager = useSubscriptionManager;
|
|
|
57471
58099
|
exports.useSubscriptionManagerSafe = useSubscriptionManagerSafe;
|
|
57472
58100
|
exports.useSupabase = useSupabase;
|
|
57473
58101
|
exports.useSupabaseClient = useSupabaseClient;
|
|
58102
|
+
exports.useSupervisorsByLineIds = useSupervisorsByLineIds;
|
|
57474
58103
|
exports.useTargets = useTargets;
|
|
57475
58104
|
exports.useTeamManagementPermissions = useTeamManagementPermissions;
|
|
57476
58105
|
exports.useTheme = useTheme;
|