@optifye/dashboard-core 6.10.1 → 6.10.3
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 +1316 -872
- package/dist/index.mjs +1311 -874
- 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,43 +49095,6 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
|
|
|
48514
49095
|
const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
|
|
48515
49096
|
return Number(hoursDiff.toFixed(1));
|
|
48516
49097
|
};
|
|
48517
|
-
var calculateBreakDuration = (startTime, endTime) => {
|
|
48518
|
-
const [startHour, startMinute] = startTime.split(":").map(Number);
|
|
48519
|
-
const [endHour, endMinute] = endTime.split(":").map(Number);
|
|
48520
|
-
let startMinutes = startHour * 60 + startMinute;
|
|
48521
|
-
let endMinutes = endHour * 60 + endMinute;
|
|
48522
|
-
if (endMinutes < startMinutes) {
|
|
48523
|
-
endMinutes += 24 * 60;
|
|
48524
|
-
}
|
|
48525
|
-
return endMinutes - startMinutes;
|
|
48526
|
-
};
|
|
48527
|
-
var parseBreaksFromDB = (dbBreaks) => {
|
|
48528
|
-
if (!dbBreaks) return [];
|
|
48529
|
-
if (Array.isArray(dbBreaks)) {
|
|
48530
|
-
return dbBreaks.map((breakItem) => ({
|
|
48531
|
-
startTime: breakItem.start || breakItem.startTime || "00:00",
|
|
48532
|
-
endTime: breakItem.end || breakItem.endTime || "00:00",
|
|
48533
|
-
duration: calculateBreakDuration(
|
|
48534
|
-
breakItem.start || breakItem.startTime || "00:00",
|
|
48535
|
-
breakItem.end || breakItem.endTime || "00:00"
|
|
48536
|
-
),
|
|
48537
|
-
remarks: breakItem.remarks || breakItem.name || ""
|
|
48538
|
-
}));
|
|
48539
|
-
} else if (dbBreaks.breaks && Array.isArray(dbBreaks.breaks)) {
|
|
48540
|
-
return dbBreaks.breaks.map((breakItem) => ({
|
|
48541
|
-
startTime: breakItem.start || breakItem.startTime || "00:00",
|
|
48542
|
-
endTime: breakItem.end || breakItem.endTime || "00:00",
|
|
48543
|
-
duration: calculateBreakDuration(
|
|
48544
|
-
breakItem.start || breakItem.startTime || "00:00",
|
|
48545
|
-
breakItem.end || breakItem.endTime || "00:00"
|
|
48546
|
-
),
|
|
48547
|
-
remarks: breakItem.remarks || breakItem.name || ""
|
|
48548
|
-
}));
|
|
48549
|
-
} else {
|
|
48550
|
-
console.warn("Unexpected breaks format:", dbBreaks);
|
|
48551
|
-
return [];
|
|
48552
|
-
}
|
|
48553
|
-
};
|
|
48554
49098
|
var getStoredLineState = (lineId) => {
|
|
48555
49099
|
try {
|
|
48556
49100
|
return JSON.parse(localStorage.getItem(`line_${lineId}_open`) || "false");
|
|
@@ -48842,16 +49386,9 @@ var ShiftsView = ({
|
|
|
48842
49386
|
() => lineIds.map((id3) => ({
|
|
48843
49387
|
id: id3,
|
|
48844
49388
|
name: lineNames[id3] || `Line ${id3.substring(0, 4)}`,
|
|
48845
|
-
|
|
48846
|
-
|
|
48847
|
-
|
|
48848
|
-
breaks: []
|
|
48849
|
-
},
|
|
48850
|
-
nightShift: {
|
|
48851
|
-
startTime: "20:00",
|
|
48852
|
-
endTime: "04:00",
|
|
48853
|
-
breaks: []
|
|
48854
|
-
},
|
|
49389
|
+
timezone: "Asia/Kolkata",
|
|
49390
|
+
shifts: [],
|
|
49391
|
+
// Will be populated from DB
|
|
48855
49392
|
isOpen: true,
|
|
48856
49393
|
isSaving: false,
|
|
48857
49394
|
saveSuccess: false
|
|
@@ -48889,58 +49426,37 @@ var ShiftsView = ({
|
|
|
48889
49426
|
setLoading(false);
|
|
48890
49427
|
return;
|
|
48891
49428
|
}
|
|
48892
|
-
const { data:
|
|
48893
|
-
if (
|
|
48894
|
-
console.error("Error fetching
|
|
48895
|
-
showToast("error", "Error loading
|
|
48896
|
-
setError("Failed to load
|
|
48897
|
-
return;
|
|
48898
|
-
}
|
|
48899
|
-
const { data: nightShiftOperatingHours, error: nightShiftError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 1).in("line_id", enabledLineIds);
|
|
48900
|
-
if (nightShiftError) {
|
|
48901
|
-
console.error("Error fetching night shift operating hours:", nightShiftError);
|
|
48902
|
-
showToast("error", "Error loading night shift data");
|
|
48903
|
-
setError("Failed to load night shift data");
|
|
49429
|
+
const { data: allShifts, error: shiftsError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").in("line_id", enabledLineIds);
|
|
49430
|
+
if (shiftsError) {
|
|
49431
|
+
console.error("Error fetching shifts:", shiftsError);
|
|
49432
|
+
showToast("error", "Error loading shift data");
|
|
49433
|
+
setError("Failed to load shift data");
|
|
48904
49434
|
return;
|
|
48905
49435
|
}
|
|
48906
|
-
const
|
|
48907
|
-
|
|
48908
|
-
|
|
48909
|
-
|
|
48910
|
-
breaks: parseBreaksFromDB(item.breaks)
|
|
48911
|
-
};
|
|
48912
|
-
return map;
|
|
49436
|
+
const shiftsByLine = (allShifts || []).reduce((acc, row) => {
|
|
49437
|
+
if (!acc[row.line_id]) acc[row.line_id] = [];
|
|
49438
|
+
acc[row.line_id].push(row);
|
|
49439
|
+
return acc;
|
|
48913
49440
|
}, {});
|
|
48914
|
-
const
|
|
48915
|
-
|
|
48916
|
-
|
|
48917
|
-
|
|
48918
|
-
breaks: parseBreaksFromDB(item.breaks)
|
|
48919
|
-
};
|
|
48920
|
-
return map;
|
|
49441
|
+
const { data: linesData } = await supabase.from("lines").select("id, line_name").in("id", enabledLineIds);
|
|
49442
|
+
const lineNameMap = (linesData || []).reduce((acc, line) => {
|
|
49443
|
+
if (line.line_name) acc[line.id] = line.line_name;
|
|
49444
|
+
return acc;
|
|
48921
49445
|
}, {});
|
|
48922
49446
|
setLineConfigs((prev) => {
|
|
48923
49447
|
const enabledConfigs = prev.filter((config) => enabledLineIds.includes(config.id));
|
|
48924
49448
|
return enabledConfigs.map((config) => {
|
|
48925
|
-
const
|
|
48926
|
-
const
|
|
48927
|
-
const
|
|
48928
|
-
|
|
48929
|
-
|
|
48930
|
-
|
|
48931
|
-
|
|
48932
|
-
|
|
48933
|
-
|
|
48934
|
-
|
|
48935
|
-
|
|
48936
|
-
...newConfig.nightShift,
|
|
48937
|
-
...nightShiftHoursMap[lineId]
|
|
48938
|
-
};
|
|
48939
|
-
}
|
|
48940
|
-
if (newConfig.isOpen === void 0) {
|
|
48941
|
-
newConfig.isOpen = getStoredLineState(lineId);
|
|
48942
|
-
}
|
|
48943
|
-
return newConfig;
|
|
49449
|
+
const rows = shiftsByLine[config.id] || [];
|
|
49450
|
+
const builtConfig = buildShiftConfigFromOperatingHoursRows(rows);
|
|
49451
|
+
const lineName = lineNameMap[config.id] || lineNames[config.id] || `Line ${config.id.substring(0, 4)}`;
|
|
49452
|
+
const sortedShifts = [...builtConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
|
|
49453
|
+
return {
|
|
49454
|
+
...config,
|
|
49455
|
+
name: lineName,
|
|
49456
|
+
shifts: sortedShifts,
|
|
49457
|
+
timezone: builtConfig.timezone || config.timezone,
|
|
49458
|
+
isOpen: config.isOpen ?? getStoredLineState(config.id)
|
|
49459
|
+
};
|
|
48944
49460
|
});
|
|
48945
49461
|
});
|
|
48946
49462
|
setLoading(false);
|
|
@@ -48963,183 +49479,79 @@ var ShiftsView = ({
|
|
|
48963
49479
|
);
|
|
48964
49480
|
});
|
|
48965
49481
|
}, []);
|
|
48966
|
-
const
|
|
48967
|
-
setLineConfigs((prev) => prev.map((config) => {
|
|
48968
|
-
const typedConfig = config;
|
|
48969
|
-
if (typedConfig.id === lineId) {
|
|
48970
|
-
const updatedDayShift = { ...typedConfig.dayShift, startTime: value };
|
|
48971
|
-
return {
|
|
48972
|
-
...typedConfig,
|
|
48973
|
-
dayShift: updatedDayShift
|
|
48974
|
-
};
|
|
48975
|
-
}
|
|
48976
|
-
return typedConfig;
|
|
48977
|
-
}));
|
|
48978
|
-
}, []);
|
|
48979
|
-
const updateDayShiftEndTime = React24.useCallback((lineId, value) => {
|
|
48980
|
-
setLineConfigs((prev) => prev.map((config) => {
|
|
48981
|
-
const typedConfig = config;
|
|
48982
|
-
if (typedConfig.id === lineId) {
|
|
48983
|
-
const updatedDayShift = { ...typedConfig.dayShift, endTime: value };
|
|
48984
|
-
return {
|
|
48985
|
-
...typedConfig,
|
|
48986
|
-
dayShift: updatedDayShift
|
|
48987
|
-
};
|
|
48988
|
-
}
|
|
48989
|
-
return typedConfig;
|
|
48990
|
-
}));
|
|
48991
|
-
}, []);
|
|
48992
|
-
const updateNightShiftStartTime = React24.useCallback((lineId, value) => {
|
|
48993
|
-
setLineConfigs((prev) => prev.map((config) => {
|
|
48994
|
-
const typedConfig = config;
|
|
48995
|
-
if (typedConfig.id === lineId) {
|
|
48996
|
-
const updatedNightShift = { ...typedConfig.nightShift, startTime: value };
|
|
48997
|
-
return {
|
|
48998
|
-
...typedConfig,
|
|
48999
|
-
nightShift: updatedNightShift
|
|
49000
|
-
};
|
|
49001
|
-
}
|
|
49002
|
-
return typedConfig;
|
|
49003
|
-
}));
|
|
49004
|
-
}, []);
|
|
49005
|
-
const updateNightShiftEndTime = React24.useCallback((lineId, value) => {
|
|
49006
|
-
setLineConfigs((prev) => prev.map((config) => {
|
|
49007
|
-
const typedConfig = config;
|
|
49008
|
-
if (typedConfig.id === lineId) {
|
|
49009
|
-
const updatedNightShift = { ...typedConfig.nightShift, endTime: value };
|
|
49010
|
-
return {
|
|
49011
|
-
...typedConfig,
|
|
49012
|
-
nightShift: updatedNightShift
|
|
49013
|
-
};
|
|
49014
|
-
}
|
|
49015
|
-
return typedConfig;
|
|
49016
|
-
}));
|
|
49017
|
-
}, []);
|
|
49018
|
-
const addDayShiftBreak = React24.useCallback((lineId) => {
|
|
49019
|
-
setLineConfigs((prev) => prev.map((config) => {
|
|
49020
|
-
const typedConfig = config;
|
|
49021
|
-
if (typedConfig.id === lineId) {
|
|
49022
|
-
const dayShift = { ...typedConfig.dayShift };
|
|
49023
|
-
const newBreak = {
|
|
49024
|
-
startTime: dayShift.startTime,
|
|
49025
|
-
endTime: dayShift.startTime,
|
|
49026
|
-
duration: 0,
|
|
49027
|
-
remarks: ""
|
|
49028
|
-
};
|
|
49029
|
-
return {
|
|
49030
|
-
...typedConfig,
|
|
49031
|
-
dayShift: {
|
|
49032
|
-
...dayShift,
|
|
49033
|
-
breaks: [...dayShift.breaks, newBreak]
|
|
49034
|
-
}
|
|
49035
|
-
};
|
|
49036
|
-
}
|
|
49037
|
-
return typedConfig;
|
|
49038
|
-
}));
|
|
49039
|
-
}, []);
|
|
49040
|
-
const addNightShiftBreak = React24.useCallback((lineId) => {
|
|
49482
|
+
const updateShiftTime = React24.useCallback((lineId, shiftIndex, field, value) => {
|
|
49041
49483
|
setLineConfigs((prev) => prev.map((config) => {
|
|
49042
|
-
|
|
49043
|
-
|
|
49044
|
-
|
|
49045
|
-
|
|
49046
|
-
startTime: nightShift.startTime,
|
|
49047
|
-
endTime: nightShift.startTime,
|
|
49048
|
-
duration: 0
|
|
49049
|
-
};
|
|
49050
|
-
return {
|
|
49051
|
-
...typedConfig,
|
|
49052
|
-
nightShift: {
|
|
49053
|
-
...nightShift,
|
|
49054
|
-
breaks: [...nightShift.breaks, newBreak]
|
|
49055
|
-
}
|
|
49056
|
-
};
|
|
49057
|
-
}
|
|
49058
|
-
return typedConfig;
|
|
49059
|
-
}));
|
|
49060
|
-
}, []);
|
|
49061
|
-
const updateDayShiftBreak = React24.useCallback((lineId, index, field, value) => {
|
|
49062
|
-
setLineConfigs((prev) => prev.map((config) => {
|
|
49063
|
-
const typedConfig = config;
|
|
49064
|
-
if (typedConfig.id === lineId) {
|
|
49065
|
-
const dayShift = { ...typedConfig.dayShift };
|
|
49066
|
-
const newBreaks = [...dayShift.breaks];
|
|
49067
|
-
newBreaks[index] = { ...newBreaks[index], [field]: value };
|
|
49068
|
-
if (field === "startTime" || field === "endTime") {
|
|
49069
|
-
const startParts = newBreaks[index].startTime.split(":").map(Number);
|
|
49070
|
-
const endParts = newBreaks[index].endTime.split(":").map(Number);
|
|
49071
|
-
let startMinutes = startParts[0] * 60 + startParts[1];
|
|
49072
|
-
let endMinutes = endParts[0] * 60 + endParts[1];
|
|
49073
|
-
if (endMinutes < startMinutes) {
|
|
49074
|
-
endMinutes += 24 * 60;
|
|
49075
|
-
}
|
|
49076
|
-
newBreaks[index].duration = endMinutes - startMinutes;
|
|
49484
|
+
if (config.id === lineId && config.shifts) {
|
|
49485
|
+
const newShifts = [...config.shifts];
|
|
49486
|
+
if (newShifts[shiftIndex]) {
|
|
49487
|
+
newShifts[shiftIndex] = { ...newShifts[shiftIndex], [field]: value };
|
|
49077
49488
|
}
|
|
49078
|
-
return {
|
|
49079
|
-
...typedConfig,
|
|
49080
|
-
dayShift: {
|
|
49081
|
-
...dayShift,
|
|
49082
|
-
breaks: newBreaks
|
|
49083
|
-
}
|
|
49084
|
-
};
|
|
49489
|
+
return { ...config, shifts: newShifts };
|
|
49085
49490
|
}
|
|
49086
|
-
return
|
|
49491
|
+
return config;
|
|
49087
49492
|
}));
|
|
49088
49493
|
}, []);
|
|
49089
|
-
const
|
|
49494
|
+
const addShiftBreak = React24.useCallback((lineId, shiftIndex) => {
|
|
49090
49495
|
setLineConfigs((prev) => prev.map((config) => {
|
|
49091
|
-
|
|
49092
|
-
|
|
49093
|
-
|
|
49094
|
-
|
|
49095
|
-
|
|
49096
|
-
|
|
49097
|
-
|
|
49098
|
-
|
|
49099
|
-
|
|
49100
|
-
|
|
49101
|
-
|
|
49102
|
-
|
|
49103
|
-
|
|
49104
|
-
|
|
49496
|
+
if (config.id === lineId && config.shifts) {
|
|
49497
|
+
const newShifts = [...config.shifts];
|
|
49498
|
+
if (newShifts[shiftIndex]) {
|
|
49499
|
+
const shift = newShifts[shiftIndex];
|
|
49500
|
+
const newBreak = {
|
|
49501
|
+
startTime: shift.startTime,
|
|
49502
|
+
endTime: shift.startTime,
|
|
49503
|
+
duration: 0,
|
|
49504
|
+
remarks: ""
|
|
49505
|
+
};
|
|
49506
|
+
newShifts[shiftIndex] = {
|
|
49507
|
+
...shift,
|
|
49508
|
+
breaks: [...shift.breaks || [], newBreak]
|
|
49509
|
+
};
|
|
49105
49510
|
}
|
|
49106
|
-
return {
|
|
49107
|
-
...typedConfig,
|
|
49108
|
-
nightShift: {
|
|
49109
|
-
...nightShift,
|
|
49110
|
-
breaks: newBreaks
|
|
49111
|
-
}
|
|
49112
|
-
};
|
|
49511
|
+
return { ...config, shifts: newShifts };
|
|
49113
49512
|
}
|
|
49114
|
-
return
|
|
49513
|
+
return config;
|
|
49115
49514
|
}));
|
|
49116
49515
|
}, []);
|
|
49117
|
-
const
|
|
49516
|
+
const updateShiftBreak = React24.useCallback((lineId, shiftIndex, breakIndex, field, value) => {
|
|
49118
49517
|
setLineConfigs((prev) => prev.map((config) => {
|
|
49119
|
-
if (config.id === lineId) {
|
|
49120
|
-
const
|
|
49121
|
-
|
|
49122
|
-
|
|
49123
|
-
|
|
49124
|
-
|
|
49125
|
-
|
|
49518
|
+
if (config.id === lineId && config.shifts) {
|
|
49519
|
+
const newShifts = [...config.shifts];
|
|
49520
|
+
if (newShifts[shiftIndex]) {
|
|
49521
|
+
const shift = newShifts[shiftIndex];
|
|
49522
|
+
const newBreaks = [...shift.breaks || []];
|
|
49523
|
+
if (newBreaks[breakIndex]) {
|
|
49524
|
+
newBreaks[breakIndex] = { ...newBreaks[breakIndex], [field]: value };
|
|
49525
|
+
if (field === "startTime" || field === "endTime") {
|
|
49526
|
+
const startParts = newBreaks[breakIndex].startTime.split(":").map(Number);
|
|
49527
|
+
const endParts = newBreaks[breakIndex].endTime.split(":").map(Number);
|
|
49528
|
+
let startMinutes = startParts[0] * 60 + startParts[1];
|
|
49529
|
+
let endMinutes = endParts[0] * 60 + endParts[1];
|
|
49530
|
+
if (endMinutes < startMinutes) {
|
|
49531
|
+
endMinutes += 24 * 60;
|
|
49532
|
+
}
|
|
49533
|
+
newBreaks[breakIndex].duration = endMinutes - startMinutes;
|
|
49534
|
+
}
|
|
49126
49535
|
}
|
|
49127
|
-
|
|
49536
|
+
newShifts[shiftIndex] = { ...shift, breaks: newBreaks };
|
|
49537
|
+
}
|
|
49538
|
+
return { ...config, shifts: newShifts };
|
|
49128
49539
|
}
|
|
49129
49540
|
return config;
|
|
49130
49541
|
}));
|
|
49131
49542
|
}, []);
|
|
49132
|
-
const
|
|
49543
|
+
const removeShiftBreak = React24.useCallback((lineId, shiftIndex, breakIndex) => {
|
|
49133
49544
|
setLineConfigs((prev) => prev.map((config) => {
|
|
49134
|
-
if (config.id === lineId) {
|
|
49135
|
-
const
|
|
49136
|
-
|
|
49137
|
-
|
|
49138
|
-
|
|
49139
|
-
...
|
|
49140
|
-
breaks:
|
|
49141
|
-
}
|
|
49142
|
-
}
|
|
49545
|
+
if (config.id === lineId && config.shifts) {
|
|
49546
|
+
const newShifts = [...config.shifts];
|
|
49547
|
+
if (newShifts[shiftIndex]) {
|
|
49548
|
+
const shift = newShifts[shiftIndex];
|
|
49549
|
+
newShifts[shiftIndex] = {
|
|
49550
|
+
...shift,
|
|
49551
|
+
breaks: (shift.breaks || []).filter((_, i) => i !== breakIndex)
|
|
49552
|
+
};
|
|
49553
|
+
}
|
|
49554
|
+
return { ...config, shifts: newShifts };
|
|
49143
49555
|
}
|
|
49144
49556
|
return config;
|
|
49145
49557
|
}));
|
|
@@ -49153,29 +49565,26 @@ var ShiftsView = ({
|
|
|
49153
49565
|
if (!lineConfig) {
|
|
49154
49566
|
throw new Error("Line configuration not found");
|
|
49155
49567
|
}
|
|
49156
|
-
const
|
|
49157
|
-
|
|
49158
|
-
|
|
49159
|
-
|
|
49160
|
-
|
|
49161
|
-
|
|
49162
|
-
|
|
49163
|
-
|
|
49164
|
-
|
|
49165
|
-
|
|
49166
|
-
|
|
49167
|
-
|
|
49168
|
-
|
|
49169
|
-
|
|
49170
|
-
|
|
49171
|
-
|
|
49172
|
-
|
|
49173
|
-
if (dayResult.error) {
|
|
49174
|
-
throw new Error(`Failed to save day shift: ${dayResult.error.message}`);
|
|
49568
|
+
const allSavedRows = [];
|
|
49569
|
+
for (const shift of lineConfig.shifts || []) {
|
|
49570
|
+
const shiftData = {
|
|
49571
|
+
line_id: lineId,
|
|
49572
|
+
shift_id: shift.shiftId,
|
|
49573
|
+
shift_name: shift.shiftName,
|
|
49574
|
+
start_time: shift.startTime,
|
|
49575
|
+
end_time: shift.endTime,
|
|
49576
|
+
breaks: formatBreaks(shift.breaks || [])
|
|
49577
|
+
};
|
|
49578
|
+
const { data, error: error2 } = await supabase.from("line_operating_hours").upsert(shiftData).select();
|
|
49579
|
+
if (error2) {
|
|
49580
|
+
throw new Error(`Failed to save shift ${shift.shiftName}: ${error2.message}`);
|
|
49581
|
+
}
|
|
49582
|
+
if (data) {
|
|
49583
|
+
allSavedRows.push(...data);
|
|
49584
|
+
}
|
|
49175
49585
|
}
|
|
49176
|
-
|
|
49177
|
-
|
|
49178
|
-
throw new Error(`Failed to save night shift: ${nightResult.error.message}`);
|
|
49586
|
+
if (allSavedRows.length > 0) {
|
|
49587
|
+
shiftConfigStore.setFromOperatingHoursRows(lineId, allSavedRows, shiftConfigStore.get(lineId));
|
|
49179
49588
|
}
|
|
49180
49589
|
setLineConfigs((prev) => prev.map(
|
|
49181
49590
|
(config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
|
|
@@ -49254,48 +49663,34 @@ var ShiftsView = ({
|
|
|
49254
49663
|
)
|
|
49255
49664
|
] })
|
|
49256
49665
|
] }) }),
|
|
49257
|
-
/* @__PURE__ */ jsxRuntime.
|
|
49258
|
-
|
|
49259
|
-
|
|
49260
|
-
|
|
49261
|
-
|
|
49262
|
-
|
|
49263
|
-
|
|
49264
|
-
|
|
49265
|
-
|
|
49266
|
-
|
|
49267
|
-
|
|
49268
|
-
|
|
49269
|
-
|
|
49270
|
-
|
|
49271
|
-
|
|
49272
|
-
|
|
49273
|
-
|
|
49274
|
-
|
|
49275
|
-
|
|
49276
|
-
|
|
49277
|
-
|
|
49278
|
-
|
|
49279
|
-
|
|
49280
|
-
|
|
49281
|
-
|
|
49282
|
-
|
|
49283
|
-
|
|
49284
|
-
|
|
49285
|
-
breaks: config.nightShift.breaks,
|
|
49286
|
-
onStartTimeChange: (value) => updateNightShiftStartTime(config.id, value),
|
|
49287
|
-
onEndTimeChange: (value) => updateNightShiftEndTime(config.id, value),
|
|
49288
|
-
onBreakUpdate: (index, field, value) => updateNightShiftBreak(config.id, index, field, value),
|
|
49289
|
-
onBreakRemove: (index) => removeNightShiftBreak(config.id, index),
|
|
49290
|
-
onBreakAdd: () => addNightShiftBreak(config.id),
|
|
49291
|
-
shiftHours: calculateShiftHours(
|
|
49292
|
-
config.nightShift.startTime,
|
|
49293
|
-
config.nightShift.endTime,
|
|
49294
|
-
config.nightShift.breaks
|
|
49295
|
-
)
|
|
49296
|
-
}
|
|
49297
|
-
)
|
|
49298
|
-
] })
|
|
49666
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { id: `shift-panel-${config.id}`, className: "p-3 sm:p-4 md:p-6 border-t border-gray-200 w-full", children: config.shifts && config.shifts.length > 0 ? config.shifts.map((shift, shiftIndex) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
49667
|
+
ShiftPanel,
|
|
49668
|
+
{
|
|
49669
|
+
title: shift.shiftName,
|
|
49670
|
+
icon: (
|
|
49671
|
+
// Icon based on shift name (case-insensitive)
|
|
49672
|
+
shift.shiftName.toLowerCase().includes("day") ? /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5 text-gray-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" }) }) : shift.shiftName.toLowerCase().includes("night") ? /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5 text-gray-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" }) }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "w-5 h-5 text-gray-600" })
|
|
49673
|
+
),
|
|
49674
|
+
startTime: shift.startTime,
|
|
49675
|
+
endTime: shift.endTime,
|
|
49676
|
+
breaks: shift.breaks || [],
|
|
49677
|
+
onStartTimeChange: (value) => updateShiftTime(config.id, shiftIndex, "startTime", value),
|
|
49678
|
+
onEndTimeChange: (value) => updateShiftTime(config.id, shiftIndex, "endTime", value),
|
|
49679
|
+
onBreakUpdate: (breakIndex, field, value) => updateShiftBreak(config.id, shiftIndex, breakIndex, field, value),
|
|
49680
|
+
onBreakRemove: (breakIndex) => removeShiftBreak(config.id, shiftIndex, breakIndex),
|
|
49681
|
+
onBreakAdd: () => addShiftBreak(config.id, shiftIndex),
|
|
49682
|
+
shiftHours: calculateShiftHours(
|
|
49683
|
+
shift.startTime,
|
|
49684
|
+
shift.endTime,
|
|
49685
|
+
shift.breaks || []
|
|
49686
|
+
)
|
|
49687
|
+
},
|
|
49688
|
+
shift.shiftId
|
|
49689
|
+
)) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center text-gray-500 py-8", children: [
|
|
49690
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "w-12 h-12 mx-auto mb-3 text-gray-400" }),
|
|
49691
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "No shifts configured for this line" }),
|
|
49692
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400 mt-1", children: "Shifts will appear here once configured in the database" })
|
|
49693
|
+
] }) })
|
|
49299
49694
|
] }, config.id)) })
|
|
49300
49695
|
] })
|
|
49301
49696
|
] });
|
|
@@ -50146,6 +50541,7 @@ var TargetsViewUI = ({
|
|
|
50146
50541
|
onUpdateSelectedSKU,
|
|
50147
50542
|
skuRequired = false
|
|
50148
50543
|
}) => {
|
|
50544
|
+
const { displayNames: workspaceDisplayNames } = useWorkspaceDisplayNames();
|
|
50149
50545
|
if (isLoading) {
|
|
50150
50546
|
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
50547
|
}
|
|
@@ -50293,7 +50689,7 @@ var TargetsViewUI = ({
|
|
|
50293
50689
|
] })
|
|
50294
50690
|
] }) }),
|
|
50295
50691
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-gray-100", children: line.workspaces.map((workspace) => {
|
|
50296
|
-
const formattedName = formatWorkspaceName(workspace.name, lineId);
|
|
50692
|
+
const formattedName = workspaceDisplayNames[`${lineId}_${workspace.name}`] || formatWorkspaceName(workspace.name, lineId);
|
|
50297
50693
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
50298
50694
|
"div",
|
|
50299
50695
|
{
|
|
@@ -51017,8 +51413,17 @@ var TargetsView = ({
|
|
|
51017
51413
|
};
|
|
51018
51414
|
const handleUpdateWorkspaceDisplayName = React24.useCallback(async (workspaceId, displayName) => {
|
|
51019
51415
|
try {
|
|
51020
|
-
await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
51021
|
-
|
|
51416
|
+
const updated = await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
51417
|
+
if (updated?.line_id && updated?.workspace_id) {
|
|
51418
|
+
upsertWorkspaceDisplayNameInCache({
|
|
51419
|
+
lineId: updated.line_id,
|
|
51420
|
+
workspaceId: updated.workspace_id,
|
|
51421
|
+
displayName: updated?.display_name || displayName,
|
|
51422
|
+
enabled: updated?.enable
|
|
51423
|
+
});
|
|
51424
|
+
} else {
|
|
51425
|
+
await forceRefreshWorkspaceDisplayNames();
|
|
51426
|
+
}
|
|
51022
51427
|
sonner.toast.success("Workspace name updated successfully");
|
|
51023
51428
|
} catch (error) {
|
|
51024
51429
|
console.error("Error updating workspace display name:", error);
|
|
@@ -51697,7 +52102,13 @@ var WorkspaceDetailView = ({
|
|
|
51697
52102
|
}
|
|
51698
52103
|
)
|
|
51699
52104
|
] }),
|
|
51700
|
-
activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
52105
|
+
activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
52106
|
+
WorkspacePdfGenerator,
|
|
52107
|
+
{
|
|
52108
|
+
workspace,
|
|
52109
|
+
idleTimeReasons: idleTimeChartData
|
|
52110
|
+
}
|
|
52111
|
+
) }),
|
|
51701
52112
|
activeTab === "monthly_history" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
51702
52113
|
WorkspaceMonthlyPdfGenerator,
|
|
51703
52114
|
{
|
|
@@ -52752,6 +53163,7 @@ var WorkspaceHealthView = ({
|
|
|
52752
53163
|
const [selectedWorkspace, setSelectedWorkspace] = React24.useState(null);
|
|
52753
53164
|
const [selectedDate, setSelectedDate] = React24.useState(void 0);
|
|
52754
53165
|
const [selectedShiftId, setSelectedShiftId] = React24.useState(void 0);
|
|
53166
|
+
const [showDatePicker, setShowDatePicker] = React24.useState(false);
|
|
52755
53167
|
const effectiveTimezone = timezone || "UTC";
|
|
52756
53168
|
const currentShiftDetails = getCurrentShift(effectiveTimezone, shiftConfig);
|
|
52757
53169
|
const operationalDate = currentShiftDetails.date;
|
|
@@ -52770,8 +53182,9 @@ var WorkspaceHealthView = ({
|
|
|
52770
53182
|
loading,
|
|
52771
53183
|
error,
|
|
52772
53184
|
refetch,
|
|
52773
|
-
shiftConfig: loadedShiftConfig
|
|
53185
|
+
shiftConfig: loadedShiftConfig,
|
|
52774
53186
|
// Get shift config from the hook to pass to modal
|
|
53187
|
+
shiftConfigMap
|
|
52775
53188
|
} = useWorkspaceHealth({
|
|
52776
53189
|
lineId: effectiveLineIdForFetch,
|
|
52777
53190
|
// undefined in factory view = fetch all lines
|
|
@@ -52783,6 +53196,13 @@ var WorkspaceHealthView = ({
|
|
|
52783
53196
|
date: selectedDate,
|
|
52784
53197
|
shiftId: selectedShiftId
|
|
52785
53198
|
});
|
|
53199
|
+
const modalShiftConfig = React24.useMemo(() => {
|
|
53200
|
+
if (!selectedWorkspace) return void 0;
|
|
53201
|
+
if (isFactoryView) {
|
|
53202
|
+
return shiftConfigMap.get(selectedWorkspace.line_id);
|
|
53203
|
+
}
|
|
53204
|
+
return loadedShiftConfig || void 0;
|
|
53205
|
+
}, [isFactoryView, loadedShiftConfig, selectedWorkspace, shiftConfigMap]);
|
|
52786
53206
|
const handleWorkspaceClick = React24.useCallback(
|
|
52787
53207
|
(workspace) => {
|
|
52788
53208
|
const url = `/workspace/${workspace.workspace_id}`;
|
|
@@ -52800,28 +53220,6 @@ var WorkspaceHealthView = ({
|
|
|
52800
53220
|
const handleCloseDetails = React24.useCallback(() => {
|
|
52801
53221
|
setSelectedWorkspace(null);
|
|
52802
53222
|
}, []);
|
|
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
53223
|
const getStatusIcon = (status) => {
|
|
52826
53224
|
switch (status) {
|
|
52827
53225
|
case "healthy":
|
|
@@ -52859,104 +53257,42 @@ var WorkspaceHealthView = ({
|
|
|
52859
53257
|
}
|
|
52860
53258
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
52861
53259
|
/* @__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
|
-
] }) })
|
|
53260
|
+
/* @__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: [
|
|
53261
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
53262
|
+
BackButtonMinimal,
|
|
53263
|
+
{
|
|
53264
|
+
onClick: () => router$1.push("/"),
|
|
53265
|
+
text: "Back",
|
|
53266
|
+
size: "default",
|
|
53267
|
+
"aria-label": "Navigate back to dashboard"
|
|
53268
|
+
}
|
|
53269
|
+
) }),
|
|
53270
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
|
|
53271
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "System Health" }),
|
|
53272
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center", children: "Monitor workspace connectivity and operational status" })
|
|
52904
53273
|
] }),
|
|
52905
|
-
/* @__PURE__ */ jsxRuntime.
|
|
52906
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52907
|
-
|
|
53274
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
53275
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53276
|
+
"button",
|
|
52908
53277
|
{
|
|
52909
|
-
onClick: () =>
|
|
52910
|
-
text: "
|
|
52911
|
-
|
|
52912
|
-
|
|
53278
|
+
onClick: () => setShowDatePicker(!showDatePicker),
|
|
53279
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
53280
|
+
"aria-label": "Select date",
|
|
53281
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "h-5 w-5" })
|
|
52913
53282
|
}
|
|
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
|
-
] }),
|
|
53283
|
+
),
|
|
53284
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53285
|
+
"button",
|
|
53286
|
+
{
|
|
53287
|
+
onClick: () => refetch(),
|
|
53288
|
+
disabled: loading,
|
|
53289
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
53290
|
+
"aria-label": "Refresh",
|
|
53291
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-5 w-5 ${loading ? "animate-spin" : ""}` })
|
|
53292
|
+
}
|
|
53293
|
+
)
|
|
53294
|
+
] })
|
|
53295
|
+
] }) }) }),
|
|
52960
53296
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
52961
53297
|
summary && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
52962
53298
|
motion.div,
|
|
@@ -53032,13 +53368,79 @@ var WorkspaceHealthView = ({
|
|
|
53032
53368
|
)
|
|
53033
53369
|
] })
|
|
53034
53370
|
] }),
|
|
53371
|
+
showDatePicker && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
53372
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53373
|
+
"div",
|
|
53374
|
+
{
|
|
53375
|
+
className: "fixed inset-0 bg-black/20 z-40",
|
|
53376
|
+
onClick: () => setShowDatePicker(false)
|
|
53377
|
+
}
|
|
53378
|
+
),
|
|
53379
|
+
/* @__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: [
|
|
53380
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
53381
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Date" }),
|
|
53382
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53383
|
+
"input",
|
|
53384
|
+
{
|
|
53385
|
+
type: "date",
|
|
53386
|
+
value: selectedDate || operationalDate,
|
|
53387
|
+
max: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
53388
|
+
onChange: (e) => {
|
|
53389
|
+
setSelectedDate(e.target.value);
|
|
53390
|
+
},
|
|
53391
|
+
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"
|
|
53392
|
+
}
|
|
53393
|
+
)
|
|
53394
|
+
] }),
|
|
53395
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
53396
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Shift" }),
|
|
53397
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53398
|
+
"select",
|
|
53399
|
+
{
|
|
53400
|
+
value: selectedShiftId ?? currentShiftDetails.shiftId,
|
|
53401
|
+
onChange: (e) => setSelectedShiftId(Number(e.target.value)),
|
|
53402
|
+
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",
|
|
53403
|
+
children: (shiftConfig?.shifts || []).map((shift) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: shift.shiftId, children: [
|
|
53404
|
+
shift.shiftName,
|
|
53405
|
+
" (",
|
|
53406
|
+
shift.startTime,
|
|
53407
|
+
" - ",
|
|
53408
|
+
shift.endTime,
|
|
53409
|
+
")"
|
|
53410
|
+
] }, shift.shiftId))
|
|
53411
|
+
}
|
|
53412
|
+
)
|
|
53413
|
+
] }),
|
|
53414
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-2", children: [
|
|
53415
|
+
!isViewingCurrentShift && /* @__PURE__ */ jsxRuntime.jsx(
|
|
53416
|
+
"button",
|
|
53417
|
+
{
|
|
53418
|
+
onClick: () => {
|
|
53419
|
+
handleReturnToLive();
|
|
53420
|
+
setShowDatePicker(false);
|
|
53421
|
+
},
|
|
53422
|
+
className: "flex-1 px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors",
|
|
53423
|
+
children: "Return to Live"
|
|
53424
|
+
}
|
|
53425
|
+
),
|
|
53426
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53427
|
+
"button",
|
|
53428
|
+
{
|
|
53429
|
+
onClick: () => setShowDatePicker(false),
|
|
53430
|
+
className: "flex-1 px-3 py-2 text-sm text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors",
|
|
53431
|
+
children: "Done"
|
|
53432
|
+
}
|
|
53433
|
+
)
|
|
53434
|
+
] })
|
|
53435
|
+
] }) })
|
|
53436
|
+
] }),
|
|
53035
53437
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53036
53438
|
WorkspaceUptimeDetailModal_default,
|
|
53037
53439
|
{
|
|
53038
53440
|
workspace: selectedWorkspace,
|
|
53039
53441
|
isOpen: Boolean(selectedWorkspace),
|
|
53040
53442
|
onClose: handleCloseDetails,
|
|
53041
|
-
shiftConfig:
|
|
53443
|
+
shiftConfig: modalShiftConfig,
|
|
53042
53444
|
date: selectedDate,
|
|
53043
53445
|
shiftId: selectedShiftId
|
|
53044
53446
|
}
|
|
@@ -54181,7 +54583,7 @@ function DailyBarChart({
|
|
|
54181
54583
|
axisLine: false,
|
|
54182
54584
|
tick: (props) => {
|
|
54183
54585
|
const { x, y, payload } = props;
|
|
54184
|
-
if (payload.value === 0) return
|
|
54586
|
+
if (payload.value === 0) return /* @__PURE__ */ jsxRuntime.jsx("g", {});
|
|
54185
54587
|
const hours = Math.round(payload.value / (1e3 * 60 * 60) * 10) / 10;
|
|
54186
54588
|
return /* @__PURE__ */ jsxRuntime.jsxs("text", { x, y, dy: 4, textAnchor: "end", className: "text-xs fill-gray-400", children: [
|
|
54187
54589
|
hours,
|
|
@@ -54536,7 +54938,7 @@ var UserManagementTable = ({
|
|
|
54536
54938
|
}
|
|
54537
54939
|
),
|
|
54538
54940
|
/* @__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: "
|
|
54941
|
+
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
54942
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Actions" })
|
|
54541
54943
|
] }) }),
|
|
54542
54944
|
/* @__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 +54950,25 @@ var UserManagementTable = ({
|
|
|
54548
54950
|
const canChangeRole = permissions.canChangeRole(user);
|
|
54549
54951
|
const canRemove = permissions.canRemoveUser(user);
|
|
54550
54952
|
const hasActions = canChangeRole || canRemove;
|
|
54953
|
+
const canShowUsageModal = showUsageStats && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
54954
|
+
const handleRowClick = (e) => {
|
|
54955
|
+
const target = e.target;
|
|
54956
|
+
if (target.closest("button") || target.closest("select") || target.closest("input") || target.closest('[role="button"]') || target.closest("[data-interactive]")) {
|
|
54957
|
+
return;
|
|
54958
|
+
}
|
|
54959
|
+
if (canShowUsageModal) {
|
|
54960
|
+
setUsageDetailUserId(user.user_id);
|
|
54961
|
+
setShowUsageDetailModal(true);
|
|
54962
|
+
}
|
|
54963
|
+
};
|
|
54551
54964
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
54552
54965
|
"tr",
|
|
54553
54966
|
{
|
|
54967
|
+
onClick: handleRowClick,
|
|
54554
54968
|
className: cn(
|
|
54555
|
-
"
|
|
54556
|
-
isDeactivated && "opacity-60"
|
|
54969
|
+
"transition-all duration-150",
|
|
54970
|
+
isDeactivated && "opacity-60",
|
|
54971
|
+
canShowUsageModal ? "cursor-pointer hover:bg-blue-50/50 hover:shadow-sm" : "hover:bg-gray-50"
|
|
54557
54972
|
),
|
|
54558
54973
|
children: [
|
|
54559
54974
|
/* @__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 +55034,7 @@ var UserManagementTable = ({
|
|
|
54619
55034
|
setShowUsageDetailModal(true);
|
|
54620
55035
|
},
|
|
54621
55036
|
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
|
-
] })
|
|
55037
|
+
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
55038
|
}
|
|
54627
55039
|
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-400", children: "-" }) }),
|
|
54628
55040
|
/* @__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 +55082,50 @@ var UserManagementTable = ({
|
|
|
54670
55082
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-1", children: [
|
|
54671
55083
|
(() => {
|
|
54672
55084
|
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
54673
|
-
const
|
|
54674
|
-
|
|
54675
|
-
return canChangeRole && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
55085
|
+
const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55086
|
+
return canShowUsage && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
54676
55087
|
"button",
|
|
54677
55088
|
{
|
|
54678
55089
|
onClick: () => {
|
|
54679
55090
|
if (user) {
|
|
54680
|
-
|
|
55091
|
+
setUsageDetailUserId(user.user_id);
|
|
55092
|
+
setShowUsageDetailModal(true);
|
|
55093
|
+
handleCloseActionMenu();
|
|
54681
55094
|
}
|
|
54682
55095
|
},
|
|
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,
|
|
55096
|
+
className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
|
|
54685
55097
|
children: [
|
|
54686
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
54687
|
-
"
|
|
55098
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.BarChart3, { className: "w-4 h-4" }),
|
|
55099
|
+
"View Detailed Usage"
|
|
54688
55100
|
]
|
|
54689
55101
|
}
|
|
54690
55102
|
);
|
|
54691
55103
|
})(),
|
|
55104
|
+
(() => {
|
|
55105
|
+
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
55106
|
+
const isCurrentUser = user?.user_id === currentUserId;
|
|
55107
|
+
const canChangeRole = user && permissions.canChangeRole(user);
|
|
55108
|
+
const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55109
|
+
return canChangeRole && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
55110
|
+
canShowUsage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-200 my-1" }),
|
|
55111
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
55112
|
+
"button",
|
|
55113
|
+
{
|
|
55114
|
+
onClick: () => {
|
|
55115
|
+
if (user) {
|
|
55116
|
+
handleChangeRole(user);
|
|
55117
|
+
}
|
|
55118
|
+
},
|
|
55119
|
+
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",
|
|
55120
|
+
disabled: isCurrentUser,
|
|
55121
|
+
children: [
|
|
55122
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserCog, { className: "w-4 h-4" }),
|
|
55123
|
+
"Change Role"
|
|
55124
|
+
]
|
|
55125
|
+
}
|
|
55126
|
+
)
|
|
55127
|
+
] });
|
|
55128
|
+
})(),
|
|
54692
55129
|
(() => {
|
|
54693
55130
|
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
54694
55131
|
const isCurrentUser = user?.user_id === currentUserId;
|
|
@@ -55230,7 +55667,7 @@ var TeamManagementView = ({
|
|
|
55230
55667
|
}, {});
|
|
55231
55668
|
}, [usageData, usageDateRange.daysElapsed]);
|
|
55232
55669
|
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
|
|
55670
|
+
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
55671
|
const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "plant_head"].includes(user.role_level) : false;
|
|
55235
55672
|
const loadData = React24.useCallback(async () => {
|
|
55236
55673
|
if (!supabase) {
|
|
@@ -57282,11 +57719,13 @@ exports.WorkspacePdfExportButton = WorkspacePdfExportButton;
|
|
|
57282
57719
|
exports.WorkspacePdfGenerator = WorkspacePdfGenerator;
|
|
57283
57720
|
exports.WorkspaceWhatsAppShareButton = WorkspaceWhatsAppShareButton;
|
|
57284
57721
|
exports.actionService = actionService;
|
|
57722
|
+
exports.aggregateKPIsFromLineMetricsRows = aggregateKPIsFromLineMetricsRows;
|
|
57285
57723
|
exports.apiUtils = apiUtils;
|
|
57286
57724
|
exports.areAllLinesOnSameShift = areAllLinesOnSameShift;
|
|
57287
57725
|
exports.authCoreService = authCoreService;
|
|
57288
57726
|
exports.authOTPService = authOTPService;
|
|
57289
57727
|
exports.authRateLimitService = authRateLimitService;
|
|
57728
|
+
exports.buildKPIsFromLineMetricsRow = buildKPIsFromLineMetricsRow;
|
|
57290
57729
|
exports.checkRateLimit = checkRateLimit2;
|
|
57291
57730
|
exports.clearAllRateLimits = clearAllRateLimits2;
|
|
57292
57731
|
exports.clearRateLimit = clearRateLimit2;
|
|
@@ -57294,6 +57733,7 @@ exports.clearS3VideoCache = clearS3VideoCache;
|
|
|
57294
57733
|
exports.clearS3VideoFromCache = clearS3VideoFromCache;
|
|
57295
57734
|
exports.clearWorkspaceDisplayNamesCache = clearWorkspaceDisplayNamesCache;
|
|
57296
57735
|
exports.cn = cn;
|
|
57736
|
+
exports.createDefaultKPIs = createDefaultKPIs;
|
|
57297
57737
|
exports.createInvitationService = createInvitationService;
|
|
57298
57738
|
exports.createLinesService = createLinesService;
|
|
57299
57739
|
exports.createSessionTracker = createSessionTracker;
|
|
@@ -57318,6 +57758,7 @@ exports.fromUrlFriendlyName = fromUrlFriendlyName;
|
|
|
57318
57758
|
exports.getAllLineDisplayNames = getAllLineDisplayNames;
|
|
57319
57759
|
exports.getAllThreadMessages = getAllThreadMessages;
|
|
57320
57760
|
exports.getAllWorkspaceDisplayNamesAsync = getAllWorkspaceDisplayNamesAsync;
|
|
57761
|
+
exports.getAllWorkspaceDisplayNamesSnapshot = getAllWorkspaceDisplayNamesSnapshot;
|
|
57321
57762
|
exports.getAnonClient = getAnonClient;
|
|
57322
57763
|
exports.getAvailableShiftIds = getAvailableShiftIds;
|
|
57323
57764
|
exports.getBrowserName = getBrowserName;
|
|
@@ -57402,12 +57843,14 @@ exports.startCoreSessionRecording = startCoreSessionRecording;
|
|
|
57402
57843
|
exports.stopCoreSessionRecording = stopCoreSessionRecording;
|
|
57403
57844
|
exports.storeWorkspaceMapping = storeWorkspaceMapping;
|
|
57404
57845
|
exports.streamProxyConfig = streamProxyConfig;
|
|
57846
|
+
exports.subscribeWorkspaceDisplayNames = subscribeWorkspaceDisplayNames;
|
|
57405
57847
|
exports.throttledReloadDashboard = throttledReloadDashboard;
|
|
57406
57848
|
exports.toUrlFriendlyName = toUrlFriendlyName;
|
|
57407
57849
|
exports.trackCoreEvent = trackCoreEvent;
|
|
57408
57850
|
exports.trackCorePageView = trackCorePageView;
|
|
57409
57851
|
exports.transformToChartData = transformToChartData;
|
|
57410
57852
|
exports.updateThreadTitle = updateThreadTitle;
|
|
57853
|
+
exports.upsertWorkspaceDisplayNameInCache = upsertWorkspaceDisplayNameInCache;
|
|
57411
57854
|
exports.useAccessControl = useAccessControl;
|
|
57412
57855
|
exports.useActiveBreaks = useActiveBreaks;
|
|
57413
57856
|
exports.useActiveLineId = useActiveLineId;
|
|
@@ -57471,6 +57914,7 @@ exports.useSubscriptionManager = useSubscriptionManager;
|
|
|
57471
57914
|
exports.useSubscriptionManagerSafe = useSubscriptionManagerSafe;
|
|
57472
57915
|
exports.useSupabase = useSupabase;
|
|
57473
57916
|
exports.useSupabaseClient = useSupabaseClient;
|
|
57917
|
+
exports.useSupervisorsByLineIds = useSupervisorsByLineIds;
|
|
57474
57918
|
exports.useTargets = useTargets;
|
|
57475
57919
|
exports.useTeamManagementPermissions = useTeamManagementPermissions;
|
|
57476
57920
|
exports.useTheme = useTheme;
|