@optifye/dashboard-core 6.10.0 → 6.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +6 -8
- package/dist/index.d.mts +139 -7
- package/dist/index.d.ts +139 -7
- package/dist/index.js +1904 -833
- package/dist/index.mjs +1895 -835
- 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);
|
|
@@ -8867,27 +9103,201 @@ var useLeaderboardMetrics = (date, shiftId, limit = 10, filter2 = "all") => {
|
|
|
8867
9103
|
refetch: fetchLeaderboard
|
|
8868
9104
|
};
|
|
8869
9105
|
};
|
|
9106
|
+
var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
|
|
9107
|
+
const [shiftConfigMap, setShiftConfigMap] = React24.useState(/* @__PURE__ */ new Map());
|
|
9108
|
+
const [isLoading, setIsLoading] = React24.useState(true);
|
|
9109
|
+
const [error, setError] = React24.useState(null);
|
|
9110
|
+
const supabase = useSupabase();
|
|
9111
|
+
const lineIdsKey = React24.useMemo(() => lineIds.slice().sort().join(","), [lineIds]);
|
|
9112
|
+
React24.useEffect(() => {
|
|
9113
|
+
if (!lineIds || lineIds.length === 0) {
|
|
9114
|
+
setShiftConfigMap(/* @__PURE__ */ new Map());
|
|
9115
|
+
setIsLoading(false);
|
|
9116
|
+
setError(null);
|
|
9117
|
+
return;
|
|
9118
|
+
}
|
|
9119
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9120
|
+
const validLineIds = lineIds.filter((id3) => uuidRegex.test(id3));
|
|
9121
|
+
if (validLineIds.length === 0) {
|
|
9122
|
+
console.warn("[useMultiLineShiftConfigs] No valid line IDs provided");
|
|
9123
|
+
setShiftConfigMap(/* @__PURE__ */ new Map());
|
|
9124
|
+
setIsLoading(false);
|
|
9125
|
+
setError(null);
|
|
9126
|
+
return;
|
|
9127
|
+
}
|
|
9128
|
+
let mounted = true;
|
|
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
|
+
});
|
|
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
|
+
);
|
|
9159
|
+
const fetchAllConfigs = async () => {
|
|
9160
|
+
try {
|
|
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);
|
|
9175
|
+
if (fetchError) {
|
|
9176
|
+
console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
|
|
9177
|
+
throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
|
|
9178
|
+
}
|
|
9179
|
+
const lineShiftsMap = /* @__PURE__ */ new Map();
|
|
9180
|
+
data?.forEach((row) => {
|
|
9181
|
+
if (!lineShiftsMap.has(row.line_id)) {
|
|
9182
|
+
lineShiftsMap.set(row.line_id, []);
|
|
9183
|
+
}
|
|
9184
|
+
lineShiftsMap.get(row.line_id).push(row);
|
|
9185
|
+
});
|
|
9186
|
+
lineShiftsMap.forEach((shifts, lineId) => {
|
|
9187
|
+
shiftConfigStore.setFromOperatingHoursRows(lineId, shifts, fallbackConfig);
|
|
9188
|
+
});
|
|
9189
|
+
missingLineIds.forEach((lineId) => {
|
|
9190
|
+
if (!lineShiftsMap.has(lineId) && fallbackConfig) {
|
|
9191
|
+
console.warn(`[useMultiLineShiftConfigs] No config found for line ${lineId}, using fallback`);
|
|
9192
|
+
shiftConfigStore.set(lineId, fallbackConfig);
|
|
9193
|
+
}
|
|
9194
|
+
});
|
|
9195
|
+
console.log(`[useMultiLineShiftConfigs] Stored configs for ${lineShiftsMap.size} lines`);
|
|
9196
|
+
if (mounted) {
|
|
9197
|
+
setIsLoading(false);
|
|
9198
|
+
}
|
|
9199
|
+
} catch (err) {
|
|
9200
|
+
console.error("[useMultiLineShiftConfigs] Error:", err);
|
|
9201
|
+
if (mounted) {
|
|
9202
|
+
setError(err instanceof Error ? err.message : "Failed to fetch shift configs");
|
|
9203
|
+
setIsLoading(false);
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
};
|
|
9207
|
+
fetchAllConfigs();
|
|
9208
|
+
return () => {
|
|
9209
|
+
mounted = false;
|
|
9210
|
+
unsubscribeStore();
|
|
9211
|
+
unsubscribeRealtimeList.forEach((unsub) => unsub());
|
|
9212
|
+
};
|
|
9213
|
+
}, [lineIdsKey, supabase]);
|
|
9214
|
+
return {
|
|
9215
|
+
shiftConfigMap,
|
|
9216
|
+
isLoading,
|
|
9217
|
+
error
|
|
9218
|
+
};
|
|
9219
|
+
};
|
|
9220
|
+
|
|
9221
|
+
// src/lib/utils/shiftGrouping.ts
|
|
9222
|
+
var getCurrentShiftForLine = (lineId, shiftConfig, timezone, now2 = /* @__PURE__ */ new Date()) => {
|
|
9223
|
+
const currentShift = getCurrentShift(timezone, shiftConfig, now2);
|
|
9224
|
+
return {
|
|
9225
|
+
lineId,
|
|
9226
|
+
shiftId: currentShift.shiftId,
|
|
9227
|
+
date: currentShift.date,
|
|
9228
|
+
shiftName: currentShift.shiftName || `Shift ${currentShift.shiftId}`
|
|
9229
|
+
};
|
|
9230
|
+
};
|
|
9231
|
+
var groupLinesByShift = (shiftConfigMap, timezone, now2 = /* @__PURE__ */ new Date()) => {
|
|
9232
|
+
const lineShiftInfos = [];
|
|
9233
|
+
shiftConfigMap.forEach((shiftConfig, lineId) => {
|
|
9234
|
+
const info = getCurrentShiftForLine(lineId, shiftConfig, timezone, now2);
|
|
9235
|
+
lineShiftInfos.push(info);
|
|
9236
|
+
});
|
|
9237
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
9238
|
+
lineShiftInfos.forEach((info) => {
|
|
9239
|
+
const key = `${info.shiftId}-${info.date}`;
|
|
9240
|
+
if (!groupMap.has(key)) {
|
|
9241
|
+
groupMap.set(key, {
|
|
9242
|
+
shiftId: info.shiftId,
|
|
9243
|
+
date: info.date,
|
|
9244
|
+
shiftName: info.shiftName,
|
|
9245
|
+
lineIds: []
|
|
9246
|
+
});
|
|
9247
|
+
}
|
|
9248
|
+
groupMap.get(key).lineIds.push(info.lineId);
|
|
9249
|
+
});
|
|
9250
|
+
return Array.from(groupMap.values()).sort((a, b) => a.shiftId - b.shiftId);
|
|
9251
|
+
};
|
|
9252
|
+
var areAllLinesOnSameShift = (shiftConfigMap, timezone, now2 = /* @__PURE__ */ new Date()) => {
|
|
9253
|
+
if (shiftConfigMap.size <= 1) return true;
|
|
9254
|
+
const groups = groupLinesByShift(shiftConfigMap, timezone, now2);
|
|
9255
|
+
return groups.length === 1;
|
|
9256
|
+
};
|
|
9257
|
+
var getUniformShiftGroup = (shiftConfigMap, timezone, now2 = /* @__PURE__ */ new Date()) => {
|
|
9258
|
+
const groups = groupLinesByShift(shiftConfigMap, timezone, now2);
|
|
9259
|
+
return groups.length === 1 ? groups[0] : null;
|
|
9260
|
+
};
|
|
9261
|
+
|
|
9262
|
+
// src/lib/hooks/useDashboardMetrics.ts
|
|
8870
9263
|
var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds }) => {
|
|
8871
9264
|
const { supabaseUrl, supabaseKey } = useDashboardConfig();
|
|
8872
9265
|
const entityConfig = useEntityConfig();
|
|
8873
9266
|
const databaseConfig = useDatabaseConfig();
|
|
8874
9267
|
const dateTimeConfig = useDateTimeConfig();
|
|
8875
9268
|
const isFactoryView = lineId === (entityConfig.factoryViewId || "factory");
|
|
8876
|
-
const
|
|
8877
|
-
|
|
8878
|
-
|
|
9269
|
+
const appTimezone = useAppTimezone();
|
|
9270
|
+
const defaultTimezone = appTimezone || dateTimeConfig?.defaultTimezone || "UTC";
|
|
9271
|
+
const configuredLineIds = React24.useMemo(() => {
|
|
9272
|
+
return getConfiguredLineIds(entityConfig);
|
|
8879
9273
|
}, [entityConfig]);
|
|
8880
|
-
const
|
|
8881
|
-
const {
|
|
9274
|
+
const { shiftConfig: staticShiftConfig } = useDashboardConfig();
|
|
9275
|
+
const {
|
|
9276
|
+
shiftConfigMap: multiLineShiftConfigMap,
|
|
9277
|
+
isLoading: isMultiLineShiftConfigLoading
|
|
9278
|
+
} = useMultiLineShiftConfigs(
|
|
9279
|
+
isFactoryView ? configuredLineIds : [],
|
|
9280
|
+
staticShiftConfig
|
|
9281
|
+
);
|
|
9282
|
+
const {
|
|
9283
|
+
shiftConfig: singleLineShiftConfig,
|
|
9284
|
+
isLoading: isSingleLineShiftConfigLoading,
|
|
9285
|
+
isFromDatabase
|
|
9286
|
+
} = useDynamicShiftConfig(isFactoryView ? void 0 : lineId);
|
|
9287
|
+
const shiftLoading = isFactoryView ? isMultiLineShiftConfigLoading : isSingleLineShiftConfigLoading;
|
|
9288
|
+
const shiftGroups = React24.useMemo(() => {
|
|
9289
|
+
if (!isFactoryView) return [];
|
|
9290
|
+
if (isMultiLineShiftConfigLoading || multiLineShiftConfigMap.size === 0) return [];
|
|
9291
|
+
return groupLinesByShift(multiLineShiftConfigMap, defaultTimezone);
|
|
9292
|
+
}, [isFactoryView, isMultiLineShiftConfigLoading, multiLineShiftConfigMap, defaultTimezone]);
|
|
9293
|
+
const shiftConfig = isFactoryView ? null : singleLineShiftConfig;
|
|
8882
9294
|
console.log(`[useDashboardMetrics] \u{1F3AF} Shift config for line ${lineId}:`, {
|
|
8883
9295
|
isFactoryView,
|
|
8884
|
-
lineIdForShiftConfig,
|
|
8885
|
-
firstLineId,
|
|
8886
9296
|
isFromDatabase,
|
|
8887
|
-
shiftConfig: shiftConfig ? { shifts: shiftConfig.shifts?.length, timezone: shiftConfig.timezone } : null
|
|
9297
|
+
shiftConfig: shiftConfig ? { shifts: shiftConfig.shifts?.length, timezone: shiftConfig.timezone } : null,
|
|
9298
|
+
shiftGroupsCount: shiftGroups.length,
|
|
9299
|
+
shiftGroups: shiftGroups.map((g) => ({ shiftId: g.shiftId, date: g.date, lineCount: g.lineIds.length }))
|
|
8888
9300
|
});
|
|
8889
|
-
const appTimezone = useAppTimezone();
|
|
8890
|
-
const defaultTimezone = appTimezone || dateTimeConfig?.defaultTimezone || "UTC";
|
|
8891
9301
|
const configuredLineMetricsTable = databaseConfig?.tables?.lineMetrics ?? "line_metrics";
|
|
8892
9302
|
const schema = databaseConfig?.schema ?? "public";
|
|
8893
9303
|
const supabase = useSupabase();
|
|
@@ -8924,16 +9334,6 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
8924
9334
|
setIsLoading(true);
|
|
8925
9335
|
setError(null);
|
|
8926
9336
|
try {
|
|
8927
|
-
const currentShiftDetails = getCurrentShift(defaultTimezone, shiftConfig);
|
|
8928
|
-
const operationalDate = getOperationalDate(defaultTimezone);
|
|
8929
|
-
const targetLineIds = currentLineIdToUse === (entityConfig.factoryViewId || "factory") ? userAccessibleLineIds || getConfiguredLineIds(entityConfig) : [currentLineIdToUse];
|
|
8930
|
-
if (targetLineIds.length === 0 && currentLineIdToUse === (entityConfig.factoryViewId || "factory")) {
|
|
8931
|
-
throw new Error("Factory view selected, but no lines are configured in entityConfig.");
|
|
8932
|
-
}
|
|
8933
|
-
if (targetLineIds.length === 0) {
|
|
8934
|
-
throw new Error("No target line IDs available for fetching metrics.");
|
|
8935
|
-
}
|
|
8936
|
-
const isFactoryView2 = currentLineIdToUse === (entityConfig.factoryViewId || "factory");
|
|
8937
9337
|
const { data: { session } } = await supabase.auth.getSession();
|
|
8938
9338
|
console.log("[useDashboardMetrics] Session check:", {
|
|
8939
9339
|
hasSession: !!session,
|
|
@@ -8947,42 +9347,98 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
8947
9347
|
if (!apiUrl) {
|
|
8948
9348
|
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
8949
9349
|
}
|
|
8950
|
-
const
|
|
8951
|
-
const
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
lineIdsParam,
|
|
8956
|
-
operationalDate,
|
|
8957
|
-
shiftId: currentShiftDetails.shiftId,
|
|
8958
|
-
companyId: entityConfig.companyId
|
|
8959
|
-
});
|
|
8960
|
-
const response = await fetch(url, {
|
|
8961
|
-
method: "GET",
|
|
8962
|
-
headers: {
|
|
8963
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
8964
|
-
"Content-Type": "application/json"
|
|
8965
|
-
}
|
|
8966
|
-
});
|
|
8967
|
-
console.log("[useDashboardMetrics] Response status:", response.status, response.statusText);
|
|
8968
|
-
if (!response.ok) {
|
|
8969
|
-
const errorText = await response.text();
|
|
8970
|
-
console.error("[useDashboardMetrics] Backend API error response:", errorText);
|
|
8971
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
9350
|
+
const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
|
|
9351
|
+
const isFactory = currentLineIdToUse === factoryViewIdentifier;
|
|
9352
|
+
const targetLineIds = isFactory ? userAccessibleLineIds || configuredLineIds : [currentLineIdToUse];
|
|
9353
|
+
if (targetLineIds.length === 0) {
|
|
9354
|
+
throw new Error("No target line IDs available for fetching metrics.");
|
|
8972
9355
|
}
|
|
8973
|
-
|
|
8974
|
-
let
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
9356
|
+
let allWorkspaceMetrics = [];
|
|
9357
|
+
let allLineMetrics = [];
|
|
9358
|
+
if (isFactory && shiftGroups.length > 0) {
|
|
9359
|
+
console.log(`[useDashboardMetrics] \u{1F3ED} Factory view: Fetching for ${shiftGroups.length} shift group(s)`);
|
|
9360
|
+
const metricsPromises = shiftGroups.map(async (group) => {
|
|
9361
|
+
const lineIdsParam = `line_ids=${group.lineIds.join(",")}`;
|
|
9362
|
+
const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${group.date}&shift_id=${group.shiftId}&company_id=${entityConfig.companyId}`;
|
|
9363
|
+
console.log(`[useDashboardMetrics] \u{1F4CA} Fetching for shift ${group.shiftId} (${group.shiftName}):`, {
|
|
9364
|
+
lineIds: group.lineIds,
|
|
9365
|
+
date: group.date
|
|
9366
|
+
});
|
|
9367
|
+
const response = await fetch(url, {
|
|
9368
|
+
method: "GET",
|
|
9369
|
+
headers: {
|
|
9370
|
+
"Authorization": `Bearer ${session.access_token}`,
|
|
9371
|
+
"Content-Type": "application/json"
|
|
9372
|
+
}
|
|
9373
|
+
});
|
|
9374
|
+
if (!response.ok) {
|
|
9375
|
+
const errorText = await response.text();
|
|
9376
|
+
console.error(`[useDashboardMetrics] Backend API error for shift ${group.shiftId}:`, errorText);
|
|
9377
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
9378
|
+
}
|
|
9379
|
+
const responseText = await response.text();
|
|
9380
|
+
try {
|
|
9381
|
+
return JSON.parse(responseText);
|
|
9382
|
+
} catch (parseError) {
|
|
9383
|
+
console.error("[useDashboardMetrics] Failed to parse response:", responseText.substring(0, 500));
|
|
9384
|
+
throw new Error(`Invalid JSON response from backend`);
|
|
9385
|
+
}
|
|
9386
|
+
});
|
|
9387
|
+
const results = await Promise.all(metricsPromises);
|
|
9388
|
+
results.forEach((result) => {
|
|
9389
|
+
if (result.workspace_metrics) {
|
|
9390
|
+
allWorkspaceMetrics.push(...result.workspace_metrics);
|
|
9391
|
+
}
|
|
9392
|
+
if (result.line_metrics) {
|
|
9393
|
+
allLineMetrics.push(...result.line_metrics);
|
|
9394
|
+
}
|
|
9395
|
+
});
|
|
9396
|
+
console.log(`[useDashboardMetrics] \u{1F4CA} Merged metrics from ${results.length} shift groups:`, {
|
|
9397
|
+
workspaceCount: allWorkspaceMetrics.length,
|
|
9398
|
+
lineMetricsCount: allLineMetrics.length
|
|
9399
|
+
});
|
|
9400
|
+
} else {
|
|
9401
|
+
const currentShiftDetails = shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : shiftGroups.length === 1 ? { date: shiftGroups[0].date, shiftId: shiftGroups[0].shiftId } : getCurrentShift(defaultTimezone, staticShiftConfig);
|
|
9402
|
+
const operationalDate = currentShiftDetails.date;
|
|
9403
|
+
const lineIdsParam = isFactory ? `line_ids=${targetLineIds.join(",")}` : `line_id=${targetLineIds[0]}`;
|
|
9404
|
+
const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${operationalDate}&shift_id=${currentShiftDetails.shiftId}&company_id=${entityConfig.companyId}`;
|
|
9405
|
+
console.log("[useDashboardMetrics] Calling backend API:", {
|
|
9406
|
+
url,
|
|
9407
|
+
apiUrl,
|
|
9408
|
+
lineIdsParam,
|
|
9409
|
+
operationalDate,
|
|
9410
|
+
shiftId: currentShiftDetails.shiftId,
|
|
9411
|
+
companyId: entityConfig.companyId
|
|
9412
|
+
});
|
|
9413
|
+
const response = await fetch(url, {
|
|
9414
|
+
method: "GET",
|
|
9415
|
+
headers: {
|
|
9416
|
+
"Authorization": `Bearer ${session.access_token}`,
|
|
9417
|
+
"Content-Type": "application/json"
|
|
9418
|
+
}
|
|
9419
|
+
});
|
|
9420
|
+
console.log("[useDashboardMetrics] Response status:", response.status, response.statusText);
|
|
9421
|
+
if (!response.ok) {
|
|
9422
|
+
const errorText = await response.text();
|
|
9423
|
+
console.error("[useDashboardMetrics] Backend API error response:", errorText);
|
|
9424
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
9425
|
+
}
|
|
9426
|
+
const responseText = await response.text();
|
|
9427
|
+
let backendData;
|
|
9428
|
+
try {
|
|
9429
|
+
backendData = JSON.parse(responseText);
|
|
9430
|
+
} catch (parseError) {
|
|
9431
|
+
console.error("[useDashboardMetrics] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
|
|
9432
|
+
throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
|
|
9433
|
+
}
|
|
9434
|
+
console.log("[useDashboardMetrics] Backend response:", {
|
|
9435
|
+
workspaceCount: backendData.workspace_metrics?.length || 0,
|
|
9436
|
+
lineCount: backendData.line_metrics?.length || 0
|
|
9437
|
+
});
|
|
9438
|
+
allWorkspaceMetrics = backendData.workspace_metrics || [];
|
|
9439
|
+
allLineMetrics = backendData.line_metrics || [];
|
|
8980
9440
|
}
|
|
8981
|
-
|
|
8982
|
-
workspaceCount: backendData.workspace_metrics?.length || 0,
|
|
8983
|
-
lineCount: backendData.line_metrics?.length || 0
|
|
8984
|
-
});
|
|
8985
|
-
const transformedWorkspaceData = (backendData.workspace_metrics || []).map((item) => ({
|
|
9441
|
+
const transformedWorkspaceData = allWorkspaceMetrics.map((item) => ({
|
|
8986
9442
|
company_id: item.company_id || entityConfig.companyId,
|
|
8987
9443
|
line_id: item.line_id,
|
|
8988
9444
|
shift_id: item.shift_id,
|
|
@@ -9005,7 +9461,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
9005
9461
|
});
|
|
9006
9462
|
const newMetricsState = {
|
|
9007
9463
|
workspaceMetrics: transformedWorkspaceData,
|
|
9008
|
-
lineMetrics:
|
|
9464
|
+
lineMetrics: allLineMetrics || []
|
|
9009
9465
|
};
|
|
9010
9466
|
console.log("[useDashboardMetrics] Setting metrics state:", {
|
|
9011
9467
|
workspaceMetrics: newMetricsState.workspaceMetrics.length,
|
|
@@ -9026,9 +9482,12 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
9026
9482
|
metrics2?.lineMetrics?.length || 0,
|
|
9027
9483
|
companySpecificMetricsTable,
|
|
9028
9484
|
entityConfig,
|
|
9029
|
-
appTimezone,
|
|
9030
9485
|
defaultTimezone,
|
|
9031
9486
|
shiftConfig,
|
|
9487
|
+
shiftGroups,
|
|
9488
|
+
configuredLineIds,
|
|
9489
|
+
staticShiftConfig,
|
|
9490
|
+
userAccessibleLineIds,
|
|
9032
9491
|
shiftLoading
|
|
9033
9492
|
]);
|
|
9034
9493
|
const fetchAllMetricsRef = React24.useRef(fetchAllMetrics);
|
|
@@ -9052,32 +9511,70 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
9052
9511
|
if (!currentLineIdToUse || !supabase || companySpecificMetricsTable.includes("unknown_company") || !entityConfig.companyId) {
|
|
9053
9512
|
return;
|
|
9054
9513
|
}
|
|
9055
|
-
const
|
|
9056
|
-
const
|
|
9057
|
-
const targetLineIds = currentLineIdToUse === (entityConfig.factoryViewId || "factory") ? userAccessibleLineIds || getConfiguredLineIds(entityConfig) : [currentLineIdToUse];
|
|
9058
|
-
if (targetLineIds.length === 0) return;
|
|
9059
|
-
const wsMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
9060
|
-
const lineMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
9514
|
+
const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
|
|
9515
|
+
const isFactory = currentLineIdToUse === factoryViewIdentifier;
|
|
9061
9516
|
const channels = [];
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
{
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9517
|
+
if (isFactory && shiftGroups.length > 0) {
|
|
9518
|
+
console.log(`[useDashboardMetrics] \u{1F4E1} Setting up subscriptions for ${shiftGroups.length} shift group(s)`);
|
|
9519
|
+
shiftGroups.forEach((group, index) => {
|
|
9520
|
+
const baseFilterParts = `date=eq.${group.date},shift_id=eq.${group.shiftId}`;
|
|
9521
|
+
const lineIdFilterPart = `line_id=in.(${group.lineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
9522
|
+
const filter2 = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9523
|
+
const wsChannelName = `dashboard-ws-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9524
|
+
const wsChannel = supabase.channel(wsChannelName).on(
|
|
9525
|
+
"postgres_changes",
|
|
9526
|
+
{ event: "*", schema, table: companySpecificMetricsTable, filter: filter2 },
|
|
9527
|
+
(payload) => {
|
|
9528
|
+
const payloadData = payload.new || payload.old;
|
|
9529
|
+
if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
|
|
9530
|
+
queueUpdate();
|
|
9531
|
+
}
|
|
9071
9532
|
}
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9533
|
+
).subscribe();
|
|
9534
|
+
channels.push(wsChannel);
|
|
9535
|
+
const lmChannelName = `dashboard-lm-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9536
|
+
const lmChannel = supabase.channel(lmChannelName).on(
|
|
9537
|
+
"postgres_changes",
|
|
9538
|
+
{ event: "*", schema, table: configuredLineMetricsTable, filter: filter2 },
|
|
9539
|
+
(payload) => {
|
|
9540
|
+
const payloadData = payload.new || payload.old;
|
|
9541
|
+
if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
|
|
9542
|
+
queueUpdate();
|
|
9543
|
+
onLineMetricsUpdateRef.current?.();
|
|
9544
|
+
}
|
|
9545
|
+
}
|
|
9546
|
+
).subscribe();
|
|
9547
|
+
channels.push(lmChannel);
|
|
9548
|
+
});
|
|
9549
|
+
} else {
|
|
9550
|
+
const currentShiftDetails = shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : getCurrentShift(defaultTimezone, staticShiftConfig);
|
|
9551
|
+
const operationalDateForSubscription = currentShiftDetails.date;
|
|
9552
|
+
const targetLineIds = [currentLineIdToUse];
|
|
9553
|
+
if (targetLineIds.length === 0) return;
|
|
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}`;
|
|
9558
|
+
const createSubscription = (table, filter2, channelNameBase, callback) => {
|
|
9559
|
+
const channelName = `${channelNameBase}-${Date.now()}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9560
|
+
const channel = supabase.channel(channelName).on(
|
|
9561
|
+
"postgres_changes",
|
|
9562
|
+
{ event: "*", schema, table, filter: filter2 },
|
|
9563
|
+
(payload) => {
|
|
9564
|
+
const payloadData = payload.new;
|
|
9565
|
+
if (payloadData?.date === operationalDateForSubscription && payloadData?.shift_id === currentShiftDetails.shiftId) {
|
|
9566
|
+
callback();
|
|
9567
|
+
}
|
|
9568
|
+
}
|
|
9569
|
+
).subscribe();
|
|
9570
|
+
channels.push(channel);
|
|
9571
|
+
};
|
|
9572
|
+
createSubscription(companySpecificMetricsTable, wsMetricsFilter, "dashboard-ws-metrics", queueUpdate);
|
|
9573
|
+
createSubscription(configuredLineMetricsTable, lineMetricsFilter, "dashboard-line-metrics", () => {
|
|
9574
|
+
queueUpdate();
|
|
9575
|
+
onLineMetricsUpdateRef.current?.();
|
|
9576
|
+
});
|
|
9577
|
+
}
|
|
9081
9578
|
return () => {
|
|
9082
9579
|
channels.forEach((channel) => {
|
|
9083
9580
|
supabase?.removeChannel(channel);
|
|
@@ -9091,9 +9588,10 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
9091
9588
|
schema,
|
|
9092
9589
|
entityConfig?.companyId,
|
|
9093
9590
|
entityConfig?.factoryViewId,
|
|
9094
|
-
appTimezone,
|
|
9095
9591
|
defaultTimezone,
|
|
9096
9592
|
shiftConfig,
|
|
9593
|
+
staticShiftConfig,
|
|
9594
|
+
shiftGroups,
|
|
9097
9595
|
lineId,
|
|
9098
9596
|
userAccessibleLineIds
|
|
9099
9597
|
]);
|
|
@@ -9111,20 +9609,37 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9111
9609
|
const isFactoryView = lineId === (entityConfig.factoryViewId || "factory");
|
|
9112
9610
|
const databaseConfig = useDatabaseConfig();
|
|
9113
9611
|
const dateTimeConfig = useDateTimeConfig();
|
|
9114
|
-
useShiftConfig();
|
|
9115
|
-
const
|
|
9116
|
-
|
|
9117
|
-
|
|
9612
|
+
const staticShiftConfig = useShiftConfig();
|
|
9613
|
+
const appTimezone = useAppTimezone();
|
|
9614
|
+
const timezone = appTimezone || dateTimeConfig.defaultTimezone || "UTC";
|
|
9615
|
+
const configuredLineIds = React24.useMemo(() => {
|
|
9616
|
+
return getConfiguredLineIds(entityConfig);
|
|
9118
9617
|
}, [entityConfig]);
|
|
9119
|
-
const
|
|
9120
|
-
|
|
9121
|
-
|
|
9618
|
+
const {
|
|
9619
|
+
shiftConfigMap: multiLineShiftConfigMap,
|
|
9620
|
+
isLoading: isMultiLineShiftConfigLoading
|
|
9621
|
+
} = useMultiLineShiftConfigs(
|
|
9622
|
+
isFactoryView ? configuredLineIds : [],
|
|
9623
|
+
staticShiftConfig
|
|
9624
|
+
);
|
|
9625
|
+
const {
|
|
9626
|
+
shiftConfig: singleLineShiftConfig,
|
|
9627
|
+
isFromDatabase,
|
|
9628
|
+
isLoading: isSingleLineShiftConfigLoading
|
|
9629
|
+
} = useDynamicShiftConfig(isFactoryView ? void 0 : lineId);
|
|
9630
|
+
const isShiftConfigLoading = isFactoryView ? isMultiLineShiftConfigLoading : isSingleLineShiftConfigLoading;
|
|
9631
|
+
const shiftGroups = React24.useMemo(() => {
|
|
9632
|
+
if (!isFactoryView) return [];
|
|
9633
|
+
if (isMultiLineShiftConfigLoading || multiLineShiftConfigMap.size === 0) return [];
|
|
9634
|
+
return groupLinesByShift(multiLineShiftConfigMap, timezone);
|
|
9635
|
+
}, [isFactoryView, isMultiLineShiftConfigLoading, multiLineShiftConfigMap, timezone]);
|
|
9636
|
+
const shiftConfig = isFactoryView ? null : singleLineShiftConfig;
|
|
9122
9637
|
console.log(`[useLineKPIs] \u{1F3AF} Shift config for line ${lineId}:`, {
|
|
9123
9638
|
isFactoryView,
|
|
9124
|
-
lineIdForShiftConfig,
|
|
9125
|
-
firstLineId,
|
|
9126
9639
|
isFromDatabase,
|
|
9127
|
-
shiftConfig
|
|
9640
|
+
shiftConfig,
|
|
9641
|
+
shiftGroupsCount: shiftGroups.length,
|
|
9642
|
+
shiftGroups: shiftGroups.map((g) => ({ shiftId: g.shiftId, date: g.date, lineCount: g.lineIds.length }))
|
|
9128
9643
|
});
|
|
9129
9644
|
const supabase = useSupabase();
|
|
9130
9645
|
React24.useMemo(() => {
|
|
@@ -9138,8 +9653,6 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9138
9653
|
const updateQueueRef = React24.useRef(false);
|
|
9139
9654
|
const updateTimeoutRef = React24.useRef(null);
|
|
9140
9655
|
const queueUpdateRef = React24.useRef(void 0);
|
|
9141
|
-
const appTimezone = useAppTimezone();
|
|
9142
|
-
const timezone = appTimezone || dateTimeConfig.defaultTimezone || "UTC";
|
|
9143
9656
|
const schema = databaseConfig.schema ?? "public";
|
|
9144
9657
|
const lineMetricsTable = databaseConfig.tables?.lineMetrics ?? "line_metrics";
|
|
9145
9658
|
const companySpecificMetricsTable = React24.useMemo(
|
|
@@ -9158,8 +9671,6 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9158
9671
|
setIsLoading(true);
|
|
9159
9672
|
setError(null);
|
|
9160
9673
|
try {
|
|
9161
|
-
const currentShiftDetails = getCurrentShift(timezone, shiftConfig ?? void 0);
|
|
9162
|
-
const operationalDate = currentShiftDetails.date;
|
|
9163
9674
|
const { data: { session } } = await supabase.auth.getSession();
|
|
9164
9675
|
if (!session?.access_token) {
|
|
9165
9676
|
throw new Error("No authentication token available");
|
|
@@ -9170,33 +9681,68 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9170
9681
|
}
|
|
9171
9682
|
const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
|
|
9172
9683
|
const isFactory = currentLineId === factoryViewIdentifier;
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9684
|
+
if (isFactory && shiftGroups.length > 0) {
|
|
9685
|
+
console.log(`[useLineKPIs] \u{1F3ED} Factory view: Fetching KPIs for ${shiftGroups.length} shift group(s)`);
|
|
9686
|
+
const kpiPromises = shiftGroups.map(async (group) => {
|
|
9687
|
+
const lineIdsParam = `line_ids=${group.lineIds.join(",")}`;
|
|
9688
|
+
const url = `${apiUrl}/api/dashboard/line-kpis?${lineIdsParam}&date=${group.date}&shift_id=${group.shiftId}&company_id=${entityConfig.companyId}`;
|
|
9689
|
+
console.log(`[useLineKPIs] \u{1F4CA} Fetching for shift ${group.shiftId} (${group.shiftName}):`, {
|
|
9690
|
+
lineIds: group.lineIds,
|
|
9691
|
+
date: group.date
|
|
9692
|
+
});
|
|
9693
|
+
const response = await fetch(url, {
|
|
9694
|
+
headers: {
|
|
9695
|
+
"Authorization": `Bearer ${session.access_token}`,
|
|
9696
|
+
"Content-Type": "application/json"
|
|
9697
|
+
}
|
|
9698
|
+
});
|
|
9699
|
+
if (!response.ok) {
|
|
9700
|
+
const errorText = await response.text();
|
|
9701
|
+
console.error(`[useLineKPIs] Backend API error for shift ${group.shiftId}:`, errorText);
|
|
9702
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
9180
9703
|
}
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
} catch (parseError) {
|
|
9193
|
-
console.error("[useLineKPIs] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
|
|
9194
|
-
throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
|
|
9195
|
-
}
|
|
9196
|
-
if (backendData.kpis) {
|
|
9197
|
-
setKPIs(backendData.kpis);
|
|
9704
|
+
const responseText = await response.text();
|
|
9705
|
+
try {
|
|
9706
|
+
return JSON.parse(responseText);
|
|
9707
|
+
} catch (parseError) {
|
|
9708
|
+
console.error("[useLineKPIs] Failed to parse response as JSON:", responseText.substring(0, 500));
|
|
9709
|
+
throw new Error(`Invalid JSON response from backend`);
|
|
9710
|
+
}
|
|
9711
|
+
});
|
|
9712
|
+
const results = await Promise.all(kpiPromises);
|
|
9713
|
+
const aggregatedKPIs = aggregateKPIResults(results);
|
|
9714
|
+
setKPIs(aggregatedKPIs);
|
|
9198
9715
|
} else {
|
|
9199
|
-
|
|
9716
|
+
const currentShiftDetails = shiftConfig ? getCurrentShift(timezone, shiftConfig) : shiftGroups.length === 1 ? { date: shiftGroups[0].date, shiftId: shiftGroups[0].shiftId } : getCurrentShift(timezone, staticShiftConfig);
|
|
9717
|
+
const operationalDate = currentShiftDetails.date;
|
|
9718
|
+
const lineParam = isFactory ? `line_ids=${configuredLineIds.join(",")}` : `line_id=${currentLineId}`;
|
|
9719
|
+
const response = await fetch(
|
|
9720
|
+
`${apiUrl}/api/dashboard/line-kpis?${lineParam}&date=${operationalDate}&shift_id=${currentShiftDetails.shiftId}&company_id=${entityConfig.companyId}`,
|
|
9721
|
+
{
|
|
9722
|
+
headers: {
|
|
9723
|
+
"Authorization": `Bearer ${session.access_token}`,
|
|
9724
|
+
"Content-Type": "application/json"
|
|
9725
|
+
}
|
|
9726
|
+
}
|
|
9727
|
+
);
|
|
9728
|
+
if (!response.ok) {
|
|
9729
|
+
const errorText = await response.text();
|
|
9730
|
+
console.error("[useLineKPIs] Backend API error response:", errorText);
|
|
9731
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
9732
|
+
}
|
|
9733
|
+
const responseText = await response.text();
|
|
9734
|
+
let backendData;
|
|
9735
|
+
try {
|
|
9736
|
+
backendData = JSON.parse(responseText);
|
|
9737
|
+
} catch (parseError) {
|
|
9738
|
+
console.error("[useLineKPIs] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
|
|
9739
|
+
throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
|
|
9740
|
+
}
|
|
9741
|
+
if (backendData.kpis) {
|
|
9742
|
+
setKPIs(backendData.kpis);
|
|
9743
|
+
} else {
|
|
9744
|
+
setKPIs(null);
|
|
9745
|
+
}
|
|
9200
9746
|
}
|
|
9201
9747
|
} catch (err) {
|
|
9202
9748
|
console.error("[useLineKPIs] Error fetching KPIs:", err);
|
|
@@ -9207,7 +9753,80 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9207
9753
|
isFetchingRef.current = false;
|
|
9208
9754
|
updateQueueRef.current = false;
|
|
9209
9755
|
}
|
|
9210
|
-
}, [timezone, shiftConfig, entityConfig, supabase, enabled, isShiftConfigLoading]);
|
|
9756
|
+
}, [timezone, shiftConfig, shiftGroups, configuredLineIds, staticShiftConfig, entityConfig, supabase, enabled, isShiftConfigLoading]);
|
|
9757
|
+
const aggregateKPIResults = (results) => {
|
|
9758
|
+
const validResults = results.filter((r2) => r2?.kpis);
|
|
9759
|
+
if (validResults.length === 0) return null;
|
|
9760
|
+
if (validResults.length === 1) return validResults[0].kpis;
|
|
9761
|
+
let totalEfficiency = 0;
|
|
9762
|
+
let totalEfficiencyWeight = 0;
|
|
9763
|
+
let totalOutputCurrent = 0;
|
|
9764
|
+
let totalOutputTarget = 0;
|
|
9765
|
+
let totalIdealOutput = 0;
|
|
9766
|
+
let totalUnderperformingCurrent = 0;
|
|
9767
|
+
let totalUnderperformingTotal = 0;
|
|
9768
|
+
let totalCycleTimeSum = 0;
|
|
9769
|
+
let totalCycleTimeCount = 0;
|
|
9770
|
+
let totalQualitySum = 0;
|
|
9771
|
+
let totalQualityCount = 0;
|
|
9772
|
+
validResults.forEach((result) => {
|
|
9773
|
+
const kpis2 = result.kpis;
|
|
9774
|
+
if (kpis2.efficiency?.value !== void 0) {
|
|
9775
|
+
const weight = kpis2.outputProgress?.current || 1;
|
|
9776
|
+
totalEfficiency += kpis2.efficiency.value * weight;
|
|
9777
|
+
totalEfficiencyWeight += weight;
|
|
9778
|
+
}
|
|
9779
|
+
if (kpis2.outputProgress) {
|
|
9780
|
+
totalOutputCurrent += kpis2.outputProgress.current || 0;
|
|
9781
|
+
totalOutputTarget += kpis2.outputProgress.target || 0;
|
|
9782
|
+
totalIdealOutput += kpis2.outputProgress.idealOutput || 0;
|
|
9783
|
+
}
|
|
9784
|
+
if (kpis2.underperformingWorkers) {
|
|
9785
|
+
totalUnderperformingCurrent += kpis2.underperformingWorkers.current || 0;
|
|
9786
|
+
totalUnderperformingTotal += kpis2.underperformingWorkers.total || 0;
|
|
9787
|
+
}
|
|
9788
|
+
if (kpis2.avgCycleTime?.value !== void 0) {
|
|
9789
|
+
totalCycleTimeSum += kpis2.avgCycleTime.value;
|
|
9790
|
+
totalCycleTimeCount++;
|
|
9791
|
+
}
|
|
9792
|
+
if (kpis2.qualityCompliance?.value !== void 0) {
|
|
9793
|
+
totalQualitySum += kpis2.qualityCompliance.value;
|
|
9794
|
+
totalQualityCount++;
|
|
9795
|
+
}
|
|
9796
|
+
});
|
|
9797
|
+
const aggregated = {
|
|
9798
|
+
efficiency: {
|
|
9799
|
+
value: totalEfficiencyWeight > 0 ? totalEfficiency / totalEfficiencyWeight : 0,
|
|
9800
|
+
change: 0
|
|
9801
|
+
// Change not meaningful when aggregating across shifts
|
|
9802
|
+
},
|
|
9803
|
+
outputProgress: {
|
|
9804
|
+
current: totalOutputCurrent,
|
|
9805
|
+
target: totalOutputTarget,
|
|
9806
|
+
change: 0,
|
|
9807
|
+
// Change not meaningful when aggregating across shifts
|
|
9808
|
+
idealOutput: totalIdealOutput
|
|
9809
|
+
},
|
|
9810
|
+
underperformingWorkers: {
|
|
9811
|
+
current: totalUnderperformingCurrent,
|
|
9812
|
+
total: totalUnderperformingTotal,
|
|
9813
|
+
change: 0
|
|
9814
|
+
// Change not meaningful when aggregating across shifts
|
|
9815
|
+
},
|
|
9816
|
+
avgCycleTime: {
|
|
9817
|
+
value: totalCycleTimeCount > 0 ? totalCycleTimeSum / totalCycleTimeCount : 0,
|
|
9818
|
+
change: 0
|
|
9819
|
+
// Change not meaningful when aggregating across shifts
|
|
9820
|
+
},
|
|
9821
|
+
qualityCompliance: {
|
|
9822
|
+
value: totalQualityCount > 0 ? totalQualitySum / totalQualityCount : 0,
|
|
9823
|
+
change: 0
|
|
9824
|
+
// Change not meaningful when aggregating across shifts
|
|
9825
|
+
}
|
|
9826
|
+
};
|
|
9827
|
+
console.log("[useLineKPIs] \u{1F4CA} Aggregated KPIs from", validResults.length, "shift groups:", aggregated);
|
|
9828
|
+
return aggregated;
|
|
9829
|
+
};
|
|
9211
9830
|
const queueUpdate = React24.useCallback(() => {
|
|
9212
9831
|
if (updateTimeoutRef.current) {
|
|
9213
9832
|
clearTimeout(updateTimeoutRef.current);
|
|
@@ -9227,40 +9846,66 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9227
9846
|
return;
|
|
9228
9847
|
}
|
|
9229
9848
|
fetchKPIs();
|
|
9230
|
-
const currentShiftDetails = getCurrentShift(timezone, shiftConfig ?? void 0);
|
|
9231
|
-
const operationalDate = currentShiftDetails.date;
|
|
9232
9849
|
const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
|
|
9233
|
-
const
|
|
9234
|
-
if (targetLineIds.length === 0) {
|
|
9235
|
-
console.warn("[useLineKPIs] No target line IDs for subscription. LineId:", currentLineId);
|
|
9236
|
-
return;
|
|
9237
|
-
}
|
|
9238
|
-
const baseFilterParts = `date=eq.${operationalDate},shift_id=eq.${currentShiftDetails.shiftId}`;
|
|
9239
|
-
const lineIdFilterPart = `line_id=in.(${targetLineIds.join(",")})`;
|
|
9240
|
-
const lineMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9241
|
-
const companyTableFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9850
|
+
const isFactory = currentLineId === factoryViewIdentifier;
|
|
9242
9851
|
const activeChannels = [];
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
const
|
|
9249
|
-
|
|
9250
|
-
|
|
9852
|
+
if (isFactory && shiftGroups.length > 0) {
|
|
9853
|
+
console.log(`[useLineKPIs] \u{1F4E1} Setting up subscriptions for ${shiftGroups.length} shift group(s)`);
|
|
9854
|
+
shiftGroups.forEach((group, index) => {
|
|
9855
|
+
const baseFilterParts = `date=eq.${group.date},shift_id=eq.${group.shiftId}`;
|
|
9856
|
+
const lineIdFilterPart = `line_id=in.(${group.lineIds.join(",")})`;
|
|
9857
|
+
const filter2 = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9858
|
+
const lmChannelName = `kpi-lm-factory-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9859
|
+
const lmChannel = supabase.channel(lmChannelName).on(
|
|
9860
|
+
"postgres_changes",
|
|
9861
|
+
{ event: "*", schema, table: lineMetricsTable, filter: filter2 },
|
|
9862
|
+
(payload) => {
|
|
9863
|
+
const payloadData = payload.new || payload.old;
|
|
9864
|
+
if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
|
|
9865
|
+
queueUpdateRef.current?.();
|
|
9866
|
+
}
|
|
9867
|
+
}
|
|
9868
|
+
).subscribe((status, err) => {
|
|
9869
|
+
if (status === supabaseJs.REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === supabaseJs.REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
|
|
9870
|
+
console.error(`[useLineKPIs] Subscription to ${lineMetricsTable} (group ${index}) FAILED:`, status, err);
|
|
9871
|
+
}
|
|
9872
|
+
});
|
|
9873
|
+
activeChannels.push(lmChannel);
|
|
9874
|
+
if (companySpecificMetricsTable && !companySpecificMetricsTable.includes("unknown_company")) {
|
|
9875
|
+
const csChannelName = `kpi-cs-factory-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9876
|
+
const csChannel = supabase.channel(csChannelName).on(
|
|
9877
|
+
"postgres_changes",
|
|
9878
|
+
{ event: "*", schema, table: companySpecificMetricsTable, filter: filter2 },
|
|
9879
|
+
(payload) => {
|
|
9880
|
+
const payloadData = payload.new || payload.old;
|
|
9881
|
+
if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
|
|
9882
|
+
queueUpdateRef.current?.();
|
|
9883
|
+
}
|
|
9884
|
+
}
|
|
9885
|
+
).subscribe((status, err) => {
|
|
9886
|
+
if (status === supabaseJs.REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === supabaseJs.REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
|
|
9887
|
+
console.error(`[useLineKPIs] Subscription to ${companySpecificMetricsTable} (group ${index}) FAILED:`, status, err);
|
|
9888
|
+
}
|
|
9889
|
+
});
|
|
9890
|
+
activeChannels.push(csChannel);
|
|
9251
9891
|
}
|
|
9892
|
+
});
|
|
9893
|
+
} else {
|
|
9894
|
+
const currentShiftDetails = shiftConfig ? getCurrentShift(timezone, shiftConfig) : getCurrentShift(timezone, staticShiftConfig);
|
|
9895
|
+
const operationalDate = currentShiftDetails.date;
|
|
9896
|
+
const targetLineIds = [currentLineId];
|
|
9897
|
+
if (targetLineIds.length === 0) {
|
|
9898
|
+
console.warn("[useLineKPIs] No target line IDs for subscription. LineId:", currentLineId);
|
|
9899
|
+
return;
|
|
9252
9900
|
}
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
}
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
if (companySpecificMetricsTable && !companySpecificMetricsTable.includes("unknown_company")) {
|
|
9260
|
-
const csChannelName = `kpi-cs-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9261
|
-
const csChannel = supabase.channel(csChannelName).on(
|
|
9901
|
+
const baseFilterParts = `date=eq.${operationalDate},shift_id=eq.${currentShiftDetails.shiftId}`;
|
|
9902
|
+
const lineIdFilterPart = `line_id=in.(${targetLineIds.join(",")})`;
|
|
9903
|
+
const lineMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9904
|
+
const companyTableFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9905
|
+
const lmChannelName = `kpi-lm-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9906
|
+
const lmChannel = supabase.channel(lmChannelName).on(
|
|
9262
9907
|
"postgres_changes",
|
|
9263
|
-
{ event: "*", schema, table:
|
|
9908
|
+
{ event: "*", schema, table: lineMetricsTable, filter: lineMetricsFilter },
|
|
9264
9909
|
(payload) => {
|
|
9265
9910
|
const payloadData = payload.new || payload.old;
|
|
9266
9911
|
if (payloadData?.date === operationalDate && payloadData?.shift_id === currentShiftDetails.shiftId) {
|
|
@@ -9269,10 +9914,28 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9269
9914
|
}
|
|
9270
9915
|
).subscribe((status, err) => {
|
|
9271
9916
|
if (status === supabaseJs.REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === supabaseJs.REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
|
|
9272
|
-
console.error(`[useLineKPIs] Subscription to ${
|
|
9917
|
+
console.error(`[useLineKPIs] Subscription to ${lineMetricsTable} FAILED: ${status}`, err || "");
|
|
9273
9918
|
}
|
|
9274
9919
|
});
|
|
9275
|
-
activeChannels.push(
|
|
9920
|
+
activeChannels.push(lmChannel);
|
|
9921
|
+
if (companySpecificMetricsTable && !companySpecificMetricsTable.includes("unknown_company")) {
|
|
9922
|
+
const csChannelName = `kpi-cs-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9923
|
+
const csChannel = supabase.channel(csChannelName).on(
|
|
9924
|
+
"postgres_changes",
|
|
9925
|
+
{ event: "*", schema, table: companySpecificMetricsTable, filter: companyTableFilter },
|
|
9926
|
+
(payload) => {
|
|
9927
|
+
const payloadData = payload.new || payload.old;
|
|
9928
|
+
if (payloadData?.date === operationalDate && payloadData?.shift_id === currentShiftDetails.shiftId) {
|
|
9929
|
+
queueUpdateRef.current?.();
|
|
9930
|
+
}
|
|
9931
|
+
}
|
|
9932
|
+
).subscribe((status, err) => {
|
|
9933
|
+
if (status === supabaseJs.REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === supabaseJs.REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
|
|
9934
|
+
console.error(`[useLineKPIs] Subscription to ${companySpecificMetricsTable} FAILED: ${status}`, err || "");
|
|
9935
|
+
}
|
|
9936
|
+
});
|
|
9937
|
+
activeChannels.push(csChannel);
|
|
9938
|
+
}
|
|
9276
9939
|
}
|
|
9277
9940
|
return () => {
|
|
9278
9941
|
activeChannels.forEach((ch) => supabase.removeChannel(ch).catch((err) => console.error("[useLineKPIs] Error removing KPI channel:", err)));
|
|
@@ -9280,7 +9943,7 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9280
9943
|
clearTimeout(updateTimeoutRef.current);
|
|
9281
9944
|
}
|
|
9282
9945
|
};
|
|
9283
|
-
}, [lineId, supabase, entityConfig, schema, lineMetricsTable, companySpecificMetricsTable, timezone, shiftConfig, isShiftConfigLoading]);
|
|
9946
|
+
}, [lineId, supabase, entityConfig, schema, lineMetricsTable, companySpecificMetricsTable, timezone, shiftConfig, staticShiftConfig, shiftGroups, isShiftConfigLoading]);
|
|
9284
9947
|
return {
|
|
9285
9948
|
kpis,
|
|
9286
9949
|
isLoading,
|
|
@@ -9368,12 +10031,12 @@ var useRealtimeLineMetrics = ({
|
|
|
9368
10031
|
const companyId = entityConfig.companyId;
|
|
9369
10032
|
const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
|
|
9370
10033
|
const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
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
|
+
);
|
|
9377
10040
|
const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
|
|
9378
10041
|
line_id,
|
|
9379
10042
|
workspace_id,
|
|
@@ -9433,8 +10096,8 @@ var useRealtimeLineMetrics = ({
|
|
|
9433
10096
|
const companyId = entityConfig.companyId;
|
|
9434
10097
|
const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
|
|
9435
10098
|
const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
|
|
9436
|
-
const
|
|
9437
|
-
const enabledWorkspaceIds =
|
|
10099
|
+
const enabledWorkspaces = await workspaceService.getEnabledWorkspaces(lineIdRef.current);
|
|
10100
|
+
const enabledWorkspaceIds = enabledWorkspaces.map((ws) => ws.id);
|
|
9438
10101
|
const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
|
|
9439
10102
|
workspace_id,
|
|
9440
10103
|
workspace_name,
|
|
@@ -9554,20 +10217,23 @@ var useRealtimeLineMetrics = ({
|
|
|
9554
10217
|
const companyId = entityConfig.companyId;
|
|
9555
10218
|
const metricsTablePrefix = getMetricsTablePrefix();
|
|
9556
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}`;
|
|
9557
10224
|
const lineMetricsChannel = supabase.channel(`line-metrics-${timestamp}`).on(
|
|
9558
10225
|
"postgres_changes",
|
|
9559
10226
|
{
|
|
9560
10227
|
event: "*",
|
|
9561
10228
|
schema: "public",
|
|
9562
10229
|
table: "line_metrics",
|
|
9563
|
-
filter:
|
|
10230
|
+
filter: filter2
|
|
9564
10231
|
},
|
|
9565
10232
|
async (payload) => {
|
|
9566
10233
|
const payloadData = payload.new;
|
|
9567
10234
|
if (process.env.NODE_ENV === "development") {
|
|
9568
10235
|
console.log("Line metrics update received:", payloadData);
|
|
9569
10236
|
}
|
|
9570
|
-
const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
|
|
9571
10237
|
if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
|
|
9572
10238
|
queueUpdate();
|
|
9573
10239
|
}
|
|
@@ -9583,14 +10249,13 @@ var useRealtimeLineMetrics = ({
|
|
|
9583
10249
|
event: "*",
|
|
9584
10250
|
schema: "public",
|
|
9585
10251
|
table: metricsTable,
|
|
9586
|
-
filter:
|
|
10252
|
+
filter: filter2
|
|
9587
10253
|
},
|
|
9588
10254
|
async (payload) => {
|
|
9589
10255
|
const payloadData = payload.new;
|
|
9590
10256
|
if (process.env.NODE_ENV === "development") {
|
|
9591
10257
|
console.log(`${metricsTablePrefix} update received:`, payloadData);
|
|
9592
10258
|
}
|
|
9593
|
-
const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
|
|
9594
10259
|
if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
|
|
9595
10260
|
queueUpdate();
|
|
9596
10261
|
}
|
|
@@ -9601,7 +10266,7 @@ var useRealtimeLineMetrics = ({
|
|
|
9601
10266
|
}
|
|
9602
10267
|
});
|
|
9603
10268
|
channelsRef.current = [lineMetricsChannel, metricsChannel];
|
|
9604
|
-
}, [supabase, queueUpdate, urlDate, shiftId, entityConfig, dateTimeConfig.defaultTimezone]);
|
|
10269
|
+
}, [supabase, queueUpdate, urlDate, shiftId, entityConfig, timezone, dateTimeConfig.defaultTimezone]);
|
|
9605
10270
|
const prevShiftIdRef = React24.useRef(void 0);
|
|
9606
10271
|
React24.useEffect(() => {
|
|
9607
10272
|
if (!lineId) return;
|
|
@@ -10380,34 +11045,16 @@ var useFactoryOverviewMetrics = (date, shiftId) => {
|
|
|
10380
11045
|
if (lineIds.length === 0) {
|
|
10381
11046
|
throw new Error("No lines configured in entityConfig");
|
|
10382
11047
|
}
|
|
10383
|
-
const { data: { session } } = await supabase.auth.getSession();
|
|
10384
|
-
if (!session?.access_token) {
|
|
10385
|
-
throw new Error("No authentication token available");
|
|
10386
|
-
}
|
|
10387
|
-
const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
10388
|
-
if (!apiUrl) {
|
|
10389
|
-
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
10390
|
-
}
|
|
10391
11048
|
const params = new URLSearchParams({
|
|
10392
11049
|
line_ids: lineIds.join(","),
|
|
10393
11050
|
date: queryDate,
|
|
10394
11051
|
shift_id: queryShiftId.toString(),
|
|
10395
11052
|
company_id: entityConfig.companyId || ""
|
|
10396
11053
|
});
|
|
10397
|
-
const
|
|
10398
|
-
|
|
10399
|
-
{
|
|
10400
|
-
headers: {
|
|
10401
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
10402
|
-
"Content-Type": "application/json"
|
|
10403
|
-
}
|
|
10404
|
-
}
|
|
11054
|
+
const data = await fetchBackendJson(
|
|
11055
|
+
supabase,
|
|
11056
|
+
`/api/dashboard/factory-overview?${params.toString()}`
|
|
10405
11057
|
);
|
|
10406
|
-
if (!response.ok) {
|
|
10407
|
-
const errorText = await response.text();
|
|
10408
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
10409
|
-
}
|
|
10410
|
-
const data = await response.json();
|
|
10411
11058
|
setMetrics(data);
|
|
10412
11059
|
} catch (err) {
|
|
10413
11060
|
console.error("[useFactoryOverviewMetrics] Error fetching factory overview:", err);
|
|
@@ -10436,6 +11083,46 @@ var isInitialized = false;
|
|
|
10436
11083
|
var isInitializing = false;
|
|
10437
11084
|
var initializedWithLineIds = [];
|
|
10438
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
|
+
};
|
|
10439
11126
|
function getCurrentLineIds() {
|
|
10440
11127
|
try {
|
|
10441
11128
|
const config = _getDashboardConfigInstance();
|
|
@@ -10475,15 +11162,20 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
|
|
|
10475
11162
|
console.log("\u{1F504} Target line IDs for workspace filtering:", targetLineIds);
|
|
10476
11163
|
runtimeWorkspaceDisplayNames = {};
|
|
10477
11164
|
if (targetLineIds.length > 0) {
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
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 }) => {
|
|
10481
11173
|
runtimeWorkspaceDisplayNames[lineId] = {};
|
|
10482
11174
|
lineDisplayNamesMap.forEach((displayName, workspaceId) => {
|
|
10483
11175
|
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
10484
11176
|
});
|
|
10485
11177
|
console.log(`\u2705 Stored ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
|
|
10486
|
-
}
|
|
11178
|
+
});
|
|
10487
11179
|
} else {
|
|
10488
11180
|
console.warn("\u26A0\uFE0F No line IDs found, fetching all workspaces (less efficient)");
|
|
10489
11181
|
const allWorkspacesMap = await workspaceService.getWorkspaceDisplayNames();
|
|
@@ -10496,6 +11188,7 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
|
|
|
10496
11188
|
initializedWithLineIds = targetLineIds;
|
|
10497
11189
|
console.log("\u2705 Workspace display names initialized from Supabase:", runtimeWorkspaceDisplayNames);
|
|
10498
11190
|
console.log("\u2705 Initialized with line IDs:", initializedWithLineIds);
|
|
11191
|
+
notifyWorkspaceDisplayNamesListeners(explicitLineId);
|
|
10499
11192
|
} catch (error) {
|
|
10500
11193
|
console.error("\u274C Failed to initialize workspace display names from Supabase:", error);
|
|
10501
11194
|
} finally {
|
|
@@ -10518,6 +11211,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
|
|
|
10518
11211
|
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
10519
11212
|
});
|
|
10520
11213
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
|
|
11214
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
10521
11215
|
} catch (error) {
|
|
10522
11216
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
10523
11217
|
}
|
|
@@ -10536,6 +11230,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
|
|
|
10536
11230
|
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
10537
11231
|
});
|
|
10538
11232
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
|
|
11233
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
10539
11234
|
} catch (error) {
|
|
10540
11235
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
10541
11236
|
}
|
|
@@ -10575,6 +11270,7 @@ var getWorkspaceDisplayName = (workspaceId, lineId) => {
|
|
|
10575
11270
|
runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
|
|
10576
11271
|
});
|
|
10577
11272
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
|
|
11273
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
10578
11274
|
}).catch((error) => {
|
|
10579
11275
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
10580
11276
|
});
|
|
@@ -10615,6 +11311,7 @@ var getShortWorkspaceDisplayName = (workspaceId, lineId) => {
|
|
|
10615
11311
|
runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
|
|
10616
11312
|
});
|
|
10617
11313
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
|
|
11314
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
10618
11315
|
}).catch((error) => {
|
|
10619
11316
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
10620
11317
|
});
|
|
@@ -10656,6 +11353,9 @@ var getAllWorkspaceDisplayNamesAsync = async (companyId, lineId) => {
|
|
|
10656
11353
|
while (isInitializing) {
|
|
10657
11354
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
10658
11355
|
}
|
|
11356
|
+
if (lineId && isInitialized && !runtimeWorkspaceDisplayNames[lineId]) {
|
|
11357
|
+
await preInitializeWorkspaceDisplayNames(lineId);
|
|
11358
|
+
}
|
|
10659
11359
|
if (lineId && runtimeWorkspaceDisplayNames[lineId]) {
|
|
10660
11360
|
return { ...runtimeWorkspaceDisplayNames[lineId] };
|
|
10661
11361
|
}
|
|
@@ -10688,6 +11388,7 @@ var refreshWorkspaceDisplayNames = async (companyId) => {
|
|
|
10688
11388
|
runtimeWorkspaceDisplayNames = {};
|
|
10689
11389
|
isInitialized = false;
|
|
10690
11390
|
await initializeWorkspaceDisplayNames();
|
|
11391
|
+
notifyWorkspaceDisplayNamesListeners();
|
|
10691
11392
|
};
|
|
10692
11393
|
var clearWorkspaceDisplayNamesCache = () => {
|
|
10693
11394
|
workspaceService.clearWorkspaceDisplayNamesCache();
|
|
@@ -10696,6 +11397,7 @@ var clearWorkspaceDisplayNamesCache = () => {
|
|
|
10696
11397
|
isInitializing = false;
|
|
10697
11398
|
initializedWithLineIds = [];
|
|
10698
11399
|
initializationPromise = null;
|
|
11400
|
+
notifyWorkspaceDisplayNamesListeners();
|
|
10699
11401
|
};
|
|
10700
11402
|
|
|
10701
11403
|
// src/lib/hooks/useWorkspaceDisplayNames.ts
|
|
@@ -10718,6 +11420,23 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
|
|
|
10718
11420
|
React24.useEffect(() => {
|
|
10719
11421
|
fetchDisplayNames();
|
|
10720
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]);
|
|
10721
11440
|
return {
|
|
10722
11441
|
displayNames,
|
|
10723
11442
|
loading,
|
|
@@ -10726,7 +11445,7 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
|
|
|
10726
11445
|
};
|
|
10727
11446
|
};
|
|
10728
11447
|
var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
|
|
10729
|
-
const [displayName, setDisplayName] = React24.useState(workspaceId);
|
|
11448
|
+
const [displayName, setDisplayName] = React24.useState(() => getWorkspaceDisplayName(workspaceId, lineId));
|
|
10730
11449
|
const [loading, setLoading] = React24.useState(true);
|
|
10731
11450
|
const [error, setError] = React24.useState(null);
|
|
10732
11451
|
const fetchDisplayName = React24.useCallback(async () => {
|
|
@@ -10745,6 +11464,18 @@ var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
|
|
|
10745
11464
|
React24.useEffect(() => {
|
|
10746
11465
|
fetchDisplayName();
|
|
10747
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]);
|
|
10748
11479
|
return {
|
|
10749
11480
|
displayName,
|
|
10750
11481
|
loading,
|
|
@@ -10775,6 +11506,18 @@ var useWorkspaceDisplayNamesMap = (workspaceIds, lineId, companyId) => {
|
|
|
10775
11506
|
React24.useEffect(() => {
|
|
10776
11507
|
fetchDisplayNames();
|
|
10777
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]);
|
|
10778
11521
|
return {
|
|
10779
11522
|
displayNames,
|
|
10780
11523
|
loading,
|
|
@@ -10953,10 +11696,26 @@ var useAllWorkspaceMetrics = (options) => {
|
|
|
10953
11696
|
const dateTimeConfig = useDateTimeConfig();
|
|
10954
11697
|
const staticShiftConfig = useShiftConfig();
|
|
10955
11698
|
const timezone = useAppTimezone();
|
|
10956
|
-
const configuredLineIds = React24.useMemo(() =>
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
|
|
11699
|
+
const configuredLineIds = React24.useMemo(() => {
|
|
11700
|
+
const allLineIds = getConfiguredLineIds(entityConfig);
|
|
11701
|
+
if (options?.allowedLineIds) {
|
|
11702
|
+
return allLineIds.filter((id3) => options.allowedLineIds.includes(id3));
|
|
11703
|
+
}
|
|
11704
|
+
return allLineIds;
|
|
11705
|
+
}, [entityConfig, options?.allowedLineIds]);
|
|
11706
|
+
const {
|
|
11707
|
+
shiftConfigMap: multiLineShiftConfigMap,
|
|
11708
|
+
isLoading: isShiftConfigLoading
|
|
11709
|
+
} = useMultiLineShiftConfigs(configuredLineIds, staticShiftConfig);
|
|
11710
|
+
const shiftGroups = React24.useMemo(() => {
|
|
11711
|
+
if (isShiftConfigLoading || multiLineShiftConfigMap.size === 0) return [];
|
|
11712
|
+
return groupLinesByShift(multiLineShiftConfigMap, timezone || dateTimeConfig.defaultTimezone || "Asia/Kolkata");
|
|
11713
|
+
}, [isShiftConfigLoading, multiLineShiftConfigMap, timezone, dateTimeConfig.defaultTimezone]);
|
|
11714
|
+
console.log(`[useAllWorkspaceMetrics] \u{1F4CA} Shift groups:`, shiftGroups.map((g) => ({
|
|
11715
|
+
shiftId: g.shiftId,
|
|
11716
|
+
date: g.date,
|
|
11717
|
+
lineIds: g.lineIds
|
|
11718
|
+
})));
|
|
10960
11719
|
const supabase = useSupabase();
|
|
10961
11720
|
const [workspaces, setWorkspaces] = React24.useState([]);
|
|
10962
11721
|
const [loading, setLoading] = React24.useState(true);
|
|
@@ -10964,19 +11723,29 @@ var useAllWorkspaceMetrics = (options) => {
|
|
|
10964
11723
|
const [initialized, setInitialized] = React24.useState(false);
|
|
10965
11724
|
const fetchTimeoutRef = React24.useRef(null);
|
|
10966
11725
|
const isFetchingRef = React24.useRef(false);
|
|
10967
|
-
const
|
|
11726
|
+
const hasSpecificDateShift = options?.initialDate !== void 0 || options?.initialShiftId !== void 0;
|
|
11727
|
+
const fallbackQueryShiftId = React24.useMemo(() => {
|
|
10968
11728
|
if (options?.initialShiftId !== void 0) {
|
|
10969
11729
|
return options.initialShiftId;
|
|
10970
11730
|
}
|
|
11731
|
+
if (shiftGroups.length > 0) {
|
|
11732
|
+
return shiftGroups[0].shiftId;
|
|
11733
|
+
}
|
|
10971
11734
|
const currentShift = getCurrentShift(
|
|
10972
11735
|
timezone || dateTimeConfig.defaultTimezone || "Asia/Kolkata",
|
|
10973
|
-
|
|
11736
|
+
staticShiftConfig
|
|
10974
11737
|
);
|
|
10975
11738
|
return currentShift.shiftId;
|
|
10976
|
-
}, [options?.initialShiftId, timezone, dateTimeConfig.defaultTimezone,
|
|
10977
|
-
const
|
|
10978
|
-
|
|
10979
|
-
|
|
11739
|
+
}, [options?.initialShiftId, shiftGroups, timezone, dateTimeConfig.defaultTimezone, staticShiftConfig]);
|
|
11740
|
+
const fallbackQueryDate = React24.useMemo(() => {
|
|
11741
|
+
if (options?.initialDate) {
|
|
11742
|
+
return options.initialDate;
|
|
11743
|
+
}
|
|
11744
|
+
if (shiftGroups.length > 0) {
|
|
11745
|
+
return shiftGroups[0].date;
|
|
11746
|
+
}
|
|
11747
|
+
return getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
|
|
11748
|
+
}, [options?.initialDate, shiftGroups, timezone, dateTimeConfig.defaultTimezone]);
|
|
10980
11749
|
const metricsTable = React24.useMemo(() => {
|
|
10981
11750
|
const companyId = entityConfig.companyId;
|
|
10982
11751
|
if (!companyId) return "";
|
|
@@ -10994,49 +11763,105 @@ var useAllWorkspaceMetrics = (options) => {
|
|
|
10994
11763
|
}
|
|
10995
11764
|
setError(null);
|
|
10996
11765
|
try {
|
|
10997
|
-
const
|
|
10998
|
-
const
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11766
|
+
const lineWorkspaceMap = /* @__PURE__ */ new Map();
|
|
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 }) => {
|
|
11776
|
+
lineWorkspaceMap.set(lineId, enabledIds);
|
|
11777
|
+
enabledIds.forEach((id3) => allEnabledWorkspaceIdSet.add(id3));
|
|
11778
|
+
});
|
|
11779
|
+
const allEnabledWorkspaceIds = Array.from(allEnabledWorkspaceIdSet);
|
|
11780
|
+
if (allEnabledWorkspaceIds.length === 0) {
|
|
11006
11781
|
setWorkspaces([]);
|
|
11007
11782
|
setInitialized(true);
|
|
11008
11783
|
setLoading(false);
|
|
11009
11784
|
return;
|
|
11010
11785
|
}
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
11786
|
+
let transformedData = [];
|
|
11787
|
+
if (hasSpecificDateShift) {
|
|
11788
|
+
console.log(`[useAllWorkspaceMetrics] \u{1F4CA} Using specific date/shift: ${fallbackQueryDate} / ${fallbackQueryShiftId}`);
|
|
11789
|
+
const { data, error: fetchError } = await supabase.from(metricsTable).select(`
|
|
11790
|
+
workspace_name,
|
|
11791
|
+
total_output,
|
|
11792
|
+
avg_pph,
|
|
11793
|
+
efficiency,
|
|
11794
|
+
workspace_id,
|
|
11795
|
+
avg_cycle_time,
|
|
11796
|
+
performance_score,
|
|
11797
|
+
trend_score,
|
|
11798
|
+
line_id,
|
|
11799
|
+
total_day_output
|
|
11800
|
+
`).eq("date", fallbackQueryDate).eq("shift_id", fallbackQueryShiftId).in("workspace_id", allEnabledWorkspaceIds).order("efficiency", { ascending: false });
|
|
11801
|
+
if (fetchError) throw fetchError;
|
|
11802
|
+
transformedData = (data || []).map((item) => ({
|
|
11803
|
+
company_id: entityConfig.companyId || "unknown",
|
|
11804
|
+
line_id: item.line_id,
|
|
11805
|
+
shift_id: fallbackQueryShiftId,
|
|
11806
|
+
date: fallbackQueryDate,
|
|
11807
|
+
workspace_uuid: item.workspace_id,
|
|
11808
|
+
workspace_name: item.workspace_name,
|
|
11809
|
+
action_count: item.total_output || 0,
|
|
11810
|
+
pph: item.avg_pph || 0,
|
|
11811
|
+
performance_score: item.performance_score || 0,
|
|
11812
|
+
avg_cycle_time: item.avg_cycle_time || 0,
|
|
11813
|
+
trend: item.trend_score === 1 ? 2 : 0,
|
|
11814
|
+
predicted_output: 0,
|
|
11815
|
+
efficiency: item.efficiency || 0,
|
|
11816
|
+
action_threshold: item.total_day_output || 0
|
|
11817
|
+
}));
|
|
11818
|
+
} else if (shiftGroups.length > 0) {
|
|
11819
|
+
console.log(`[useAllWorkspaceMetrics] \u{1F3ED} Fetching per-shift: ${shiftGroups.length} group(s)`);
|
|
11820
|
+
const queryPromises = shiftGroups.map(async (group) => {
|
|
11821
|
+
const groupWorkspaceIds = group.lineIds.flatMap(
|
|
11822
|
+
(lineId) => lineWorkspaceMap.get(lineId) || []
|
|
11823
|
+
);
|
|
11824
|
+
if (groupWorkspaceIds.length === 0) {
|
|
11825
|
+
return [];
|
|
11826
|
+
}
|
|
11827
|
+
console.log(`[useAllWorkspaceMetrics] \u{1F4CA} Fetching shift ${group.shiftId} (${group.shiftName}):`, {
|
|
11828
|
+
lineIds: group.lineIds,
|
|
11829
|
+
date: group.date,
|
|
11830
|
+
workspaceCount: groupWorkspaceIds.length
|
|
11831
|
+
});
|
|
11832
|
+
const { data, error: fetchError } = await supabase.from(metricsTable).select(`
|
|
11833
|
+
workspace_name,
|
|
11834
|
+
total_output,
|
|
11835
|
+
avg_pph,
|
|
11836
|
+
efficiency,
|
|
11837
|
+
workspace_id,
|
|
11838
|
+
avg_cycle_time,
|
|
11839
|
+
performance_score,
|
|
11840
|
+
trend_score,
|
|
11841
|
+
line_id,
|
|
11842
|
+
total_day_output
|
|
11843
|
+
`).eq("date", group.date).eq("shift_id", group.shiftId).in("workspace_id", groupWorkspaceIds);
|
|
11844
|
+
if (fetchError) throw fetchError;
|
|
11845
|
+
return (data || []).map((item) => ({
|
|
11846
|
+
company_id: entityConfig.companyId || "unknown",
|
|
11847
|
+
line_id: item.line_id,
|
|
11848
|
+
shift_id: group.shiftId,
|
|
11849
|
+
date: group.date,
|
|
11850
|
+
workspace_uuid: item.workspace_id,
|
|
11851
|
+
workspace_name: item.workspace_name,
|
|
11852
|
+
action_count: item.total_output || 0,
|
|
11853
|
+
pph: item.avg_pph || 0,
|
|
11854
|
+
performance_score: item.performance_score || 0,
|
|
11855
|
+
avg_cycle_time: item.avg_cycle_time || 0,
|
|
11856
|
+
trend: item.trend_score === 1 ? 2 : 0,
|
|
11857
|
+
predicted_output: 0,
|
|
11858
|
+
efficiency: item.efficiency || 0,
|
|
11859
|
+
action_threshold: item.total_day_output || 0
|
|
11860
|
+
}));
|
|
11861
|
+
});
|
|
11862
|
+
const results = await Promise.all(queryPromises);
|
|
11863
|
+
transformedData = results.flat().sort((a, b) => (b.efficiency || 0) - (a.efficiency || 0));
|
|
11864
|
+
}
|
|
11040
11865
|
setWorkspaces((prevWorkspaces) => {
|
|
11041
11866
|
if (prevWorkspaces.length !== transformedData.length) {
|
|
11042
11867
|
return transformedData;
|
|
@@ -11058,51 +11883,78 @@ var useAllWorkspaceMetrics = (options) => {
|
|
|
11058
11883
|
setLoading(false);
|
|
11059
11884
|
isFetchingRef.current = false;
|
|
11060
11885
|
}
|
|
11061
|
-
}, [
|
|
11886
|
+
}, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, supabase, entityConfig.companyId, configuredLineIds, options?.enabled, isShiftConfigLoading]);
|
|
11062
11887
|
React24.useEffect(() => {
|
|
11063
11888
|
if (!initialized) {
|
|
11064
11889
|
fetchWorkspaceMetrics();
|
|
11065
11890
|
}
|
|
11891
|
+
const validDateShiftCombos = /* @__PURE__ */ new Set();
|
|
11892
|
+
if (hasSpecificDateShift) {
|
|
11893
|
+
validDateShiftCombos.add(`${fallbackQueryDate}-${fallbackQueryShiftId}`);
|
|
11894
|
+
} else {
|
|
11895
|
+
shiftGroups.forEach((group) => {
|
|
11896
|
+
validDateShiftCombos.add(`${group.date}-${group.shiftId}`);
|
|
11897
|
+
});
|
|
11898
|
+
}
|
|
11066
11899
|
const setupSubscription = () => {
|
|
11067
|
-
|
|
11068
|
-
|
|
11900
|
+
if (!metricsTable || configuredLineIds.length === 0) return [];
|
|
11901
|
+
const groupsToSubscribe = hasSpecificDateShift || shiftGroups.length === 0 ? [
|
|
11069
11902
|
{
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
}
|
|
11074
|
-
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11903
|
+
date: fallbackQueryDate,
|
|
11904
|
+
shiftId: fallbackQueryShiftId,
|
|
11905
|
+
lineIds: configuredLineIds
|
|
11906
|
+
}
|
|
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;
|
|
11085
11932
|
}
|
|
11086
|
-
fetchTimeoutRef.current
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
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
|
+
});
|
|
11091
11945
|
};
|
|
11092
|
-
const
|
|
11946
|
+
const channels = setupSubscription();
|
|
11093
11947
|
return () => {
|
|
11094
11948
|
if (fetchTimeoutRef.current) {
|
|
11095
11949
|
clearTimeout(fetchTimeoutRef.current);
|
|
11096
11950
|
fetchTimeoutRef.current = null;
|
|
11097
11951
|
}
|
|
11098
|
-
|
|
11099
|
-
supabase.removeChannel(channel);
|
|
11100
|
-
}
|
|
11952
|
+
channels.forEach((channel) => supabase.removeChannel(channel));
|
|
11101
11953
|
};
|
|
11102
|
-
}, [
|
|
11954
|
+
}, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId, configuredLineIds]);
|
|
11103
11955
|
React24.useEffect(() => {
|
|
11104
11956
|
setInitialized(false);
|
|
11105
|
-
}, [
|
|
11957
|
+
}, [fallbackQueryDate, fallbackQueryShiftId, shiftGroups]);
|
|
11106
11958
|
const refreshWorkspaces = React24.useCallback(() => fetchWorkspaceMetrics(), [fetchWorkspaceMetrics]);
|
|
11107
11959
|
return React24.useMemo(
|
|
11108
11960
|
() => ({ workspaces, loading, error, refreshWorkspaces }),
|
|
@@ -11591,127 +12443,6 @@ function useClipTypesWithCounts(workspaceId, date, shiftId, totalOutput, options
|
|
|
11591
12443
|
counts
|
|
11592
12444
|
};
|
|
11593
12445
|
}
|
|
11594
|
-
var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
|
|
11595
|
-
const [shiftConfigMap, setShiftConfigMap] = React24.useState(/* @__PURE__ */ new Map());
|
|
11596
|
-
const [isLoading, setIsLoading] = React24.useState(true);
|
|
11597
|
-
const [error, setError] = React24.useState(null);
|
|
11598
|
-
const supabase = useSupabase();
|
|
11599
|
-
const lineIdsKey = React24.useMemo(() => lineIds.sort().join(","), [lineIds]);
|
|
11600
|
-
React24.useEffect(() => {
|
|
11601
|
-
if (!lineIds || lineIds.length === 0) {
|
|
11602
|
-
setShiftConfigMap(/* @__PURE__ */ new Map());
|
|
11603
|
-
setIsLoading(false);
|
|
11604
|
-
return;
|
|
11605
|
-
}
|
|
11606
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11607
|
-
const validLineIds = lineIds.filter((id3) => uuidRegex.test(id3));
|
|
11608
|
-
if (validLineIds.length === 0) {
|
|
11609
|
-
console.warn("[useMultiLineShiftConfigs] No valid line IDs provided");
|
|
11610
|
-
setShiftConfigMap(/* @__PURE__ */ new Map());
|
|
11611
|
-
setIsLoading(false);
|
|
11612
|
-
return;
|
|
11613
|
-
}
|
|
11614
|
-
let mounted = true;
|
|
11615
|
-
const calculateBreakDuration2 = (startTime, endTime) => {
|
|
11616
|
-
const [sh, sm] = startTime.split(":").map(Number);
|
|
11617
|
-
const [eh, em] = endTime.split(":").map(Number);
|
|
11618
|
-
let startMinutes = sh * 60 + sm;
|
|
11619
|
-
let endMinutes = eh * 60 + em;
|
|
11620
|
-
if (endMinutes < startMinutes) {
|
|
11621
|
-
endMinutes += 24 * 60;
|
|
11622
|
-
}
|
|
11623
|
-
return endMinutes - startMinutes;
|
|
11624
|
-
};
|
|
11625
|
-
const stripSeconds = (timeStr) => {
|
|
11626
|
-
if (!timeStr) return timeStr;
|
|
11627
|
-
return timeStr.substring(0, 5);
|
|
11628
|
-
};
|
|
11629
|
-
const buildShiftConfigFromRows = (shifts, fallback) => {
|
|
11630
|
-
const mapped = shifts.map((shift) => ({
|
|
11631
|
-
shiftId: shift.shift_id,
|
|
11632
|
-
shiftName: shift.shift_name || `Shift ${shift.shift_id}`,
|
|
11633
|
-
startTime: stripSeconds(shift.start_time),
|
|
11634
|
-
endTime: stripSeconds(shift.end_time),
|
|
11635
|
-
breaks: Array.isArray(shift.breaks) ? shift.breaks.map((b) => ({
|
|
11636
|
-
startTime: b.start || b.startTime || "00:00",
|
|
11637
|
-
endTime: b.end || b.endTime || "00:00",
|
|
11638
|
-
duration: calculateBreakDuration2(
|
|
11639
|
-
b.start || b.startTime || "00:00",
|
|
11640
|
-
b.end || b.endTime || "00:00"
|
|
11641
|
-
),
|
|
11642
|
-
remarks: b.remarks || b.name || ""
|
|
11643
|
-
})) : [],
|
|
11644
|
-
timezone: shift.timezone
|
|
11645
|
-
}));
|
|
11646
|
-
const day = mapped.find((s) => s.shiftId === 0);
|
|
11647
|
-
const night = mapped.find((s) => s.shiftId === 1);
|
|
11648
|
-
return {
|
|
11649
|
-
shifts: mapped,
|
|
11650
|
-
timezone: mapped[0]?.timezone,
|
|
11651
|
-
dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallback?.dayShift,
|
|
11652
|
-
nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallback?.nightShift,
|
|
11653
|
-
transitionPeriodMinutes: fallback?.transitionPeriodMinutes || 0
|
|
11654
|
-
};
|
|
11655
|
-
};
|
|
11656
|
-
const fetchAllConfigs = async () => {
|
|
11657
|
-
try {
|
|
11658
|
-
setIsLoading(true);
|
|
11659
|
-
setError(null);
|
|
11660
|
-
console.log(`[useMultiLineShiftConfigs] Fetching shift configs for ${validLineIds.length} lines`);
|
|
11661
|
-
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", validLineIds);
|
|
11662
|
-
if (fetchError) {
|
|
11663
|
-
console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
|
|
11664
|
-
throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
|
|
11665
|
-
}
|
|
11666
|
-
const lineShiftsMap = /* @__PURE__ */ new Map();
|
|
11667
|
-
data?.forEach((row) => {
|
|
11668
|
-
if (!lineShiftsMap.has(row.line_id)) {
|
|
11669
|
-
lineShiftsMap.set(row.line_id, []);
|
|
11670
|
-
}
|
|
11671
|
-
lineShiftsMap.get(row.line_id).push(row);
|
|
11672
|
-
});
|
|
11673
|
-
const configMap = /* @__PURE__ */ new Map();
|
|
11674
|
-
lineShiftsMap.forEach((shifts, lineId) => {
|
|
11675
|
-
const config = buildShiftConfigFromRows(shifts, fallbackConfig);
|
|
11676
|
-
configMap.set(lineId, config);
|
|
11677
|
-
});
|
|
11678
|
-
validLineIds.forEach((lineId) => {
|
|
11679
|
-
if (!configMap.has(lineId) && fallbackConfig) {
|
|
11680
|
-
console.warn(`[useMultiLineShiftConfigs] No config found for line ${lineId}, using fallback`);
|
|
11681
|
-
configMap.set(lineId, fallbackConfig);
|
|
11682
|
-
}
|
|
11683
|
-
});
|
|
11684
|
-
console.log(`[useMultiLineShiftConfigs] Built configs for ${configMap.size} lines`);
|
|
11685
|
-
if (mounted) {
|
|
11686
|
-
setShiftConfigMap(configMap);
|
|
11687
|
-
setIsLoading(false);
|
|
11688
|
-
}
|
|
11689
|
-
} catch (err) {
|
|
11690
|
-
console.error("[useMultiLineShiftConfigs] Error:", err);
|
|
11691
|
-
if (mounted) {
|
|
11692
|
-
setError(err instanceof Error ? err.message : "Failed to fetch shift configs");
|
|
11693
|
-
if (fallbackConfig) {
|
|
11694
|
-
const fallbackMap = /* @__PURE__ */ new Map();
|
|
11695
|
-
validLineIds.forEach((lineId) => {
|
|
11696
|
-
fallbackMap.set(lineId, fallbackConfig);
|
|
11697
|
-
});
|
|
11698
|
-
setShiftConfigMap(fallbackMap);
|
|
11699
|
-
}
|
|
11700
|
-
setIsLoading(false);
|
|
11701
|
-
}
|
|
11702
|
-
}
|
|
11703
|
-
};
|
|
11704
|
-
fetchAllConfigs();
|
|
11705
|
-
return () => {
|
|
11706
|
-
mounted = false;
|
|
11707
|
-
};
|
|
11708
|
-
}, [lineIdsKey, supabase]);
|
|
11709
|
-
return {
|
|
11710
|
-
shiftConfigMap,
|
|
11711
|
-
isLoading,
|
|
11712
|
-
error
|
|
11713
|
-
};
|
|
11714
|
-
};
|
|
11715
12446
|
var MAX_RETRIES = 10;
|
|
11716
12447
|
var RETRY_DELAY = 500;
|
|
11717
12448
|
function useNavigation(customNavigate) {
|
|
@@ -12658,6 +13389,85 @@ function useLineSupervisor(lineId) {
|
|
|
12658
13389
|
error
|
|
12659
13390
|
};
|
|
12660
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
|
+
};
|
|
12661
13471
|
function useIdleTimeReasons({
|
|
12662
13472
|
workspaceId,
|
|
12663
13473
|
lineId,
|
|
@@ -13063,6 +13873,102 @@ var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig, li
|
|
|
13063
13873
|
return fullName;
|
|
13064
13874
|
};
|
|
13065
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
|
+
|
|
13066
13972
|
// ../../node_modules/clsx/dist/clsx.mjs
|
|
13067
13973
|
function r(e) {
|
|
13068
13974
|
var t, f, n = "";
|
|
@@ -28099,6 +29005,7 @@ if (typeof document !== "undefined") {
|
|
|
28099
29005
|
}
|
|
28100
29006
|
}
|
|
28101
29007
|
var BASE_HLS_CONFIG = {
|
|
29008
|
+
// Keep buffer small to reduce wasted downloads on slow links
|
|
28102
29009
|
maxBufferLength: 3,
|
|
28103
29010
|
maxMaxBufferLength: 8,
|
|
28104
29011
|
maxBufferSize: 50 * 1e3 * 1e3,
|
|
@@ -28106,10 +29013,10 @@ var BASE_HLS_CONFIG = {
|
|
|
28106
29013
|
manifestLoadingTimeOut: 15e3,
|
|
28107
29014
|
manifestLoadingMaxRetry: 3,
|
|
28108
29015
|
manifestLoadingRetryDelay: 500,
|
|
28109
|
-
levelLoadingTimeOut:
|
|
29016
|
+
levelLoadingTimeOut: 2e4,
|
|
28110
29017
|
levelLoadingMaxRetry: 5,
|
|
28111
29018
|
levelLoadingRetryDelay: 500,
|
|
28112
|
-
fragLoadingTimeOut:
|
|
29019
|
+
fragLoadingTimeOut: 2e4,
|
|
28113
29020
|
fragLoadingMaxRetry: 5,
|
|
28114
29021
|
fragLoadingRetryDelay: 500,
|
|
28115
29022
|
startPosition: -1,
|
|
@@ -28122,7 +29029,10 @@ var BASE_HLS_CONFIG = {
|
|
|
28122
29029
|
abrBandWidthFactor: 0.95,
|
|
28123
29030
|
abrBandWidthUpFactor: 0.7,
|
|
28124
29031
|
abrMaxWithRealBitrate: false,
|
|
28125
|
-
|
|
29032
|
+
// Favor a conservative first rendition on constrained networks
|
|
29033
|
+
abrEwmaDefaultEstimate: 1e6,
|
|
29034
|
+
startLevel: 0,
|
|
29035
|
+
capLevelToPlayerSize: true
|
|
28126
29036
|
};
|
|
28127
29037
|
var HlsVideoPlayer = React24.forwardRef(({
|
|
28128
29038
|
src,
|
|
@@ -29206,7 +30116,7 @@ var getSupabaseClient2 = () => {
|
|
|
29206
30116
|
}
|
|
29207
30117
|
return supabaseJs.createClient(url, key);
|
|
29208
30118
|
};
|
|
29209
|
-
var
|
|
30119
|
+
var getAuthToken4 = async () => {
|
|
29210
30120
|
try {
|
|
29211
30121
|
const supabase = getSupabaseClient2();
|
|
29212
30122
|
const { data: { session } } = await supabase.auth.getSession();
|
|
@@ -29229,7 +30139,7 @@ function useWorkspaceCrop(workspaceId) {
|
|
|
29229
30139
|
setIsLoading(true);
|
|
29230
30140
|
setError(null);
|
|
29231
30141
|
try {
|
|
29232
|
-
const token = await
|
|
30142
|
+
const token = await getAuthToken4();
|
|
29233
30143
|
if (!token) {
|
|
29234
30144
|
throw new Error("Authentication required");
|
|
29235
30145
|
}
|
|
@@ -30278,7 +31188,7 @@ var FileManagerFilters = ({
|
|
|
30278
31188
|
const ROOT_CAUSE_OPTIONS = [
|
|
30279
31189
|
"Operator Absent",
|
|
30280
31190
|
"Operator Idle",
|
|
30281
|
-
"Machine
|
|
31191
|
+
"Machine Downtime",
|
|
30282
31192
|
"No Material"
|
|
30283
31193
|
];
|
|
30284
31194
|
const getIdleTimeRootCause = React24.useCallback((clipId) => {
|
|
@@ -30318,7 +31228,7 @@ var FileManagerFilters = ({
|
|
|
30318
31228
|
method: "POST",
|
|
30319
31229
|
headers: {
|
|
30320
31230
|
"Content-Type": "application/json",
|
|
30321
|
-
"Authorization": `Bearer ${await
|
|
31231
|
+
"Authorization": `Bearer ${await getAuthToken5()}`
|
|
30322
31232
|
},
|
|
30323
31233
|
body: JSON.stringify({
|
|
30324
31234
|
action: "clip-metadata",
|
|
@@ -30351,7 +31261,7 @@ var FileManagerFilters = ({
|
|
|
30351
31261
|
});
|
|
30352
31262
|
}
|
|
30353
31263
|
}, [workspaceId, date, shift]);
|
|
30354
|
-
const
|
|
31264
|
+
const getAuthToken5 = async () => {
|
|
30355
31265
|
try {
|
|
30356
31266
|
const { createClient: createClient5 } = await import('@supabase/supabase-js');
|
|
30357
31267
|
const supabase = createClient5(
|
|
@@ -30377,7 +31287,7 @@ var FileManagerFilters = ({
|
|
|
30377
31287
|
method: "POST",
|
|
30378
31288
|
headers: {
|
|
30379
31289
|
"Content-Type": "application/json",
|
|
30380
|
-
"Authorization": `Bearer ${await
|
|
31290
|
+
"Authorization": `Bearer ${await getAuthToken5()}`
|
|
30381
31291
|
},
|
|
30382
31292
|
body: JSON.stringify({
|
|
30383
31293
|
action: "percentile-clips",
|
|
@@ -32021,7 +32931,7 @@ var BottlenecksContent = ({
|
|
|
32021
32931
|
fetchClipCounts();
|
|
32022
32932
|
}
|
|
32023
32933
|
}, [workspaceId, effectiveDateString, effectiveShiftId, s3ClipsService, fetchClipCounts, isEffectiveShiftReady]);
|
|
32024
|
-
const
|
|
32934
|
+
const getAuthToken5 = React24.useCallback(async () => {
|
|
32025
32935
|
try {
|
|
32026
32936
|
const { createClient: createClient5 } = await import('@supabase/supabase-js');
|
|
32027
32937
|
const supabase = createClient5(
|
|
@@ -32040,7 +32950,7 @@ var BottlenecksContent = ({
|
|
|
32040
32950
|
const fetchTriageClips = async () => {
|
|
32041
32951
|
setIsLoadingTriageClips(true);
|
|
32042
32952
|
try {
|
|
32043
|
-
const token = await
|
|
32953
|
+
const token = await getAuthToken5();
|
|
32044
32954
|
if (!token) {
|
|
32045
32955
|
console.error("[BottlenecksContent] No auth token available");
|
|
32046
32956
|
return;
|
|
@@ -32088,7 +32998,7 @@ var BottlenecksContent = ({
|
|
|
32088
32998
|
}
|
|
32089
32999
|
};
|
|
32090
33000
|
fetchTriageClips();
|
|
32091
|
-
}, [triageMode, workspaceId, effectiveDateString, effectiveShiftId,
|
|
33001
|
+
}, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken5, isEffectiveShiftReady]);
|
|
32092
33002
|
React24.useEffect(() => {
|
|
32093
33003
|
if (!triageMode || triageClips.length === 0 || !session?.access_token) {
|
|
32094
33004
|
return;
|
|
@@ -32419,7 +33329,20 @@ var BottlenecksContent = ({
|
|
|
32419
33329
|
updateActiveFilter(categoryId);
|
|
32420
33330
|
}
|
|
32421
33331
|
try {
|
|
32422
|
-
|
|
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;
|
|
32423
33346
|
let metadataArray = categoryMetadataRef.current;
|
|
32424
33347
|
const clipExistsInMetadata = metadataArray.some((clip) => clip.clipId === clipId);
|
|
32425
33348
|
if (metadataArray.length === 0 || !clipExistsInMetadata) {
|
|
@@ -32436,16 +33359,7 @@ var BottlenecksContent = ({
|
|
|
32436
33359
|
}
|
|
32437
33360
|
setCurrentMetadataIndex(clickedClipIndex);
|
|
32438
33361
|
currentMetadataIndexRef.current = clickedClipIndex;
|
|
32439
|
-
|
|
32440
|
-
if (video) {
|
|
32441
|
-
setPendingVideo(video);
|
|
32442
|
-
setCurrentClipId(clipId);
|
|
32443
|
-
setAllVideos([video]);
|
|
32444
|
-
setCurrentIndex(0);
|
|
32445
|
-
console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
|
|
32446
|
-
} else {
|
|
32447
|
-
throw new Error(`Failed to load video data for clip ${clipId}`);
|
|
32448
|
-
}
|
|
33362
|
+
console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
|
|
32449
33363
|
} catch (error2) {
|
|
32450
33364
|
console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
|
|
32451
33365
|
if (isMountedRef.current) {
|
|
@@ -35332,7 +36246,7 @@ var STATIC_COLORS = {
|
|
|
35332
36246
|
// red-600 - Critical/Urgent
|
|
35333
36247
|
"No Material": "#f59e0b",
|
|
35334
36248
|
// amber-500 - Warning/Supply Chain
|
|
35335
|
-
"Machine
|
|
36249
|
+
"Machine Downtime": "#3b82f6",
|
|
35336
36250
|
// blue-500 - Scheduled/Technical
|
|
35337
36251
|
"Operator Idle": "#8b5cf6"
|
|
35338
36252
|
// violet-500 - Low Priority/Behavioral
|
|
@@ -37717,7 +38631,7 @@ var WorkspaceWhatsAppShareButton = ({
|
|
|
37717
38631
|
}
|
|
37718
38632
|
);
|
|
37719
38633
|
};
|
|
37720
|
-
var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
38634
|
+
var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
|
|
37721
38635
|
const [isGenerating, setIsGenerating] = React24.useState(false);
|
|
37722
38636
|
const entityConfig = useEntityConfig();
|
|
37723
38637
|
const generatePDF = async () => {
|
|
@@ -37792,8 +38706,10 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
37792
38706
|
doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
|
|
37793
38707
|
};
|
|
37794
38708
|
const perfOverviewStartY = 93;
|
|
38709
|
+
const hasIdleTimeReason = idleTimeReasons && idleTimeReasons.length > 0;
|
|
38710
|
+
const perfOverviewHeight = hasIdleTimeReason ? 80 : 70;
|
|
37795
38711
|
doc.setFillColor(245, 245, 245);
|
|
37796
|
-
doc.roundedRect(15, perfOverviewStartY, 180,
|
|
38712
|
+
doc.roundedRect(15, perfOverviewStartY, 180, perfOverviewHeight, 3, 3, "F");
|
|
37797
38713
|
doc.setFontSize(18);
|
|
37798
38714
|
doc.setFont("helvetica", "bold");
|
|
37799
38715
|
doc.setTextColor(40, 40, 40);
|
|
@@ -37817,32 +38733,68 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
37817
38733
|
doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 2);
|
|
37818
38734
|
doc.setFont("helvetica", "bold");
|
|
37819
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;
|
|
37820
38753
|
doc.setDrawColor(180, 180, 180);
|
|
37821
38754
|
doc.setLineWidth(0.8);
|
|
37822
|
-
doc.line(20,
|
|
37823
|
-
const hourlyPerfStartY =
|
|
38755
|
+
doc.line(20, separatorBeforeHourlyY, 190, separatorBeforeHourlyY);
|
|
38756
|
+
const hourlyPerfStartY = hasIdleTimeReason ? 188 : 178;
|
|
37824
38757
|
const hourlyData = workspace.hourly_action_counts || [];
|
|
37825
38758
|
const hourlyTarget = workspace.pph_threshold;
|
|
37826
|
-
const
|
|
37827
|
-
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;
|
|
37828
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
|
+
}
|
|
37829
38776
|
const backgroundHeight = tableStartY - hourlyPerfStartY + hourlyData.length * rowHeight + bottomPadding;
|
|
37830
38777
|
doc.setFillColor(245, 245, 245);
|
|
37831
38778
|
doc.roundedRect(15, hourlyPerfStartY, 180, backgroundHeight, 3, 3, "F");
|
|
37832
|
-
doc.setFontSize(
|
|
38779
|
+
doc.setFontSize(titleFontSize);
|
|
37833
38780
|
doc.setFont("helvetica", "bold");
|
|
37834
38781
|
doc.setTextColor(40, 40, 40);
|
|
37835
|
-
|
|
38782
|
+
const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
|
|
38783
|
+
doc.text("Hourly Performance", 20, hourlyTitleY);
|
|
37836
38784
|
doc.setTextColor(0, 0, 0);
|
|
37837
|
-
doc.setFontSize(
|
|
38785
|
+
doc.setFontSize(headerFontSize);
|
|
37838
38786
|
doc.setFont("helvetica", "bold");
|
|
37839
|
-
|
|
37840
|
-
|
|
37841
|
-
doc.text("
|
|
37842
|
-
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);
|
|
37843
38793
|
doc.setLineWidth(0.3);
|
|
37844
38794
|
doc.setDrawColor(200, 200, 200);
|
|
37845
|
-
|
|
38795
|
+
const separatorY = headerY + 3;
|
|
38796
|
+
doc.line(20, separatorY, 190, separatorY);
|
|
38797
|
+
doc.setFontSize(contentFontSize);
|
|
37846
38798
|
doc.setFont("helvetica", "normal");
|
|
37847
38799
|
let yPos = tableStartY;
|
|
37848
38800
|
const workspaceDate = new Date(workspace.date);
|
|
@@ -45266,6 +46218,10 @@ function HomeView({
|
|
|
45266
46218
|
const [diagnosisModalOpen, setDiagnosisModalOpen] = React24.useState(false);
|
|
45267
46219
|
const [diagnoses, setDiagnoses] = React24.useState([]);
|
|
45268
46220
|
const timezone = useAppTimezone();
|
|
46221
|
+
const { shiftConfigMap: lineShiftConfigs } = useMultiLineShiftConfigs(
|
|
46222
|
+
allLineIds,
|
|
46223
|
+
dashboardConfig?.shiftConfig
|
|
46224
|
+
);
|
|
45269
46225
|
React24.useEffect(() => {
|
|
45270
46226
|
const initDisplayNames = async () => {
|
|
45271
46227
|
try {
|
|
@@ -45290,23 +46246,6 @@ function HomeView({
|
|
|
45290
46246
|
loading: displayNamesLoading,
|
|
45291
46247
|
error: displayNamesError
|
|
45292
46248
|
} = useWorkspaceDisplayNames(displayNameLineId, void 0);
|
|
45293
|
-
React24.useCallback(() => {
|
|
45294
|
-
console.log("Refetching KPIs after line metrics update");
|
|
45295
|
-
}, []);
|
|
45296
|
-
const {
|
|
45297
|
-
kpis,
|
|
45298
|
-
isLoading: kpisLoading,
|
|
45299
|
-
error: kpisError,
|
|
45300
|
-
refetch: refetchKPIs
|
|
45301
|
-
} = useLineKPIs({
|
|
45302
|
-
lineId: selectedLineId
|
|
45303
|
-
});
|
|
45304
|
-
const onLineMetricsUpdate = React24.useCallback(() => {
|
|
45305
|
-
const timer = setTimeout(() => {
|
|
45306
|
-
refetchKPIs();
|
|
45307
|
-
}, 1e3);
|
|
45308
|
-
return () => clearTimeout(timer);
|
|
45309
|
-
}, [refetchKPIs]);
|
|
45310
46249
|
const {
|
|
45311
46250
|
workspaceMetrics,
|
|
45312
46251
|
lineMetrics,
|
|
@@ -45315,10 +46254,22 @@ function HomeView({
|
|
|
45315
46254
|
refetch: refetchMetrics
|
|
45316
46255
|
} = useDashboardMetrics({
|
|
45317
46256
|
lineId: selectedLineId,
|
|
45318
|
-
onLineMetricsUpdate,
|
|
45319
46257
|
userAccessibleLineIds: allLineIds
|
|
45320
46258
|
// Pass user's accessible lines for supervisor filtering
|
|
45321
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]);
|
|
45322
46273
|
const {
|
|
45323
46274
|
activeBreaks: allActiveBreaks,
|
|
45324
46275
|
isLoading: breaksLoading,
|
|
@@ -45363,12 +46314,18 @@ function HomeView({
|
|
|
45363
46314
|
label: "Diagnose",
|
|
45364
46315
|
onClick: async () => {
|
|
45365
46316
|
console.log("\u{1F50D} Investigating bottleneck:", bottleneck.log_number);
|
|
45366
|
-
const operationalDate = getOperationalDate(
|
|
45367
|
-
timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC"
|
|
45368
|
-
);
|
|
45369
46317
|
const tz = timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC";
|
|
45370
|
-
const
|
|
46318
|
+
const lineShiftConfig = bottleneck.line_id && lineShiftConfigs.get(bottleneck.line_id);
|
|
46319
|
+
const effectiveShiftConfig = lineShiftConfig || dashboardConfig?.shiftConfig;
|
|
46320
|
+
const shiftResult = getCurrentShift(tz, effectiveShiftConfig);
|
|
45371
46321
|
const currentShift = shiftResult.shiftId;
|
|
46322
|
+
const operationalDate = shiftResult.date;
|
|
46323
|
+
console.log("\u{1F550} Using shift config for line:", {
|
|
46324
|
+
lineId: bottleneck.line_id,
|
|
46325
|
+
hasLineConfig: !!lineShiftConfig,
|
|
46326
|
+
shiftId: currentShift,
|
|
46327
|
+
date: operationalDate
|
|
46328
|
+
});
|
|
45372
46329
|
console.log("\u{1F3AC} [Investigate] Opening clips modal with data:", {
|
|
45373
46330
|
workspaceId: bottleneck.workspace_id,
|
|
45374
46331
|
ticketId: bottleneck.id,
|
|
@@ -45438,12 +46395,18 @@ function HomeView({
|
|
|
45438
46395
|
state,
|
|
45439
46396
|
priority: bottleneck.priority
|
|
45440
46397
|
});
|
|
45441
|
-
const operationalDate = getOperationalDate(
|
|
45442
|
-
timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC"
|
|
45443
|
-
);
|
|
45444
46398
|
const tz = timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC";
|
|
45445
|
-
const
|
|
46399
|
+
const lineShiftConfig = bottleneck.line_id && lineShiftConfigs.get(bottleneck.line_id);
|
|
46400
|
+
const effectiveShiftConfig = lineShiftConfig || dashboardConfig?.shiftConfig;
|
|
46401
|
+
const shiftResult = getCurrentShift(tz, effectiveShiftConfig);
|
|
45446
46402
|
const currentShift = shiftResult.shiftId;
|
|
46403
|
+
const operationalDate = shiftResult.date;
|
|
46404
|
+
console.log("\u{1F550} Using shift config for line:", {
|
|
46405
|
+
lineId: bottleneck.line_id,
|
|
46406
|
+
hasLineConfig: !!lineShiftConfig,
|
|
46407
|
+
shiftId: currentShift,
|
|
46408
|
+
date: operationalDate
|
|
46409
|
+
});
|
|
45447
46410
|
setBottleneckModalData({
|
|
45448
46411
|
workspaceId: bottleneck.workspace_id,
|
|
45449
46412
|
workspaceName: bottleneck_workspace.workspace_name || "Unknown Workspace",
|
|
@@ -45500,7 +46463,7 @@ function HomeView({
|
|
|
45500
46463
|
};
|
|
45501
46464
|
setBottleneckNotification(errorNotification);
|
|
45502
46465
|
}
|
|
45503
|
-
}, [notificationService, timezone, dashboardConfig]);
|
|
46466
|
+
}, [notificationService, timezone, dashboardConfig, lineShiftConfigs]);
|
|
45504
46467
|
React24.useEffect(() => {
|
|
45505
46468
|
const ticketsEnabled = dashboardConfig?.ticketsConfig?.enabled ?? true;
|
|
45506
46469
|
if (!ticketsEnabled) {
|
|
@@ -45625,12 +46588,10 @@ function HomeView({
|
|
|
45625
46588
|
React24.useEffect(() => {
|
|
45626
46589
|
if (metricsError) {
|
|
45627
46590
|
setErrorMessage(metricsError.message);
|
|
45628
|
-
} else if (kpisError) {
|
|
45629
|
-
setErrorMessage(kpisError.message);
|
|
45630
46591
|
} else {
|
|
45631
46592
|
setErrorMessage(null);
|
|
45632
46593
|
}
|
|
45633
|
-
}, [metricsError
|
|
46594
|
+
}, [metricsError]);
|
|
45634
46595
|
const handleLineChange = React24.useCallback((value) => {
|
|
45635
46596
|
setIsChangingFilter(true);
|
|
45636
46597
|
setSelectedLineId(value);
|
|
@@ -45646,14 +46607,14 @@ function HomeView({
|
|
|
45646
46607
|
}
|
|
45647
46608
|
}, [LINE_FILTER_STORAGE_KEY]);
|
|
45648
46609
|
React24.useEffect(() => {
|
|
45649
|
-
if (!metricsLoading &&
|
|
46610
|
+
if (!metricsLoading && isChangingFilter) {
|
|
45650
46611
|
if (workspaceMetrics.length > 0 || selectedLineId === factoryViewId) {
|
|
45651
46612
|
setIsChangingFilter(false);
|
|
45652
46613
|
}
|
|
45653
46614
|
}
|
|
45654
|
-
}, [metricsLoading,
|
|
46615
|
+
}, [metricsLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
|
|
45655
46616
|
React24.useEffect(() => {
|
|
45656
|
-
if (!metricsLoading && !
|
|
46617
|
+
if (!metricsLoading && !hasInitialDataLoaded) {
|
|
45657
46618
|
setHasInitialDataLoaded(true);
|
|
45658
46619
|
trackCoreEvent("Home View Loaded", {
|
|
45659
46620
|
default_line_id: defaultLineId,
|
|
@@ -45661,7 +46622,7 @@ function HomeView({
|
|
|
45661
46622
|
is_supervisor: isSupervisor
|
|
45662
46623
|
});
|
|
45663
46624
|
}
|
|
45664
|
-
}, [metricsLoading,
|
|
46625
|
+
}, [metricsLoading, hasInitialDataLoaded, defaultLineId, factoryViewId, isSupervisor]);
|
|
45665
46626
|
const lineTitle = React24.useMemo(() => {
|
|
45666
46627
|
return factoryName;
|
|
45667
46628
|
}, [factoryName]);
|
|
@@ -45675,7 +46636,7 @@ function HomeView({
|
|
|
45675
46636
|
] });
|
|
45676
46637
|
}, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
|
|
45677
46638
|
const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
|
|
45678
|
-
const isDataLoading = metricsLoading
|
|
46639
|
+
const isDataLoading = metricsLoading;
|
|
45679
46640
|
if (isInitialLoading) {
|
|
45680
46641
|
return /* @__PURE__ */ jsxRuntime.jsx(LoadingPageCmp, { message: "Loading Dashboard..." });
|
|
45681
46642
|
}
|
|
@@ -45819,7 +46780,7 @@ function withWorkspaceDisplayNames(Component3, options = {}) {
|
|
|
45819
46780
|
const lineIdsKey = React24.useMemo(() => {
|
|
45820
46781
|
if (!props.lineIds) return "";
|
|
45821
46782
|
if (Array.isArray(props.lineIds)) {
|
|
45822
|
-
return props.lineIds.sort().join(",");
|
|
46783
|
+
return props.lineIds.slice().sort().join(",");
|
|
45823
46784
|
}
|
|
45824
46785
|
const values = Object.values(props.lineIds).filter(Boolean);
|
|
45825
46786
|
return values.sort().join(",");
|
|
@@ -46942,9 +47903,15 @@ var KPIDetailView = ({
|
|
|
46942
47903
|
};
|
|
46943
47904
|
var KPIDetailViewWithDisplayNames = withSelectedLineDisplayNames(KPIDetailView);
|
|
46944
47905
|
var KPIDetailView_default = KPIDetailViewWithDisplayNames;
|
|
46945
|
-
var LineCard = ({
|
|
46946
|
-
|
|
46947
|
-
|
|
47906
|
+
var LineCard = ({
|
|
47907
|
+
line,
|
|
47908
|
+
kpis,
|
|
47909
|
+
isLoading,
|
|
47910
|
+
error,
|
|
47911
|
+
onClick,
|
|
47912
|
+
supervisorEnabled = false,
|
|
47913
|
+
supervisorName
|
|
47914
|
+
}) => {
|
|
46948
47915
|
const isOnTrack = React24__namespace.default.useMemo(() => {
|
|
46949
47916
|
if (!kpis) return null;
|
|
46950
47917
|
return kpis.efficiency.value > 90;
|
|
@@ -47053,6 +48020,7 @@ var KPIsOverviewView = ({
|
|
|
47053
48020
|
const [error, setError] = React24.useState(null);
|
|
47054
48021
|
const supabase = useSupabase();
|
|
47055
48022
|
const dashboardConfig = useDashboardConfig();
|
|
48023
|
+
const entityConfig = useEntityConfig();
|
|
47056
48024
|
const navigation = useNavigation(navigate);
|
|
47057
48025
|
const dateTimeConfig = useDateTimeConfig();
|
|
47058
48026
|
const representativeLineId = lineIds?.[0] || lines[0]?.id;
|
|
@@ -47060,6 +48028,27 @@ var KPIsOverviewView = ({
|
|
|
47060
48028
|
const supervisorEnabled = dashboardConfig?.supervisorConfig?.enabled || false;
|
|
47061
48029
|
const dbTimezone = useAppTimezone();
|
|
47062
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
|
+
});
|
|
47063
48052
|
React24.useEffect(() => {
|
|
47064
48053
|
trackCorePageView("KPIs Overview");
|
|
47065
48054
|
}, []);
|
|
@@ -47277,8 +48266,12 @@ var KPIsOverviewView = ({
|
|
|
47277
48266
|
LineCard,
|
|
47278
48267
|
{
|
|
47279
48268
|
line,
|
|
48269
|
+
kpis: metricsError ? null : kpisByLineId.get(line.id) ?? (metricsLoading ? null : defaultKPIs),
|
|
48270
|
+
isLoading: metricsLoading,
|
|
48271
|
+
error: metricsError,
|
|
47280
48272
|
onClick: (kpis) => handleLineClick(line, kpis),
|
|
47281
|
-
supervisorEnabled
|
|
48273
|
+
supervisorEnabled,
|
|
48274
|
+
supervisorName: supervisorNamesByLineId.get(line.id) || null
|
|
47282
48275
|
},
|
|
47283
48276
|
line.id
|
|
47284
48277
|
)) }) })
|
|
@@ -47443,6 +48436,7 @@ var LeaderboardDetailView = React24.memo(({
|
|
|
47443
48436
|
const entityConfig = useEntityConfig();
|
|
47444
48437
|
const [sortAscending, setSortAscending] = React24.useState(false);
|
|
47445
48438
|
const timezone = useAppTimezone();
|
|
48439
|
+
const staticShiftConfig = useShiftConfig();
|
|
47446
48440
|
const [isMobile, setIsMobile] = React24.useState(false);
|
|
47447
48441
|
React24__namespace.default.useEffect(() => {
|
|
47448
48442
|
const checkMobile = () => setIsMobile(window.innerWidth < 640);
|
|
@@ -47471,9 +48465,27 @@ var LeaderboardDetailView = React24.memo(({
|
|
|
47471
48465
|
() => typeof shift === "number" ? shift : typeof shift === "string" ? parseInt(shift) : void 0,
|
|
47472
48466
|
[shift]
|
|
47473
48467
|
);
|
|
47474
|
-
const
|
|
47475
|
-
|
|
47476
|
-
|
|
48468
|
+
const configuredLineIds = React24.useMemo(() => {
|
|
48469
|
+
const allLineIds = getConfiguredLineIds(entityConfig);
|
|
48470
|
+
if (userAccessibleLineIds) {
|
|
48471
|
+
return allLineIds.filter((id3) => userAccessibleLineIds.includes(id3));
|
|
48472
|
+
}
|
|
48473
|
+
return allLineIds;
|
|
48474
|
+
}, [entityConfig, userAccessibleLineIds]);
|
|
48475
|
+
const shouldFetchShiftConfigs = !date && shiftId === void 0;
|
|
48476
|
+
const {
|
|
48477
|
+
shiftConfigMap: multiLineShiftConfigMap,
|
|
48478
|
+
isLoading: isShiftConfigLoading
|
|
48479
|
+
} = useMultiLineShiftConfigs(
|
|
48480
|
+
shouldFetchShiftConfigs ? configuredLineIds : [],
|
|
48481
|
+
staticShiftConfig
|
|
48482
|
+
);
|
|
48483
|
+
const shiftGroups = React24.useMemo(() => {
|
|
48484
|
+
if (!shouldFetchShiftConfigs || isShiftConfigLoading || multiLineShiftConfigMap.size === 0) {
|
|
48485
|
+
return [];
|
|
48486
|
+
}
|
|
48487
|
+
return groupLinesByShift(multiLineShiftConfigMap, timezone || "Asia/Kolkata");
|
|
48488
|
+
}, [shouldFetchShiftConfigs, isShiftConfigLoading, multiLineShiftConfigMap, timezone]);
|
|
47477
48489
|
const {
|
|
47478
48490
|
workspaces,
|
|
47479
48491
|
loading: workspacesLoading,
|
|
@@ -47481,21 +48493,28 @@ var LeaderboardDetailView = React24.memo(({
|
|
|
47481
48493
|
refreshWorkspaces
|
|
47482
48494
|
} = useAllWorkspaceMetrics({
|
|
47483
48495
|
initialDate: date,
|
|
47484
|
-
initialShiftId:
|
|
48496
|
+
initialShiftId: shiftId,
|
|
47485
48497
|
allowedLineIds: userAccessibleLineIds,
|
|
47486
|
-
// Filter to user's accessible lines only
|
|
47487
48498
|
enabled: !isShiftConfigLoading
|
|
47488
|
-
// Pass enabled flag
|
|
47489
48499
|
});
|
|
47490
48500
|
const getShiftName = React24.useCallback((shiftId2) => {
|
|
47491
48501
|
if (shiftId2 !== void 0) {
|
|
47492
|
-
return getShiftNameById(shiftId2, timezone || "Asia/Kolkata",
|
|
48502
|
+
return getShiftNameById(shiftId2, timezone || "Asia/Kolkata", staticShiftConfig);
|
|
47493
48503
|
}
|
|
47494
|
-
|
|
47495
|
-
|
|
47496
|
-
|
|
48504
|
+
if (shiftGroups.length > 1) {
|
|
48505
|
+
const shiftNames = shiftGroups.map((g) => g.shiftName).join(" & ");
|
|
48506
|
+
return shiftNames;
|
|
48507
|
+
} else if (shiftGroups.length === 1) {
|
|
48508
|
+
return shiftGroups[0].shiftName;
|
|
48509
|
+
}
|
|
48510
|
+
const currentShift = getCurrentShift(timezone || "Asia/Kolkata", staticShiftConfig);
|
|
48511
|
+
return currentShift.shiftName || getShiftNameById(currentShift.shiftId, timezone || "Asia/Kolkata", staticShiftConfig);
|
|
48512
|
+
}, [timezone, staticShiftConfig, shiftGroups]);
|
|
47497
48513
|
const getShiftIcon = React24.useCallback((shiftId2) => {
|
|
47498
|
-
|
|
48514
|
+
if (shiftId2 === void 0 && shiftGroups.length > 1) {
|
|
48515
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) });
|
|
48516
|
+
}
|
|
48517
|
+
const effectiveShiftId = shiftId2 !== void 0 ? shiftId2 : shiftGroups.length === 1 ? shiftGroups[0].shiftId : getCurrentShift(timezone || "Asia/Kolkata", staticShiftConfig).shiftId;
|
|
47499
48518
|
const shiftNameLower = getShiftName(effectiveShiftId).toLowerCase();
|
|
47500
48519
|
if (shiftNameLower.includes("day") || shiftNameLower.includes("morning")) {
|
|
47501
48520
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", 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" }) });
|
|
@@ -47507,7 +48526,7 @@ var LeaderboardDetailView = React24.memo(({
|
|
|
47507
48526
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", 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" }) });
|
|
47508
48527
|
}
|
|
47509
48528
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) });
|
|
47510
|
-
}, [getShiftName]);
|
|
48529
|
+
}, [getShiftName, shiftGroups, timezone, staticShiftConfig]);
|
|
47511
48530
|
const formatDate2 = React24.useCallback((date2) => {
|
|
47512
48531
|
return new Intl.DateTimeFormat("en-US", {
|
|
47513
48532
|
weekday: "long",
|
|
@@ -48076,7 +49095,7 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
|
|
|
48076
49095
|
const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
|
|
48077
49096
|
return Number(hoursDiff.toFixed(1));
|
|
48078
49097
|
};
|
|
48079
|
-
var
|
|
49098
|
+
var calculateBreakDuration2 = (startTime, endTime) => {
|
|
48080
49099
|
const [startHour, startMinute] = startTime.split(":").map(Number);
|
|
48081
49100
|
const [endHour, endMinute] = endTime.split(":").map(Number);
|
|
48082
49101
|
let startMinutes = startHour * 60 + startMinute;
|
|
@@ -48092,7 +49111,7 @@ var parseBreaksFromDB = (dbBreaks) => {
|
|
|
48092
49111
|
return dbBreaks.map((breakItem) => ({
|
|
48093
49112
|
startTime: breakItem.start || breakItem.startTime || "00:00",
|
|
48094
49113
|
endTime: breakItem.end || breakItem.endTime || "00:00",
|
|
48095
|
-
duration:
|
|
49114
|
+
duration: calculateBreakDuration2(
|
|
48096
49115
|
breakItem.start || breakItem.startTime || "00:00",
|
|
48097
49116
|
breakItem.end || breakItem.endTime || "00:00"
|
|
48098
49117
|
),
|
|
@@ -48102,7 +49121,7 @@ var parseBreaksFromDB = (dbBreaks) => {
|
|
|
48102
49121
|
return dbBreaks.breaks.map((breakItem) => ({
|
|
48103
49122
|
startTime: breakItem.start || breakItem.startTime || "00:00",
|
|
48104
49123
|
endTime: breakItem.end || breakItem.endTime || "00:00",
|
|
48105
|
-
duration:
|
|
49124
|
+
duration: calculateBreakDuration2(
|
|
48106
49125
|
breakItem.start || breakItem.startTime || "00:00",
|
|
48107
49126
|
breakItem.end || breakItem.endTime || "00:00"
|
|
48108
49127
|
),
|
|
@@ -48739,6 +49758,13 @@ var ShiftsView = ({
|
|
|
48739
49758
|
if (nightResult.error) {
|
|
48740
49759
|
throw new Error(`Failed to save night shift: ${nightResult.error.message}`);
|
|
48741
49760
|
}
|
|
49761
|
+
const updatedRows = [
|
|
49762
|
+
...dayResult.data || [],
|
|
49763
|
+
...nightResult.data || []
|
|
49764
|
+
];
|
|
49765
|
+
if (updatedRows.length > 0) {
|
|
49766
|
+
shiftConfigStore.setFromOperatingHoursRows(lineId, updatedRows, shiftConfigStore.get(lineId));
|
|
49767
|
+
}
|
|
48742
49768
|
setLineConfigs((prev) => prev.map(
|
|
48743
49769
|
(config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
|
|
48744
49770
|
));
|
|
@@ -49708,6 +50734,7 @@ var TargetsViewUI = ({
|
|
|
49708
50734
|
onUpdateSelectedSKU,
|
|
49709
50735
|
skuRequired = false
|
|
49710
50736
|
}) => {
|
|
50737
|
+
const { displayNames: workspaceDisplayNames } = useWorkspaceDisplayNames();
|
|
49711
50738
|
if (isLoading) {
|
|
49712
50739
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading targets..." }) }) });
|
|
49713
50740
|
}
|
|
@@ -49855,7 +50882,7 @@ var TargetsViewUI = ({
|
|
|
49855
50882
|
] })
|
|
49856
50883
|
] }) }),
|
|
49857
50884
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-gray-100", children: line.workspaces.map((workspace) => {
|
|
49858
|
-
const formattedName = formatWorkspaceName(workspace.name, lineId);
|
|
50885
|
+
const formattedName = workspaceDisplayNames[`${lineId}_${workspace.name}`] || formatWorkspaceName(workspace.name, lineId);
|
|
49859
50886
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49860
50887
|
"div",
|
|
49861
50888
|
{
|
|
@@ -50579,8 +51606,17 @@ var TargetsView = ({
|
|
|
50579
51606
|
};
|
|
50580
51607
|
const handleUpdateWorkspaceDisplayName = React24.useCallback(async (workspaceId, displayName) => {
|
|
50581
51608
|
try {
|
|
50582
|
-
await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
50583
|
-
|
|
51609
|
+
const updated = await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
51610
|
+
if (updated?.line_id && updated?.workspace_id) {
|
|
51611
|
+
upsertWorkspaceDisplayNameInCache({
|
|
51612
|
+
lineId: updated.line_id,
|
|
51613
|
+
workspaceId: updated.workspace_id,
|
|
51614
|
+
displayName: updated?.display_name || displayName,
|
|
51615
|
+
enabled: updated?.enable
|
|
51616
|
+
});
|
|
51617
|
+
} else {
|
|
51618
|
+
await forceRefreshWorkspaceDisplayNames();
|
|
51619
|
+
}
|
|
50584
51620
|
sonner.toast.success("Workspace name updated successfully");
|
|
50585
51621
|
} catch (error) {
|
|
50586
51622
|
console.error("Error updating workspace display name:", error);
|
|
@@ -51259,7 +52295,13 @@ var WorkspaceDetailView = ({
|
|
|
51259
52295
|
}
|
|
51260
52296
|
)
|
|
51261
52297
|
] }),
|
|
51262
|
-
activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
52298
|
+
activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
52299
|
+
WorkspacePdfGenerator,
|
|
52300
|
+
{
|
|
52301
|
+
workspace,
|
|
52302
|
+
idleTimeReasons: idleTimeChartData
|
|
52303
|
+
}
|
|
52304
|
+
) }),
|
|
51263
52305
|
activeTab === "monthly_history" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
51264
52306
|
WorkspaceMonthlyPdfGenerator,
|
|
51265
52307
|
{
|
|
@@ -52314,6 +53356,7 @@ var WorkspaceHealthView = ({
|
|
|
52314
53356
|
const [selectedWorkspace, setSelectedWorkspace] = React24.useState(null);
|
|
52315
53357
|
const [selectedDate, setSelectedDate] = React24.useState(void 0);
|
|
52316
53358
|
const [selectedShiftId, setSelectedShiftId] = React24.useState(void 0);
|
|
53359
|
+
const [showDatePicker, setShowDatePicker] = React24.useState(false);
|
|
52317
53360
|
const effectiveTimezone = timezone || "UTC";
|
|
52318
53361
|
const currentShiftDetails = getCurrentShift(effectiveTimezone, shiftConfig);
|
|
52319
53362
|
const operationalDate = currentShiftDetails.date;
|
|
@@ -52362,28 +53405,6 @@ var WorkspaceHealthView = ({
|
|
|
52362
53405
|
const handleCloseDetails = React24.useCallback(() => {
|
|
52363
53406
|
setSelectedWorkspace(null);
|
|
52364
53407
|
}, []);
|
|
52365
|
-
const handleExport = React24.useCallback(() => {
|
|
52366
|
-
const csv = [
|
|
52367
|
-
["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
|
|
52368
|
-
...workspaces.map((w) => [
|
|
52369
|
-
w.workspace_display_name || "",
|
|
52370
|
-
w.line_name || "",
|
|
52371
|
-
w.company_name || "",
|
|
52372
|
-
w.status,
|
|
52373
|
-
w.last_heartbeat,
|
|
52374
|
-
w.consecutive_misses?.toString() || "0"
|
|
52375
|
-
])
|
|
52376
|
-
].map((row) => row.join(",")).join("\n");
|
|
52377
|
-
const blob = new Blob([csv], { type: "text/csv" });
|
|
52378
|
-
const url = window.URL.createObjectURL(blob);
|
|
52379
|
-
const a = document.createElement("a");
|
|
52380
|
-
a.href = url;
|
|
52381
|
-
a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
|
|
52382
|
-
document.body.appendChild(a);
|
|
52383
|
-
a.click();
|
|
52384
|
-
document.body.removeChild(a);
|
|
52385
|
-
window.URL.revokeObjectURL(url);
|
|
52386
|
-
}, [workspaces]);
|
|
52387
53408
|
const getStatusIcon = (status) => {
|
|
52388
53409
|
switch (status) {
|
|
52389
53410
|
case "healthy":
|
|
@@ -52421,104 +53442,42 @@ var WorkspaceHealthView = ({
|
|
|
52421
53442
|
}
|
|
52422
53443
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
52423
53444
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
|
|
52424
|
-
/* @__PURE__ */ jsxRuntime.
|
|
52425
|
-
/* @__PURE__ */ jsxRuntime.
|
|
52426
|
-
|
|
52427
|
-
|
|
52428
|
-
|
|
52429
|
-
|
|
52430
|
-
|
|
52431
|
-
|
|
52432
|
-
|
|
52433
|
-
|
|
52434
|
-
|
|
52435
|
-
|
|
52436
|
-
|
|
52437
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52438
|
-
"button",
|
|
52439
|
-
{
|
|
52440
|
-
onClick: () => {
|
|
52441
|
-
refetch();
|
|
52442
|
-
},
|
|
52443
|
-
className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52444
|
-
"aria-label": "Refresh",
|
|
52445
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4" })
|
|
52446
|
-
}
|
|
52447
|
-
),
|
|
52448
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52449
|
-
"button",
|
|
52450
|
-
{
|
|
52451
|
-
onClick: handleExport,
|
|
52452
|
-
className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52453
|
-
"aria-label": "Export CSV",
|
|
52454
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" })
|
|
52455
|
-
}
|
|
52456
|
-
)
|
|
52457
|
-
] })
|
|
52458
|
-
] }),
|
|
52459
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2", children: [
|
|
52460
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-gray-900", children: "System Health" }),
|
|
52461
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2 w-2", children: [
|
|
52462
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
|
|
52463
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-emerald-500" })
|
|
52464
|
-
] })
|
|
52465
|
-
] }) })
|
|
53445
|
+
/* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
53446
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
53447
|
+
BackButtonMinimal,
|
|
53448
|
+
{
|
|
53449
|
+
onClick: () => router$1.push("/"),
|
|
53450
|
+
text: "Back",
|
|
53451
|
+
size: "default",
|
|
53452
|
+
"aria-label": "Navigate back to dashboard"
|
|
53453
|
+
}
|
|
53454
|
+
) }),
|
|
53455
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
|
|
53456
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "System Health" }),
|
|
53457
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center", children: "Monitor workspace connectivity and operational status" })
|
|
52466
53458
|
] }),
|
|
52467
|
-
/* @__PURE__ */ jsxRuntime.
|
|
52468
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52469
|
-
|
|
53459
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
53460
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53461
|
+
"button",
|
|
52470
53462
|
{
|
|
52471
|
-
onClick: () =>
|
|
52472
|
-
text: "
|
|
52473
|
-
|
|
52474
|
-
|
|
53463
|
+
onClick: () => setShowDatePicker(!showDatePicker),
|
|
53464
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
53465
|
+
"aria-label": "Select date",
|
|
53466
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "h-5 w-5" })
|
|
52475
53467
|
}
|
|
52476
|
-
)
|
|
52477
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52478
|
-
|
|
52479
|
-
|
|
52480
|
-
|
|
52481
|
-
|
|
52482
|
-
|
|
52483
|
-
|
|
52484
|
-
|
|
52485
|
-
|
|
52486
|
-
|
|
52487
|
-
|
|
52488
|
-
|
|
52489
|
-
refetch();
|
|
52490
|
-
},
|
|
52491
|
-
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52492
|
-
"aria-label": "Refresh",
|
|
52493
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
|
|
52494
|
-
}
|
|
52495
|
-
),
|
|
52496
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52497
|
-
"button",
|
|
52498
|
-
{
|
|
52499
|
-
onClick: handleExport,
|
|
52500
|
-
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52501
|
-
"aria-label": "Export CSV",
|
|
52502
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
|
|
52503
|
-
}
|
|
52504
|
-
)
|
|
52505
|
-
] }),
|
|
52506
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
|
|
52507
|
-
] }) }),
|
|
52508
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 sm:mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
52509
|
-
HealthDateShiftSelector,
|
|
52510
|
-
{
|
|
52511
|
-
selectedDate: selectedDate || operationalDate,
|
|
52512
|
-
selectedShiftId: selectedShiftId ?? currentShiftDetails.shiftId,
|
|
52513
|
-
shiftConfig,
|
|
52514
|
-
timezone: effectiveTimezone,
|
|
52515
|
-
onDateChange: setSelectedDate,
|
|
52516
|
-
onShiftChange: setSelectedShiftId,
|
|
52517
|
-
isCurrentShift: isViewingCurrentShift,
|
|
52518
|
-
onReturnToLive: handleReturnToLive
|
|
52519
|
-
}
|
|
52520
|
-
) })
|
|
52521
|
-
] }),
|
|
53468
|
+
),
|
|
53469
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53470
|
+
"button",
|
|
53471
|
+
{
|
|
53472
|
+
onClick: () => refetch(),
|
|
53473
|
+
disabled: loading,
|
|
53474
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
53475
|
+
"aria-label": "Refresh",
|
|
53476
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-5 w-5 ${loading ? "animate-spin" : ""}` })
|
|
53477
|
+
}
|
|
53478
|
+
)
|
|
53479
|
+
] })
|
|
53480
|
+
] }) }) }),
|
|
52522
53481
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
52523
53482
|
summary && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
52524
53483
|
motion.div,
|
|
@@ -52594,6 +53553,72 @@ var WorkspaceHealthView = ({
|
|
|
52594
53553
|
)
|
|
52595
53554
|
] })
|
|
52596
53555
|
] }),
|
|
53556
|
+
showDatePicker && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
53557
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53558
|
+
"div",
|
|
53559
|
+
{
|
|
53560
|
+
className: "fixed inset-0 bg-black/20 z-40",
|
|
53561
|
+
onClick: () => setShowDatePicker(false)
|
|
53562
|
+
}
|
|
53563
|
+
),
|
|
53564
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed top-20 right-4 md:right-8 z-50 bg-white rounded-lg shadow-xl border border-gray-200 p-4 w-80", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
53565
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
53566
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Date" }),
|
|
53567
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53568
|
+
"input",
|
|
53569
|
+
{
|
|
53570
|
+
type: "date",
|
|
53571
|
+
value: selectedDate || operationalDate,
|
|
53572
|
+
max: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
53573
|
+
onChange: (e) => {
|
|
53574
|
+
setSelectedDate(e.target.value);
|
|
53575
|
+
},
|
|
53576
|
+
className: "w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
53577
|
+
}
|
|
53578
|
+
)
|
|
53579
|
+
] }),
|
|
53580
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
53581
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Shift" }),
|
|
53582
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53583
|
+
"select",
|
|
53584
|
+
{
|
|
53585
|
+
value: selectedShiftId ?? currentShiftDetails.shiftId,
|
|
53586
|
+
onChange: (e) => setSelectedShiftId(Number(e.target.value)),
|
|
53587
|
+
className: "w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white",
|
|
53588
|
+
children: (shiftConfig?.shifts || []).map((shift) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: shift.shiftId, children: [
|
|
53589
|
+
shift.shiftName,
|
|
53590
|
+
" (",
|
|
53591
|
+
shift.startTime,
|
|
53592
|
+
" - ",
|
|
53593
|
+
shift.endTime,
|
|
53594
|
+
")"
|
|
53595
|
+
] }, shift.shiftId))
|
|
53596
|
+
}
|
|
53597
|
+
)
|
|
53598
|
+
] }),
|
|
53599
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-2", children: [
|
|
53600
|
+
!isViewingCurrentShift && /* @__PURE__ */ jsxRuntime.jsx(
|
|
53601
|
+
"button",
|
|
53602
|
+
{
|
|
53603
|
+
onClick: () => {
|
|
53604
|
+
handleReturnToLive();
|
|
53605
|
+
setShowDatePicker(false);
|
|
53606
|
+
},
|
|
53607
|
+
className: "flex-1 px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors",
|
|
53608
|
+
children: "Return to Live"
|
|
53609
|
+
}
|
|
53610
|
+
),
|
|
53611
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
53612
|
+
"button",
|
|
53613
|
+
{
|
|
53614
|
+
onClick: () => setShowDatePicker(false),
|
|
53615
|
+
className: "flex-1 px-3 py-2 text-sm text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors",
|
|
53616
|
+
children: "Done"
|
|
53617
|
+
}
|
|
53618
|
+
)
|
|
53619
|
+
] })
|
|
53620
|
+
] }) })
|
|
53621
|
+
] }),
|
|
52597
53622
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
52598
53623
|
WorkspaceUptimeDetailModal_default,
|
|
52599
53624
|
{
|
|
@@ -53743,7 +54768,7 @@ function DailyBarChart({
|
|
|
53743
54768
|
axisLine: false,
|
|
53744
54769
|
tick: (props) => {
|
|
53745
54770
|
const { x, y, payload } = props;
|
|
53746
|
-
if (payload.value === 0) return
|
|
54771
|
+
if (payload.value === 0) return /* @__PURE__ */ jsxRuntime.jsx("g", {});
|
|
53747
54772
|
const hours = Math.round(payload.value / (1e3 * 60 * 60) * 10) / 10;
|
|
53748
54773
|
return /* @__PURE__ */ jsxRuntime.jsxs("text", { x, y, dy: 4, textAnchor: "end", className: "text-xs fill-gray-400", children: [
|
|
53749
54774
|
hours,
|
|
@@ -54098,7 +55123,7 @@ var UserManagementTable = ({
|
|
|
54098
55123
|
}
|
|
54099
55124
|
),
|
|
54100
55125
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Assignments" }),
|
|
54101
|
-
showUsageStats && /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "
|
|
55126
|
+
showUsageStats && /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Average Daily usage" }),
|
|
54102
55127
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Actions" })
|
|
54103
55128
|
] }) }),
|
|
54104
55129
|
/* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: filteredAndSortedUsers.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: showUsageStats ? 5 : 4, className: "px-6 py-12", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center text-gray-400 gap-2", children: [
|
|
@@ -54110,12 +55135,25 @@ var UserManagementTable = ({
|
|
|
54110
55135
|
const canChangeRole = permissions.canChangeRole(user);
|
|
54111
55136
|
const canRemove = permissions.canRemoveUser(user);
|
|
54112
55137
|
const hasActions = canChangeRole || canRemove;
|
|
55138
|
+
const canShowUsageModal = showUsageStats && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55139
|
+
const handleRowClick = (e) => {
|
|
55140
|
+
const target = e.target;
|
|
55141
|
+
if (target.closest("button") || target.closest("select") || target.closest("input") || target.closest('[role="button"]') || target.closest("[data-interactive]")) {
|
|
55142
|
+
return;
|
|
55143
|
+
}
|
|
55144
|
+
if (canShowUsageModal) {
|
|
55145
|
+
setUsageDetailUserId(user.user_id);
|
|
55146
|
+
setShowUsageDetailModal(true);
|
|
55147
|
+
}
|
|
55148
|
+
};
|
|
54113
55149
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
54114
55150
|
"tr",
|
|
54115
55151
|
{
|
|
55152
|
+
onClick: handleRowClick,
|
|
54116
55153
|
className: cn(
|
|
54117
|
-
"
|
|
54118
|
-
isDeactivated && "opacity-60"
|
|
55154
|
+
"transition-all duration-150",
|
|
55155
|
+
isDeactivated && "opacity-60",
|
|
55156
|
+
canShowUsageModal ? "cursor-pointer hover:bg-blue-50/50 hover:shadow-sm" : "hover:bg-gray-50"
|
|
54119
55157
|
),
|
|
54120
55158
|
children: [
|
|
54121
55159
|
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
@@ -54181,10 +55219,7 @@ var UserManagementTable = ({
|
|
|
54181
55219
|
setShowUsageDetailModal(true);
|
|
54182
55220
|
},
|
|
54183
55221
|
className: "group flex items-center gap-3 hover:bg-gray-50 -mx-2 px-2 py-1.5 rounded-lg transition-all duration-200",
|
|
54184
|
-
children: isUsageLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse bg-gray-200 h-5 w-20 rounded" }) : /* @__PURE__ */ jsxRuntime.
|
|
54185
|
-
/* @__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) }),
|
|
54186
|
-
/* @__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" })
|
|
54187
|
-
] })
|
|
55222
|
+
children: isUsageLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse bg-gray-200 h-5 w-20 rounded" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-base font-semibold ${avgDailyUsage?.[user.user_id] && avgDailyUsage[user.user_id] > 0 ? "text-gray-900 group-hover:text-blue-600" : "text-gray-400"} transition-colors`, children: formatDuration3(avgDailyUsage?.[user.user_id] || 0) })
|
|
54188
55223
|
}
|
|
54189
55224
|
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-400", children: "-" }) }),
|
|
54190
55225
|
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right", children: hasActions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -54232,25 +55267,50 @@ var UserManagementTable = ({
|
|
|
54232
55267
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-1", children: [
|
|
54233
55268
|
(() => {
|
|
54234
55269
|
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
54235
|
-
const
|
|
54236
|
-
|
|
54237
|
-
return canChangeRole && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
55270
|
+
const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55271
|
+
return canShowUsage && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
54238
55272
|
"button",
|
|
54239
55273
|
{
|
|
54240
55274
|
onClick: () => {
|
|
54241
55275
|
if (user) {
|
|
54242
|
-
|
|
55276
|
+
setUsageDetailUserId(user.user_id);
|
|
55277
|
+
setShowUsageDetailModal(true);
|
|
55278
|
+
handleCloseActionMenu();
|
|
54243
55279
|
}
|
|
54244
55280
|
},
|
|
54245
|
-
className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2
|
|
54246
|
-
disabled: isCurrentUser,
|
|
55281
|
+
className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
|
|
54247
55282
|
children: [
|
|
54248
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
54249
|
-
"
|
|
55283
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.BarChart3, { className: "w-4 h-4" }),
|
|
55284
|
+
"View Detailed Usage"
|
|
54250
55285
|
]
|
|
54251
55286
|
}
|
|
54252
55287
|
);
|
|
54253
55288
|
})(),
|
|
55289
|
+
(() => {
|
|
55290
|
+
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
55291
|
+
const isCurrentUser = user?.user_id === currentUserId;
|
|
55292
|
+
const canChangeRole = user && permissions.canChangeRole(user);
|
|
55293
|
+
const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55294
|
+
return canChangeRole && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
55295
|
+
canShowUsage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-200 my-1" }),
|
|
55296
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
55297
|
+
"button",
|
|
55298
|
+
{
|
|
55299
|
+
onClick: () => {
|
|
55300
|
+
if (user) {
|
|
55301
|
+
handleChangeRole(user);
|
|
55302
|
+
}
|
|
55303
|
+
},
|
|
55304
|
+
className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
|
|
55305
|
+
disabled: isCurrentUser,
|
|
55306
|
+
children: [
|
|
55307
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserCog, { className: "w-4 h-4" }),
|
|
55308
|
+
"Change Role"
|
|
55309
|
+
]
|
|
55310
|
+
}
|
|
55311
|
+
)
|
|
55312
|
+
] });
|
|
55313
|
+
})(),
|
|
54254
55314
|
(() => {
|
|
54255
55315
|
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
54256
55316
|
const isCurrentUser = user?.user_id === currentUserId;
|
|
@@ -54792,7 +55852,7 @@ var TeamManagementView = ({
|
|
|
54792
55852
|
}, {});
|
|
54793
55853
|
}, [usageData, usageDateRange.daysElapsed]);
|
|
54794
55854
|
const pageTitle = user?.role_level === "optifye" ? "Platform Users" : "Team Management";
|
|
54795
|
-
const pageDescription = user?.role_level === "optifye" ? "Manage users across all companies" : user?.role_level === "owner" ? "Manage
|
|
55855
|
+
const pageDescription = user?.role_level === "optifye" ? "Manage users across all companies" : user?.role_level === "owner" ? "Manage team members and view their daily usage" : "Manage supervisors in your factory";
|
|
54796
55856
|
const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "plant_head"].includes(user.role_level) : false;
|
|
54797
55857
|
const loadData = React24.useCallback(async () => {
|
|
54798
55858
|
if (!supabase) {
|
|
@@ -56844,10 +57904,13 @@ exports.WorkspacePdfExportButton = WorkspacePdfExportButton;
|
|
|
56844
57904
|
exports.WorkspacePdfGenerator = WorkspacePdfGenerator;
|
|
56845
57905
|
exports.WorkspaceWhatsAppShareButton = WorkspaceWhatsAppShareButton;
|
|
56846
57906
|
exports.actionService = actionService;
|
|
57907
|
+
exports.aggregateKPIsFromLineMetricsRows = aggregateKPIsFromLineMetricsRows;
|
|
56847
57908
|
exports.apiUtils = apiUtils;
|
|
57909
|
+
exports.areAllLinesOnSameShift = areAllLinesOnSameShift;
|
|
56848
57910
|
exports.authCoreService = authCoreService;
|
|
56849
57911
|
exports.authOTPService = authOTPService;
|
|
56850
57912
|
exports.authRateLimitService = authRateLimitService;
|
|
57913
|
+
exports.buildKPIsFromLineMetricsRow = buildKPIsFromLineMetricsRow;
|
|
56851
57914
|
exports.checkRateLimit = checkRateLimit2;
|
|
56852
57915
|
exports.clearAllRateLimits = clearAllRateLimits2;
|
|
56853
57916
|
exports.clearRateLimit = clearRateLimit2;
|
|
@@ -56855,6 +57918,7 @@ exports.clearS3VideoCache = clearS3VideoCache;
|
|
|
56855
57918
|
exports.clearS3VideoFromCache = clearS3VideoFromCache;
|
|
56856
57919
|
exports.clearWorkspaceDisplayNamesCache = clearWorkspaceDisplayNamesCache;
|
|
56857
57920
|
exports.cn = cn;
|
|
57921
|
+
exports.createDefaultKPIs = createDefaultKPIs;
|
|
56858
57922
|
exports.createInvitationService = createInvitationService;
|
|
56859
57923
|
exports.createLinesService = createLinesService;
|
|
56860
57924
|
exports.createSessionTracker = createSessionTracker;
|
|
@@ -56879,6 +57943,7 @@ exports.fromUrlFriendlyName = fromUrlFriendlyName;
|
|
|
56879
57943
|
exports.getAllLineDisplayNames = getAllLineDisplayNames;
|
|
56880
57944
|
exports.getAllThreadMessages = getAllThreadMessages;
|
|
56881
57945
|
exports.getAllWorkspaceDisplayNamesAsync = getAllWorkspaceDisplayNamesAsync;
|
|
57946
|
+
exports.getAllWorkspaceDisplayNamesSnapshot = getAllWorkspaceDisplayNamesSnapshot;
|
|
56882
57947
|
exports.getAnonClient = getAnonClient;
|
|
56883
57948
|
exports.getAvailableShiftIds = getAvailableShiftIds;
|
|
56884
57949
|
exports.getBrowserName = getBrowserName;
|
|
@@ -56890,6 +57955,7 @@ exports.getConfiguredLineIds = getConfiguredLineIds;
|
|
|
56890
57955
|
exports.getCoreSessionRecordingProperties = getCoreSessionRecordingProperties;
|
|
56891
57956
|
exports.getCoreSessionReplayUrl = getCoreSessionReplayUrl;
|
|
56892
57957
|
exports.getCurrentShift = getCurrentShift;
|
|
57958
|
+
exports.getCurrentShiftForLine = getCurrentShiftForLine;
|
|
56893
57959
|
exports.getCurrentTimeInZone = getCurrentTimeInZone;
|
|
56894
57960
|
exports.getDashboardHeaderTimeInZone = getDashboardHeaderTimeInZone;
|
|
56895
57961
|
exports.getDaysDifferenceInZone = getDaysDifferenceInZone;
|
|
@@ -56912,6 +57978,7 @@ exports.getShortWorkspaceDisplayNameAsync = getShortWorkspaceDisplayNameAsync;
|
|
|
56912
57978
|
exports.getStoredWorkspaceMappings = getStoredWorkspaceMappings;
|
|
56913
57979
|
exports.getSubscriptionManager = getSubscriptionManager;
|
|
56914
57980
|
exports.getThreadMessages = getThreadMessages;
|
|
57981
|
+
exports.getUniformShiftGroup = getUniformShiftGroup;
|
|
56915
57982
|
exports.getUserThreads = getUserThreads;
|
|
56916
57983
|
exports.getUserThreadsPaginated = getUserThreadsPaginated;
|
|
56917
57984
|
exports.getWorkspaceDisplayName = getWorkspaceDisplayName;
|
|
@@ -56919,6 +57986,7 @@ exports.getWorkspaceDisplayNameAsync = getWorkspaceDisplayNameAsync;
|
|
|
56919
57986
|
exports.getWorkspaceDisplayNamesMap = getWorkspaceDisplayNamesMap;
|
|
56920
57987
|
exports.getWorkspaceFromUrl = getWorkspaceFromUrl;
|
|
56921
57988
|
exports.getWorkspaceNavigationParams = getWorkspaceNavigationParams;
|
|
57989
|
+
exports.groupLinesByShift = groupLinesByShift;
|
|
56922
57990
|
exports.hasAnyShiftData = hasAnyShiftData;
|
|
56923
57991
|
exports.identifyCoreUser = identifyCoreUser;
|
|
56924
57992
|
exports.initializeCoreMixpanel = initializeCoreMixpanel;
|
|
@@ -56960,12 +58028,14 @@ exports.startCoreSessionRecording = startCoreSessionRecording;
|
|
|
56960
58028
|
exports.stopCoreSessionRecording = stopCoreSessionRecording;
|
|
56961
58029
|
exports.storeWorkspaceMapping = storeWorkspaceMapping;
|
|
56962
58030
|
exports.streamProxyConfig = streamProxyConfig;
|
|
58031
|
+
exports.subscribeWorkspaceDisplayNames = subscribeWorkspaceDisplayNames;
|
|
56963
58032
|
exports.throttledReloadDashboard = throttledReloadDashboard;
|
|
56964
58033
|
exports.toUrlFriendlyName = toUrlFriendlyName;
|
|
56965
58034
|
exports.trackCoreEvent = trackCoreEvent;
|
|
56966
58035
|
exports.trackCorePageView = trackCorePageView;
|
|
56967
58036
|
exports.transformToChartData = transformToChartData;
|
|
56968
58037
|
exports.updateThreadTitle = updateThreadTitle;
|
|
58038
|
+
exports.upsertWorkspaceDisplayNameInCache = upsertWorkspaceDisplayNameInCache;
|
|
56969
58039
|
exports.useAccessControl = useAccessControl;
|
|
56970
58040
|
exports.useActiveBreaks = useActiveBreaks;
|
|
56971
58041
|
exports.useActiveLineId = useActiveLineId;
|
|
@@ -57029,6 +58099,7 @@ exports.useSubscriptionManager = useSubscriptionManager;
|
|
|
57029
58099
|
exports.useSubscriptionManagerSafe = useSubscriptionManagerSafe;
|
|
57030
58100
|
exports.useSupabase = useSupabase;
|
|
57031
58101
|
exports.useSupabaseClient = useSupabaseClient;
|
|
58102
|
+
exports.useSupervisorsByLineIds = useSupervisorsByLineIds;
|
|
57032
58103
|
exports.useTargets = useTargets;
|
|
57033
58104
|
exports.useTeamManagementPermissions = useTeamManagementPermissions;
|
|
57034
58105
|
exports.useTheme = useTheme;
|