@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.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import Hls3, { Events, ErrorTypes } from 'hls.js';
|
|
|
11
11
|
import useSWR from 'swr';
|
|
12
12
|
import { noop, warning, invariant, progress, secondsToMilliseconds, millisecondsToSeconds, memo as memo$1 } from 'motion-utils';
|
|
13
13
|
import { getValueTransition, hover, press, isPrimaryPointer, GroupPlaybackControls, setDragLock, supportsLinearEasing, attachTimeline, isGenerator, calcGeneratorDuration, isWaapiSupportedEasing, mapEasingToNativeEasing, maxGeneratorDuration, generateLinearEasing, isBezierDefinition } from 'motion-dom';
|
|
14
|
-
import { Camera, ChevronDown, ChevronUp, Check, Map as Map$1, Video, ShieldCheck, Star, Award, ArrowLeft, X, Coffee, Plus, Clock, Calendar, Save, AlertCircle, Loader2, Minus, ArrowDown, ArrowUp, ChevronLeft, ChevronRight, Pause, Play, Wrench, XCircle, Package, UserX, Zap, HelpCircle, AlertTriangle, Tag, Sparkles, TrendingUp, Settings2, CheckCircle2, RefreshCw, TrendingDown, FolderOpen, Folder, Sliders, Activity, Layers, Filter, Search, Edit2, CheckCircle, Building2, Mail, Users, User, Lock, ArrowRight, Info, Share2, Trophy, Target, Download, Sun, Moon, MousePointer, MessageSquare, Trash2, Menu, Send, Copy, UserCheck, LogOut, UserPlus, Settings, LifeBuoy, EyeOff, Eye, MoreVertical, UserCog, Shield, UserCircle } from 'lucide-react';
|
|
14
|
+
import { Camera, ChevronDown, ChevronUp, Check, Map as Map$1, Video, ShieldCheck, Star, Award, ArrowLeft, X, Coffee, Plus, Clock, Calendar, Save, AlertCircle, Loader2, Minus, ArrowDown, ArrowUp, ChevronLeft, ChevronRight, Pause, Play, Wrench, XCircle, Package, UserX, Zap, HelpCircle, AlertTriangle, Tag, Sparkles, TrendingUp, Settings2, CheckCircle2, RefreshCw, TrendingDown, FolderOpen, Folder, Sliders, Activity, Layers, Filter, Search, Edit2, CheckCircle, Building2, Mail, Users, User, Lock, ArrowRight, Info, Share2, Trophy, Target, Download, Sun, Moon, MousePointer, MessageSquare, Trash2, Menu, Send, Copy, UserCheck, LogOut, UserPlus, Settings, LifeBuoy, EyeOff, Eye, MoreVertical, BarChart3, UserCog, Shield, UserCircle } from 'lucide-react';
|
|
15
15
|
import { toast } from 'sonner';
|
|
16
16
|
import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Bar, LabelList, ResponsiveContainer, LineChart as LineChart$1, Line, PieChart, Pie, Cell, ReferenceLine, ComposedChart, Area, ScatterChart, Scatter } from 'recharts';
|
|
17
17
|
import { Slot } from '@radix-ui/react-slot';
|
|
@@ -1818,22 +1818,112 @@ var qualityService = {
|
|
|
1818
1818
|
}
|
|
1819
1819
|
};
|
|
1820
1820
|
|
|
1821
|
-
// src/lib/services/
|
|
1821
|
+
// src/lib/services/backendClient.ts
|
|
1822
|
+
var ACCESS_TOKEN_REFRESH_BUFFER_MS = 6e4;
|
|
1823
|
+
var cachedAccessToken = null;
|
|
1824
|
+
var cachedAccessTokenExpiresAtMs = null;
|
|
1825
|
+
var cachedUserId = null;
|
|
1826
|
+
var inFlightRequests = /* @__PURE__ */ new Map();
|
|
1827
|
+
var authListenerSubscription = null;
|
|
1828
|
+
var authListenerSupabase = null;
|
|
1829
|
+
var clearBackendClientCaches = () => {
|
|
1830
|
+
cachedAccessToken = null;
|
|
1831
|
+
cachedAccessTokenExpiresAtMs = null;
|
|
1832
|
+
cachedUserId = null;
|
|
1833
|
+
inFlightRequests.clear();
|
|
1834
|
+
};
|
|
1835
|
+
var initBackendClientAuthListener = (supabase) => {
|
|
1836
|
+
if (authListenerSupabase === supabase && authListenerSubscription) return;
|
|
1837
|
+
if (authListenerSubscription) {
|
|
1838
|
+
authListenerSubscription.unsubscribe();
|
|
1839
|
+
authListenerSubscription = null;
|
|
1840
|
+
}
|
|
1841
|
+
authListenerSupabase = supabase;
|
|
1842
|
+
const { data } = supabase.auth.onAuthStateChange(() => {
|
|
1843
|
+
clearBackendClientCaches();
|
|
1844
|
+
});
|
|
1845
|
+
authListenerSubscription = data.subscription;
|
|
1846
|
+
};
|
|
1822
1847
|
var getBackendUrl = () => {
|
|
1823
1848
|
const url = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
1824
1849
|
if (!url) {
|
|
1825
1850
|
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
1826
1851
|
}
|
|
1827
|
-
return url;
|
|
1852
|
+
return url.replace(/\/$/, "");
|
|
1828
1853
|
};
|
|
1829
|
-
var getAuthToken = async () => {
|
|
1830
|
-
const
|
|
1831
|
-
if (
|
|
1832
|
-
|
|
1854
|
+
var getAuthToken = async (supabase) => {
|
|
1855
|
+
const now2 = Date.now();
|
|
1856
|
+
if (cachedAccessToken && cachedAccessTokenExpiresAtMs && now2 < cachedAccessTokenExpiresAtMs - ACCESS_TOKEN_REFRESH_BUFFER_MS) {
|
|
1857
|
+
return cachedAccessToken;
|
|
1858
|
+
}
|
|
1859
|
+
const {
|
|
1860
|
+
data: { session }
|
|
1861
|
+
} = await supabase.auth.getSession();
|
|
1833
1862
|
if (!session?.access_token) {
|
|
1863
|
+
clearBackendClientCaches();
|
|
1834
1864
|
throw new Error("No authentication token available. Please log in.");
|
|
1835
1865
|
}
|
|
1836
|
-
|
|
1866
|
+
cachedAccessToken = session.access_token;
|
|
1867
|
+
cachedUserId = session.user?.id || null;
|
|
1868
|
+
const expiresAtSeconds = session?.expires_at;
|
|
1869
|
+
cachedAccessTokenExpiresAtMs = typeof expiresAtSeconds === "number" ? expiresAtSeconds * 1e3 : now2 + 5 * 60 * 1e3;
|
|
1870
|
+
return cachedAccessToken;
|
|
1871
|
+
};
|
|
1872
|
+
var defaultDedupeKey = (method, url, body) => {
|
|
1873
|
+
const bodyKey = body === void 0 ? "" : typeof body === "string" ? body : JSON.stringify(body);
|
|
1874
|
+
return `${cachedUserId || "anon"}::${method.toUpperCase()}::${url}::${bodyKey}`;
|
|
1875
|
+
};
|
|
1876
|
+
var fetchBackendJson = async (supabase, endpoint, options = {}) => {
|
|
1877
|
+
const baseUrl = getBackendUrl();
|
|
1878
|
+
const url = endpoint.startsWith("http") ? endpoint : `${baseUrl}${endpoint.startsWith("/") ? "" : "/"}${endpoint}`;
|
|
1879
|
+
const method = (options.method || "GET").toString();
|
|
1880
|
+
const bodyForKey = options.body;
|
|
1881
|
+
const dedupeKey = options.dedupeKey || defaultDedupeKey(method, url, bodyForKey);
|
|
1882
|
+
const existing = inFlightRequests.get(dedupeKey);
|
|
1883
|
+
if (existing) {
|
|
1884
|
+
return existing;
|
|
1885
|
+
}
|
|
1886
|
+
const requestPromise = (async () => {
|
|
1887
|
+
const headers = new Headers(options.headers || {});
|
|
1888
|
+
if (!options.skipAuth) {
|
|
1889
|
+
const token = await getAuthToken(supabase);
|
|
1890
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
1891
|
+
}
|
|
1892
|
+
if (!headers.has("Content-Type") && options.body !== void 0) {
|
|
1893
|
+
headers.set("Content-Type", "application/json");
|
|
1894
|
+
}
|
|
1895
|
+
const response = await fetch(url, {
|
|
1896
|
+
...options,
|
|
1897
|
+
headers
|
|
1898
|
+
});
|
|
1899
|
+
if (!response.ok) {
|
|
1900
|
+
const errorText = await response.text();
|
|
1901
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
1902
|
+
}
|
|
1903
|
+
if (response.status === 204) return void 0;
|
|
1904
|
+
const text = await response.text();
|
|
1905
|
+
return text ? JSON.parse(text) : void 0;
|
|
1906
|
+
})();
|
|
1907
|
+
inFlightRequests.set(dedupeKey, requestPromise);
|
|
1908
|
+
try {
|
|
1909
|
+
return await requestPromise;
|
|
1910
|
+
} finally {
|
|
1911
|
+
inFlightRequests.delete(dedupeKey);
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
|
|
1915
|
+
// src/lib/services/workspaceService.ts
|
|
1916
|
+
var getBackendUrl2 = () => {
|
|
1917
|
+
const url = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
1918
|
+
if (!url) {
|
|
1919
|
+
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
1920
|
+
}
|
|
1921
|
+
return url;
|
|
1922
|
+
};
|
|
1923
|
+
var getAuthToken2 = async () => {
|
|
1924
|
+
const supabase = _getSupabaseInstance();
|
|
1925
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1926
|
+
return getAuthToken(supabase);
|
|
1837
1927
|
};
|
|
1838
1928
|
var workspaceService = {
|
|
1839
1929
|
// Cache for workspace display names to avoid repeated API calls
|
|
@@ -1841,26 +1931,56 @@ var workspaceService = {
|
|
|
1841
1931
|
_cacheTimestamp: 0,
|
|
1842
1932
|
_cacheExpiryMs: 5 * 60 * 1e3,
|
|
1843
1933
|
// 5 minutes cache
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1934
|
+
// Cache for workspace lists to avoid repeated API calls (line configuration changes infrequently)
|
|
1935
|
+
_workspacesCache: /* @__PURE__ */ new Map(),
|
|
1936
|
+
_workspacesInFlight: /* @__PURE__ */ new Map(),
|
|
1937
|
+
_workspacesCacheExpiryMs: 10 * 60 * 1e3,
|
|
1938
|
+
// 10 minutes cache
|
|
1939
|
+
async getWorkspaces(lineId, options) {
|
|
1940
|
+
const enabledOnly = options?.enabledOnly ?? false;
|
|
1941
|
+
const force = options?.force ?? false;
|
|
1942
|
+
const cacheKey = `${lineId}::enabledOnly=${enabledOnly}`;
|
|
1943
|
+
const now2 = Date.now();
|
|
1944
|
+
const cached = this._workspacesCache.get(cacheKey);
|
|
1945
|
+
if (!force && cached && now2 - cached.timestamp < this._workspacesCacheExpiryMs) {
|
|
1946
|
+
return cached.workspaces;
|
|
1947
|
+
}
|
|
1948
|
+
const inFlight = this._workspacesInFlight.get(cacheKey);
|
|
1949
|
+
if (!force && inFlight) {
|
|
1950
|
+
return inFlight;
|
|
1951
|
+
}
|
|
1952
|
+
const fetchPromise = (async () => {
|
|
1953
|
+
try {
|
|
1954
|
+
const token = await getAuthToken2();
|
|
1955
|
+
const apiUrl = getBackendUrl2();
|
|
1956
|
+
const params = new URLSearchParams({ line_id: lineId });
|
|
1957
|
+
if (enabledOnly) params.set("enabled_only", "true");
|
|
1958
|
+
const response = await fetch(`${apiUrl}/api/workspaces?${params.toString()}`, {
|
|
1959
|
+
headers: {
|
|
1960
|
+
"Authorization": `Bearer ${token}`,
|
|
1961
|
+
"Content-Type": "application/json"
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
if (!response.ok) {
|
|
1965
|
+
const errorText = await response.text();
|
|
1966
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
1852
1967
|
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1968
|
+
const data = await response.json();
|
|
1969
|
+
const workspaces = data.workspaces || [];
|
|
1970
|
+
this._workspacesCache.set(cacheKey, { workspaces, timestamp: Date.now() });
|
|
1971
|
+
return workspaces;
|
|
1972
|
+
} catch (error) {
|
|
1973
|
+
console.error("Error fetching workspaces:", error);
|
|
1974
|
+
throw error;
|
|
1975
|
+
} finally {
|
|
1976
|
+
this._workspacesInFlight.delete(cacheKey);
|
|
1857
1977
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
}
|
|
1978
|
+
})();
|
|
1979
|
+
this._workspacesInFlight.set(cacheKey, fetchPromise);
|
|
1980
|
+
return fetchPromise;
|
|
1981
|
+
},
|
|
1982
|
+
async getEnabledWorkspaces(lineId, options) {
|
|
1983
|
+
return this.getWorkspaces(lineId, { enabledOnly: true, force: options?.force });
|
|
1864
1984
|
},
|
|
1865
1985
|
/**
|
|
1866
1986
|
* Fetches workspace display names from the database
|
|
@@ -1868,8 +1988,8 @@ var workspaceService = {
|
|
|
1868
1988
|
*/
|
|
1869
1989
|
async getWorkspaceDisplayNames(companyId, lineId) {
|
|
1870
1990
|
try {
|
|
1871
|
-
const token = await
|
|
1872
|
-
const apiUrl =
|
|
1991
|
+
const token = await getAuthToken2();
|
|
1992
|
+
const apiUrl = getBackendUrl2();
|
|
1873
1993
|
const params = new URLSearchParams();
|
|
1874
1994
|
if (companyId) params.append("company_id", companyId);
|
|
1875
1995
|
if (lineId) params.append("line_id", lineId);
|
|
@@ -1928,16 +2048,39 @@ var workspaceService = {
|
|
|
1928
2048
|
this._workspaceDisplayNamesCache.clear();
|
|
1929
2049
|
this._cacheTimestamp = 0;
|
|
1930
2050
|
},
|
|
2051
|
+
clearWorkspacesCache() {
|
|
2052
|
+
this._workspacesCache.clear();
|
|
2053
|
+
this._workspacesInFlight.clear();
|
|
2054
|
+
},
|
|
2055
|
+
invalidateWorkspacesCacheForLine(lineId) {
|
|
2056
|
+
const prefix = `${lineId}::`;
|
|
2057
|
+
Array.from(this._workspacesCache.keys()).forEach((key) => {
|
|
2058
|
+
if (key.startsWith(prefix)) this._workspacesCache.delete(key);
|
|
2059
|
+
});
|
|
2060
|
+
Array.from(this._workspacesInFlight.keys()).forEach((key) => {
|
|
2061
|
+
if (key.startsWith(prefix)) this._workspacesInFlight.delete(key);
|
|
2062
|
+
});
|
|
2063
|
+
},
|
|
2064
|
+
_patchCachedWorkspacesForLine(lineId, workspaceRowId, patch) {
|
|
2065
|
+
const prefix = `${lineId}::`;
|
|
2066
|
+
Array.from(this._workspacesCache.entries()).forEach(([key, entry]) => {
|
|
2067
|
+
if (!key.startsWith(prefix)) return;
|
|
2068
|
+
const nextWorkspaces = (entry.workspaces || []).map(
|
|
2069
|
+
(ws) => ws?.id === workspaceRowId ? { ...ws, ...patch } : ws
|
|
2070
|
+
);
|
|
2071
|
+
this._workspacesCache.set(key, { workspaces: nextWorkspaces, timestamp: Date.now() });
|
|
2072
|
+
});
|
|
2073
|
+
},
|
|
1931
2074
|
/**
|
|
1932
2075
|
* Updates the display name for a workspace
|
|
1933
2076
|
* @param workspaceId - The workspace UUID
|
|
1934
2077
|
* @param displayName - The new display name
|
|
1935
|
-
* @returns
|
|
2078
|
+
* @returns Updated workspace record from backend
|
|
1936
2079
|
*/
|
|
1937
2080
|
async updateWorkspaceDisplayName(workspaceId, displayName) {
|
|
1938
2081
|
try {
|
|
1939
|
-
const token = await
|
|
1940
|
-
const apiUrl =
|
|
2082
|
+
const token = await getAuthToken2();
|
|
2083
|
+
const apiUrl = getBackendUrl2();
|
|
1941
2084
|
const response = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/display-name`, {
|
|
1942
2085
|
method: "PATCH",
|
|
1943
2086
|
headers: {
|
|
@@ -1950,7 +2093,15 @@ var workspaceService = {
|
|
|
1950
2093
|
const errorText = await response.text();
|
|
1951
2094
|
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
1952
2095
|
}
|
|
2096
|
+
const data = await response.json();
|
|
1953
2097
|
this.clearWorkspaceDisplayNamesCache();
|
|
2098
|
+
const updatedWorkspace = data?.workspace || null;
|
|
2099
|
+
if (updatedWorkspace?.line_id && updatedWorkspace?.id) {
|
|
2100
|
+
this._patchCachedWorkspacesForLine(updatedWorkspace.line_id, updatedWorkspace.id, {
|
|
2101
|
+
display_name: updatedWorkspace.display_name
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
return updatedWorkspace;
|
|
1954
2105
|
} catch (error) {
|
|
1955
2106
|
console.error(`Error updating workspace display name for ${workspaceId}:`, error);
|
|
1956
2107
|
throw error;
|
|
@@ -1958,8 +2109,8 @@ var workspaceService = {
|
|
|
1958
2109
|
},
|
|
1959
2110
|
async updateWorkspaceAction(updates) {
|
|
1960
2111
|
try {
|
|
1961
|
-
const token = await
|
|
1962
|
-
const apiUrl =
|
|
2112
|
+
const token = await getAuthToken2();
|
|
2113
|
+
const apiUrl = getBackendUrl2();
|
|
1963
2114
|
const response = await fetch(`${apiUrl}/api/workspaces/actions/update`, {
|
|
1964
2115
|
method: "POST",
|
|
1965
2116
|
headers: {
|
|
@@ -1972,6 +2123,7 @@ var workspaceService = {
|
|
|
1972
2123
|
const errorText = await response.text();
|
|
1973
2124
|
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
1974
2125
|
}
|
|
2126
|
+
this.clearWorkspacesCache();
|
|
1975
2127
|
} catch (error) {
|
|
1976
2128
|
console.error("Error updating workspace actions:", error);
|
|
1977
2129
|
throw error;
|
|
@@ -1979,8 +2131,8 @@ var workspaceService = {
|
|
|
1979
2131
|
},
|
|
1980
2132
|
async updateActionThresholds(thresholds) {
|
|
1981
2133
|
try {
|
|
1982
|
-
const token = await
|
|
1983
|
-
const apiUrl =
|
|
2134
|
+
const token = await getAuthToken2();
|
|
2135
|
+
const apiUrl = getBackendUrl2();
|
|
1984
2136
|
const response = await fetch(`${apiUrl}/api/workspaces/action-thresholds/update`, {
|
|
1985
2137
|
method: "POST",
|
|
1986
2138
|
headers: {
|
|
@@ -2000,8 +2152,8 @@ var workspaceService = {
|
|
|
2000
2152
|
},
|
|
2001
2153
|
async getActionThresholds(lineId, date, shiftId = 0) {
|
|
2002
2154
|
try {
|
|
2003
|
-
const token = await
|
|
2004
|
-
const apiUrl =
|
|
2155
|
+
const token = await getAuthToken2();
|
|
2156
|
+
const apiUrl = getBackendUrl2();
|
|
2005
2157
|
const response = await fetch(
|
|
2006
2158
|
`${apiUrl}/api/workspaces/action-thresholds?line_id=${lineId}&date=${date}&shift_id=${shiftId}`,
|
|
2007
2159
|
{
|
|
@@ -2056,8 +2208,8 @@ var workspaceService = {
|
|
|
2056
2208
|
const totalPPH = outputWorkspaces.reduce((sum, ws) => sum + (ws.action_pph_threshold || 0), 0);
|
|
2057
2209
|
const operationalDate = getOperationalDate(defaultTimezone || "UTC");
|
|
2058
2210
|
try {
|
|
2059
|
-
const token = await
|
|
2060
|
-
const apiUrl =
|
|
2211
|
+
const token = await getAuthToken2();
|
|
2212
|
+
const apiUrl = getBackendUrl2();
|
|
2061
2213
|
const response = await fetch(
|
|
2062
2214
|
`${apiUrl}/api/workspaces/line-thresholds?line_id=${lineId}`,
|
|
2063
2215
|
{
|
|
@@ -2088,8 +2240,8 @@ var workspaceService = {
|
|
|
2088
2240
|
// Returns ShiftConfiguration array, which is the logical representation.
|
|
2089
2241
|
async getShiftConfigurations(lineId) {
|
|
2090
2242
|
try {
|
|
2091
|
-
const token = await
|
|
2092
|
-
const apiUrl =
|
|
2243
|
+
const token = await getAuthToken2();
|
|
2244
|
+
const apiUrl = getBackendUrl2();
|
|
2093
2245
|
const response = await fetch(
|
|
2094
2246
|
`${apiUrl}/api/workspaces/shift-configurations?line_id=${lineId}`,
|
|
2095
2247
|
{
|
|
@@ -2119,8 +2271,8 @@ var workspaceService = {
|
|
|
2119
2271
|
},
|
|
2120
2272
|
async updateShiftConfigurations(shiftConfig) {
|
|
2121
2273
|
try {
|
|
2122
|
-
const token = await
|
|
2123
|
-
const apiUrl =
|
|
2274
|
+
const token = await getAuthToken2();
|
|
2275
|
+
const apiUrl = getBackendUrl2();
|
|
2124
2276
|
const response = await fetch(
|
|
2125
2277
|
`${apiUrl}/api/workspaces/shift-configurations`,
|
|
2126
2278
|
{
|
|
@@ -2157,8 +2309,8 @@ var workspaceService = {
|
|
|
2157
2309
|
*/
|
|
2158
2310
|
async fetchBulkTargets(params) {
|
|
2159
2311
|
try {
|
|
2160
|
-
const token = await
|
|
2161
|
-
const apiUrl =
|
|
2312
|
+
const token = await getAuthToken2();
|
|
2313
|
+
const apiUrl = getBackendUrl2();
|
|
2162
2314
|
const {
|
|
2163
2315
|
companyId,
|
|
2164
2316
|
lineIds,
|
|
@@ -2344,7 +2496,17 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2344
2496
|
}
|
|
2345
2497
|
}
|
|
2346
2498
|
const dataWithUptime = processedData.map((workspace) => {
|
|
2347
|
-
const
|
|
2499
|
+
const mapKey = this.getUptimeMapKey(workspace.line_id, workspace.workspace_id);
|
|
2500
|
+
const uptimeDetails = uptimeMap.get(mapKey);
|
|
2501
|
+
console.log(`[getWorkspaceHealthStatus] Lookup:`, {
|
|
2502
|
+
mapKey,
|
|
2503
|
+
workspaceLineId: workspace.line_id,
|
|
2504
|
+
workspaceId: workspace.workspace_id,
|
|
2505
|
+
workspaceName: workspace.workspace_display_name,
|
|
2506
|
+
found: !!uptimeDetails,
|
|
2507
|
+
uptime: uptimeDetails?.percentage,
|
|
2508
|
+
downtime: uptimeDetails ? Math.max(0, uptimeDetails.expectedMinutes - uptimeDetails.actualMinutes) : null
|
|
2509
|
+
});
|
|
2348
2510
|
if (uptimeDetails) {
|
|
2349
2511
|
return {
|
|
2350
2512
|
...workspace,
|
|
@@ -2705,6 +2867,13 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2705
2867
|
clearCache() {
|
|
2706
2868
|
this.cache.clear();
|
|
2707
2869
|
}
|
|
2870
|
+
/**
|
|
2871
|
+
* Generate a composite key for the uptime map that includes line_id
|
|
2872
|
+
* This prevents data collision when multiple lines have workspaces with the same workspace_id
|
|
2873
|
+
*/
|
|
2874
|
+
getUptimeMapKey(lineId, workspaceId) {
|
|
2875
|
+
return lineId ? `${lineId}::${workspaceId}` : workspaceId;
|
|
2876
|
+
}
|
|
2708
2877
|
async calculateWorkspaceUptime(companyId, passedShiftConfig, timezone, lineShiftConfigs, overrideDate, overrideShiftId) {
|
|
2709
2878
|
const supabase = _getSupabaseInstance();
|
|
2710
2879
|
if (!supabase) throw new Error("Supabase client not initialized");
|
|
@@ -2734,7 +2903,7 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2734
2903
|
const queryShiftId = overrideShiftId ?? currentShiftId;
|
|
2735
2904
|
const tableName = `performance_metrics_${companyId.replace(/-/g, "_")}`;
|
|
2736
2905
|
try {
|
|
2737
|
-
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);
|
|
2906
|
+
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);
|
|
2738
2907
|
if (error) {
|
|
2739
2908
|
console.error("Error fetching performance metrics:", error);
|
|
2740
2909
|
return /* @__PURE__ */ new Map();
|
|
@@ -2780,7 +2949,8 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2780
2949
|
}
|
|
2781
2950
|
const completedWindow = uptimeMinutes + downtimeMinutes;
|
|
2782
2951
|
const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
2783
|
-
|
|
2952
|
+
const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
|
|
2953
|
+
uptimeMap.set(mapKey, {
|
|
2784
2954
|
expectedMinutes: completedMinutes,
|
|
2785
2955
|
actualMinutes: uptimeMinutes,
|
|
2786
2956
|
percentage,
|
|
@@ -2822,6 +2992,17 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2822
2992
|
uniqueQueries.get(key).lineConfigs.push({ lineId, shiftStartDate, completedMinutes });
|
|
2823
2993
|
});
|
|
2824
2994
|
console.log(`[calculateWorkspaceUptimeMultiLine] Querying ${uniqueQueries.size} unique date/shift combinations for ${lineShiftConfigs.size} lines`);
|
|
2995
|
+
uniqueQueries.forEach((queryConfig, key) => {
|
|
2996
|
+
console.log(`[calculateWorkspaceUptimeMultiLine] Query batch ${key}:`, {
|
|
2997
|
+
date: queryConfig.date,
|
|
2998
|
+
shiftId: queryConfig.shiftId,
|
|
2999
|
+
lineConfigs: queryConfig.lineConfigs.map((lc) => ({
|
|
3000
|
+
lineId: lc.lineId,
|
|
3001
|
+
completedMinutes: lc.completedMinutes,
|
|
3002
|
+
shiftStartDate: lc.shiftStartDate.toISOString()
|
|
3003
|
+
}))
|
|
3004
|
+
});
|
|
3005
|
+
});
|
|
2825
3006
|
const queryPromises = Array.from(uniqueQueries.entries()).map(async ([key, { date, shiftId, lineConfigs }]) => {
|
|
2826
3007
|
try {
|
|
2827
3008
|
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);
|
|
@@ -2838,8 +3019,14 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2838
3019
|
const results = await Promise.all(queryPromises);
|
|
2839
3020
|
for (const { records, lineConfigs } of results) {
|
|
2840
3021
|
for (const record of records) {
|
|
2841
|
-
const lineConfig = lineConfigs.find((lc) =>
|
|
2842
|
-
|
|
3022
|
+
const lineConfig = lineConfigs.find((lc) => lc.lineId === record.line_id);
|
|
3023
|
+
console.log(`[calculateWorkspaceUptimeMultiLine] Record match:`, {
|
|
3024
|
+
recordLineId: record.line_id,
|
|
3025
|
+
recordWorkspaceId: record.workspace_id,
|
|
3026
|
+
recordWorkspaceDisplayName: record.workspace_display_name,
|
|
3027
|
+
availableLineIds: lineConfigs.map((lc) => lc.lineId),
|
|
3028
|
+
matchFound: !!lineConfig,
|
|
3029
|
+
matchedLineId: lineConfig?.lineId || "FALLBACK to " + lineConfigs[0]?.lineId
|
|
2843
3030
|
});
|
|
2844
3031
|
const effectiveLineConfig = lineConfig || lineConfigs[0];
|
|
2845
3032
|
if (!effectiveLineConfig) continue;
|
|
@@ -2882,12 +3069,22 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
|
2882
3069
|
}
|
|
2883
3070
|
const completedWindow = uptimeMinutes + downtimeMinutes;
|
|
2884
3071
|
const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
|
|
2885
|
-
|
|
3072
|
+
const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
|
|
3073
|
+
uptimeMap.set(mapKey, {
|
|
2886
3074
|
expectedMinutes: completedMinutes,
|
|
2887
3075
|
actualMinutes: uptimeMinutes,
|
|
2888
3076
|
percentage,
|
|
2889
3077
|
lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2890
3078
|
});
|
|
3079
|
+
console.log(`[calculateWorkspaceUptimeMultiLine] Storing uptime:`, {
|
|
3080
|
+
mapKey,
|
|
3081
|
+
lineId: record.line_id,
|
|
3082
|
+
workspaceId: record.workspace_id,
|
|
3083
|
+
workspaceDisplayName: record.workspace_display_name,
|
|
3084
|
+
expectedMinutes: completedMinutes,
|
|
3085
|
+
actualMinutes: uptimeMinutes,
|
|
3086
|
+
percentage
|
|
3087
|
+
});
|
|
2891
3088
|
}
|
|
2892
3089
|
}
|
|
2893
3090
|
console.log(`[calculateWorkspaceUptimeMultiLine] Calculated uptime for ${uptimeMap.size} workspaces`);
|
|
@@ -4535,7 +4732,7 @@ var getSupabaseClient = () => {
|
|
|
4535
4732
|
}
|
|
4536
4733
|
return createClient(url, key);
|
|
4537
4734
|
};
|
|
4538
|
-
var
|
|
4735
|
+
var getAuthToken3 = async () => {
|
|
4539
4736
|
try {
|
|
4540
4737
|
const supabase = getSupabaseClient();
|
|
4541
4738
|
const { data: { session } } = await supabase.auth.getSession();
|
|
@@ -4569,7 +4766,7 @@ var S3ClipsSupabaseService = class {
|
|
|
4569
4766
|
* Fetch with authentication and error handling
|
|
4570
4767
|
*/
|
|
4571
4768
|
async fetchWithAuth(endpoint, body) {
|
|
4572
|
-
const token = await
|
|
4769
|
+
const token = await getAuthToken3();
|
|
4573
4770
|
if (!token) {
|
|
4574
4771
|
throw new Error("Authentication required");
|
|
4575
4772
|
}
|
|
@@ -6279,7 +6476,7 @@ var IDLE_TIME_REASON_COLORS = {
|
|
|
6279
6476
|
bg: "bg-amber-50",
|
|
6280
6477
|
border: "border-amber-200"
|
|
6281
6478
|
},
|
|
6282
|
-
"Machine
|
|
6479
|
+
"Machine Downtime": {
|
|
6283
6480
|
hex: "#3b82f6",
|
|
6284
6481
|
// blue-500 - Scheduled/Technical
|
|
6285
6482
|
text: "text-blue-600",
|
|
@@ -6715,6 +6912,7 @@ var SupabaseProvider = ({ client, children }) => {
|
|
|
6715
6912
|
_setSupabaseInstance(client);
|
|
6716
6913
|
useEffect(() => {
|
|
6717
6914
|
_setSupabaseInstance(client);
|
|
6915
|
+
initBackendClientAuthListener(client);
|
|
6718
6916
|
}, [client]);
|
|
6719
6917
|
const contextValue = useMemo(() => ({ supabase: client }), [client]);
|
|
6720
6918
|
return /* @__PURE__ */ jsx(SupabaseContext.Provider, { value: contextValue, children });
|
|
@@ -7629,33 +7827,10 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
7629
7827
|
console.log("[useWorkspaceDetailedMetrics] Using shift ID:", queryShiftId, "from input shift:", shiftId);
|
|
7630
7828
|
console.log("[useWorkspaceDetailedMetrics] Using date:", queryDate, "from input date:", date);
|
|
7631
7829
|
console.log(`[useWorkspaceDetailedMetrics] Fetching from backend API for workspace: ${workspaceId}, date: ${queryDate}, shift: ${queryShiftId}`);
|
|
7632
|
-
const
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
}
|
|
7636
|
-
const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
7637
|
-
const response = await fetch(
|
|
7638
|
-
`${apiUrl}/api/dashboard/workspace/${workspaceId}/metrics?date=${queryDate}&shift_id=${queryShiftId}&company_id=${companyId}`,
|
|
7639
|
-
{
|
|
7640
|
-
headers: {
|
|
7641
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
7642
|
-
"Content-Type": "application/json"
|
|
7643
|
-
}
|
|
7644
|
-
}
|
|
7830
|
+
const backendData = await fetchBackendJson(
|
|
7831
|
+
supabase,
|
|
7832
|
+
`/api/dashboard/workspace/${workspaceId}/metrics?date=${queryDate}&shift_id=${queryShiftId}&company_id=${companyId}`
|
|
7645
7833
|
);
|
|
7646
|
-
if (!response.ok) {
|
|
7647
|
-
const errorText = await response.text();
|
|
7648
|
-
console.error("[useWorkspaceDetailedMetrics] Backend API error response:", errorText);
|
|
7649
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
7650
|
-
}
|
|
7651
|
-
const responseText = await response.text();
|
|
7652
|
-
let backendData;
|
|
7653
|
-
try {
|
|
7654
|
-
backendData = JSON.parse(responseText);
|
|
7655
|
-
} catch (parseError) {
|
|
7656
|
-
console.error("[useWorkspaceDetailedMetrics] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
|
|
7657
|
-
throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
|
|
7658
|
-
}
|
|
7659
7834
|
const data = backendData.metrics;
|
|
7660
7835
|
if (data && options?.shiftConfig) {
|
|
7661
7836
|
const dynamicShiftName = getShiftNameById(
|
|
@@ -7669,20 +7844,10 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
7669
7844
|
}
|
|
7670
7845
|
if (!data && !date && shiftId === void 0) {
|
|
7671
7846
|
console.log("[useWorkspaceDetailedMetrics] No data found for current date/shift, attempting to find most recent data...");
|
|
7672
|
-
const
|
|
7673
|
-
|
|
7674
|
-
{
|
|
7675
|
-
headers: {
|
|
7676
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
7677
|
-
"Content-Type": "application/json"
|
|
7678
|
-
}
|
|
7679
|
-
}
|
|
7847
|
+
const fallbackData = await fetchBackendJson(
|
|
7848
|
+
supabase,
|
|
7849
|
+
`/api/dashboard/workspace/${workspaceId}/metrics?company_id=${companyId}&latest=true`
|
|
7680
7850
|
);
|
|
7681
|
-
if (!fallbackResponse.ok) {
|
|
7682
|
-
const errorText = await fallbackResponse.text();
|
|
7683
|
-
throw new Error(`Backend API error (${fallbackResponse.status}): ${errorText}`);
|
|
7684
|
-
}
|
|
7685
|
-
const fallbackData = await fallbackResponse.json();
|
|
7686
7851
|
const recentData = fallbackData.metrics;
|
|
7687
7852
|
if (recentData) {
|
|
7688
7853
|
console.log(`[useWorkspaceDetailedMetrics] Found fallback data from date: ${recentData.date}, shift: ${recentData.shift_id}`);
|
|
@@ -8244,15 +8409,171 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
8244
8409
|
// Force refresh without cache
|
|
8245
8410
|
};
|
|
8246
8411
|
};
|
|
8412
|
+
|
|
8413
|
+
// src/lib/stores/shiftConfigStore.ts
|
|
8414
|
+
var shiftConfigsByLineId = /* @__PURE__ */ new Map();
|
|
8415
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
8416
|
+
var inFlightFetchesByLineId = /* @__PURE__ */ new Map();
|
|
8417
|
+
var subscriptionsByLineId = /* @__PURE__ */ new Map();
|
|
8418
|
+
var calculateBreakDuration = (startTime, endTime) => {
|
|
8419
|
+
const [sh, sm] = startTime.split(":").map(Number);
|
|
8420
|
+
const [eh, em] = endTime.split(":").map(Number);
|
|
8421
|
+
let startMinutes = sh * 60 + sm;
|
|
8422
|
+
let endMinutes = eh * 60 + em;
|
|
8423
|
+
if (endMinutes < startMinutes) {
|
|
8424
|
+
endMinutes += 24 * 60;
|
|
8425
|
+
}
|
|
8426
|
+
return endMinutes - startMinutes;
|
|
8427
|
+
};
|
|
8428
|
+
var stripSeconds = (timeStr) => {
|
|
8429
|
+
if (!timeStr) return timeStr;
|
|
8430
|
+
return timeStr.substring(0, 5);
|
|
8431
|
+
};
|
|
8432
|
+
var buildShiftConfigFromOperatingHoursRows = (rows, fallback) => {
|
|
8433
|
+
const mapped = (rows || []).map((row) => ({
|
|
8434
|
+
shiftId: row.shift_id,
|
|
8435
|
+
shiftName: row.shift_name || `Shift ${row.shift_id}`,
|
|
8436
|
+
startTime: stripSeconds(row.start_time),
|
|
8437
|
+
endTime: stripSeconds(row.end_time),
|
|
8438
|
+
breaks: (() => {
|
|
8439
|
+
const raw = Array.isArray(row.breaks) ? row.breaks : Array.isArray(row.breaks?.breaks) ? row.breaks.breaks : [];
|
|
8440
|
+
return raw.map((b) => ({
|
|
8441
|
+
startTime: stripSeconds(b.start || b.startTime || "00:00"),
|
|
8442
|
+
endTime: stripSeconds(b.end || b.endTime || "00:00"),
|
|
8443
|
+
duration: calculateBreakDuration(
|
|
8444
|
+
stripSeconds(b.start || b.startTime || "00:00"),
|
|
8445
|
+
stripSeconds(b.end || b.endTime || "00:00")
|
|
8446
|
+
),
|
|
8447
|
+
remarks: b.remarks || b.name || ""
|
|
8448
|
+
}));
|
|
8449
|
+
})(),
|
|
8450
|
+
timezone: row.timezone || void 0
|
|
8451
|
+
}));
|
|
8452
|
+
const day = mapped.find((s) => s.shiftId === 0);
|
|
8453
|
+
const night = mapped.find((s) => s.shiftId === 1);
|
|
8454
|
+
return {
|
|
8455
|
+
shifts: mapped,
|
|
8456
|
+
timezone: mapped[0]?.timezone || fallback?.timezone,
|
|
8457
|
+
dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallback?.dayShift,
|
|
8458
|
+
nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallback?.nightShift,
|
|
8459
|
+
transitionPeriodMinutes: fallback?.transitionPeriodMinutes || 0
|
|
8460
|
+
};
|
|
8461
|
+
};
|
|
8462
|
+
var shiftConfigStore = {
|
|
8463
|
+
get(lineId) {
|
|
8464
|
+
return shiftConfigsByLineId.get(lineId);
|
|
8465
|
+
},
|
|
8466
|
+
set(lineId, config) {
|
|
8467
|
+
shiftConfigsByLineId.set(lineId, config);
|
|
8468
|
+
listeners.forEach((listener) => listener(lineId));
|
|
8469
|
+
},
|
|
8470
|
+
setFromOperatingHoursRows(lineId, rows, fallback) {
|
|
8471
|
+
const config = buildShiftConfigFromOperatingHoursRows(rows, fallback);
|
|
8472
|
+
this.set(lineId, config);
|
|
8473
|
+
return config;
|
|
8474
|
+
},
|
|
8475
|
+
getMany(lineIds) {
|
|
8476
|
+
const map = /* @__PURE__ */ new Map();
|
|
8477
|
+
lineIds.forEach((lineId) => {
|
|
8478
|
+
const config = shiftConfigsByLineId.get(lineId);
|
|
8479
|
+
if (config) map.set(lineId, config);
|
|
8480
|
+
});
|
|
8481
|
+
return map;
|
|
8482
|
+
},
|
|
8483
|
+
subscribe(listener) {
|
|
8484
|
+
listeners.add(listener);
|
|
8485
|
+
return () => listeners.delete(listener);
|
|
8486
|
+
},
|
|
8487
|
+
clear() {
|
|
8488
|
+
shiftConfigsByLineId.clear();
|
|
8489
|
+
listeners.forEach((listener) => listener("*"));
|
|
8490
|
+
}
|
|
8491
|
+
};
|
|
8492
|
+
var fetchAndStoreShiftConfig = async (supabase, lineId, fallback) => {
|
|
8493
|
+
const existing = inFlightFetchesByLineId.get(lineId);
|
|
8494
|
+
if (existing) return existing;
|
|
8495
|
+
const promise = (async () => {
|
|
8496
|
+
try {
|
|
8497
|
+
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);
|
|
8498
|
+
if (error) {
|
|
8499
|
+
throw new Error(`Failed to fetch shift config: ${error.message}`);
|
|
8500
|
+
}
|
|
8501
|
+
if (!data || data.length === 0) {
|
|
8502
|
+
if (fallback) {
|
|
8503
|
+
shiftConfigStore.set(lineId, fallback);
|
|
8504
|
+
return fallback;
|
|
8505
|
+
}
|
|
8506
|
+
return null;
|
|
8507
|
+
}
|
|
8508
|
+
return shiftConfigStore.setFromOperatingHoursRows(lineId, data, fallback);
|
|
8509
|
+
} finally {
|
|
8510
|
+
inFlightFetchesByLineId.delete(lineId);
|
|
8511
|
+
}
|
|
8512
|
+
})();
|
|
8513
|
+
inFlightFetchesByLineId.set(lineId, promise);
|
|
8514
|
+
return promise;
|
|
8515
|
+
};
|
|
8516
|
+
var ensureShiftConfigSubscription = (supabase, lineId, fallback) => {
|
|
8517
|
+
const existing = subscriptionsByLineId.get(lineId);
|
|
8518
|
+
if (existing && existing.supabase === supabase) {
|
|
8519
|
+
existing.refCount += 1;
|
|
8520
|
+
return () => {
|
|
8521
|
+
const current = subscriptionsByLineId.get(lineId);
|
|
8522
|
+
if (!current) return;
|
|
8523
|
+
current.refCount -= 1;
|
|
8524
|
+
if (current.refCount <= 0) {
|
|
8525
|
+
current.channel.unsubscribe();
|
|
8526
|
+
subscriptionsByLineId.delete(lineId);
|
|
8527
|
+
}
|
|
8528
|
+
};
|
|
8529
|
+
}
|
|
8530
|
+
if (existing) {
|
|
8531
|
+
existing.channel.unsubscribe();
|
|
8532
|
+
subscriptionsByLineId.delete(lineId);
|
|
8533
|
+
}
|
|
8534
|
+
const channel = supabase.channel(`shift_config_${lineId}`).on(
|
|
8535
|
+
"postgres_changes",
|
|
8536
|
+
{
|
|
8537
|
+
event: "*",
|
|
8538
|
+
schema: "public",
|
|
8539
|
+
table: "line_operating_hours",
|
|
8540
|
+
filter: `line_id=eq.${lineId}`
|
|
8541
|
+
},
|
|
8542
|
+
() => {
|
|
8543
|
+
fetchAndStoreShiftConfig(supabase, lineId, fallback).catch((err) => {
|
|
8544
|
+
console.error("[shiftConfigStore] Failed to refresh shift config", { lineId, err });
|
|
8545
|
+
});
|
|
8546
|
+
}
|
|
8547
|
+
).subscribe();
|
|
8548
|
+
subscriptionsByLineId.set(lineId, { supabase, channel, 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
|
+
|
|
8560
|
+
// src/lib/hooks/useLineShiftConfig.ts
|
|
8247
8561
|
var useLineShiftConfig = (lineId, fallbackConfig) => {
|
|
8248
|
-
const [shiftConfig, setShiftConfig] = useState(
|
|
8249
|
-
|
|
8562
|
+
const [shiftConfig, setShiftConfig] = useState(() => {
|
|
8563
|
+
if (!lineId || lineId === "factory" || lineId === "all") return fallbackConfig || null;
|
|
8564
|
+
return shiftConfigStore.get(lineId) || fallbackConfig || null;
|
|
8565
|
+
});
|
|
8566
|
+
const [isLoading, setIsLoading] = useState(() => {
|
|
8567
|
+
if (!lineId || lineId === "factory" || lineId === "all") return false;
|
|
8568
|
+
return !shiftConfigStore.get(lineId);
|
|
8569
|
+
});
|
|
8250
8570
|
const [error, setError] = useState(null);
|
|
8251
8571
|
const supabase = useSupabase();
|
|
8252
8572
|
useEffect(() => {
|
|
8253
8573
|
if (!lineId || lineId === "factory" || lineId === "all") {
|
|
8254
8574
|
setShiftConfig(fallbackConfig || null);
|
|
8255
8575
|
setIsLoading(false);
|
|
8576
|
+
setError(null);
|
|
8256
8577
|
return;
|
|
8257
8578
|
}
|
|
8258
8579
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -8260,98 +8581,49 @@ var useLineShiftConfig = (lineId, fallbackConfig) => {
|
|
|
8260
8581
|
console.warn(`[useShiftConfig] Invalid line ID format: ${lineId}, using fallback`);
|
|
8261
8582
|
setShiftConfig(fallbackConfig || null);
|
|
8262
8583
|
setIsLoading(false);
|
|
8584
|
+
setError(null);
|
|
8263
8585
|
return;
|
|
8264
8586
|
}
|
|
8265
8587
|
let mounted = true;
|
|
8266
|
-
const
|
|
8267
|
-
const
|
|
8268
|
-
|
|
8269
|
-
let startMinutes = sh * 60 + sm;
|
|
8270
|
-
let endMinutes = eh * 60 + em;
|
|
8271
|
-
if (endMinutes < startMinutes) {
|
|
8272
|
-
endMinutes += 24 * 60;
|
|
8273
|
-
}
|
|
8274
|
-
return endMinutes - startMinutes;
|
|
8588
|
+
const syncFromStore = () => {
|
|
8589
|
+
const stored = shiftConfigStore.get(lineId);
|
|
8590
|
+
setShiftConfig(stored || fallbackConfig || null);
|
|
8275
8591
|
};
|
|
8276
|
-
|
|
8592
|
+
syncFromStore();
|
|
8593
|
+
const unsubscribeStore = shiftConfigStore.subscribe((changedLineId) => {
|
|
8594
|
+
if (!mounted) return;
|
|
8595
|
+
if (changedLineId === "*" || changedLineId === lineId) {
|
|
8596
|
+
syncFromStore();
|
|
8597
|
+
}
|
|
8598
|
+
});
|
|
8599
|
+
const unsubscribeRealtime = ensureShiftConfigSubscription(supabase, lineId, fallbackConfig);
|
|
8600
|
+
const ensureLoaded = async () => {
|
|
8277
8601
|
try {
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
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);
|
|
8282
|
-
if (shiftsError) {
|
|
8283
|
-
console.error(`[useLineShiftConfig] \u274C Error fetching shifts:`, shiftsError);
|
|
8284
|
-
throw new Error(`Failed to fetch shift config: ${shiftsError.message}`);
|
|
8285
|
-
}
|
|
8286
|
-
if (!shiftsData || shiftsData.length === 0) {
|
|
8287
|
-
console.warn(`[useLineShiftConfig] \u26A0\uFE0F No shift config found for line ${lineId}, using fallback`);
|
|
8288
|
-
if (mounted) {
|
|
8289
|
-
setShiftConfig(fallbackConfig || null);
|
|
8290
|
-
setIsLoading(false);
|
|
8291
|
-
}
|
|
8602
|
+
const existing = shiftConfigStore.get(lineId);
|
|
8603
|
+
if (existing) {
|
|
8604
|
+
if (mounted) setIsLoading(false);
|
|
8292
8605
|
return;
|
|
8293
8606
|
}
|
|
8294
|
-
const stripSeconds = (timeStr) => {
|
|
8295
|
-
if (!timeStr) return timeStr;
|
|
8296
|
-
return timeStr.substring(0, 5);
|
|
8297
|
-
};
|
|
8298
|
-
const mapped = shiftsData.map((shift) => ({
|
|
8299
|
-
shiftId: shift.shift_id,
|
|
8300
|
-
shiftName: shift.shift_name || `Shift ${shift.shift_id}`,
|
|
8301
|
-
startTime: stripSeconds(shift.start_time),
|
|
8302
|
-
endTime: stripSeconds(shift.end_time),
|
|
8303
|
-
breaks: Array.isArray(shift.breaks) ? shift.breaks.map((b) => ({
|
|
8304
|
-
startTime: b.start || b.startTime || "00:00",
|
|
8305
|
-
endTime: b.end || b.endTime || "00:00",
|
|
8306
|
-
duration: calculateBreakDuration2(
|
|
8307
|
-
b.start || b.startTime || "00:00",
|
|
8308
|
-
b.end || b.endTime || "00:00"
|
|
8309
|
-
),
|
|
8310
|
-
remarks: b.remarks || b.name || ""
|
|
8311
|
-
})) : [],
|
|
8312
|
-
timezone: shift.timezone
|
|
8313
|
-
}));
|
|
8314
|
-
const day = mapped.find((s) => s.shiftId === 0);
|
|
8315
|
-
const night = mapped.find((s) => s.shiftId === 1);
|
|
8316
|
-
const config = {
|
|
8317
|
-
shifts: mapped,
|
|
8318
|
-
timezone: mapped[0]?.timezone,
|
|
8319
|
-
dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallbackConfig?.dayShift,
|
|
8320
|
-
nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallbackConfig?.nightShift,
|
|
8321
|
-
transitionPeriodMinutes: fallbackConfig?.transitionPeriodMinutes || 0
|
|
8322
|
-
};
|
|
8323
|
-
console.log(`[useLineShiftConfig] \u2705 Built config from DB:`, config);
|
|
8324
8607
|
if (mounted) {
|
|
8325
|
-
|
|
8326
|
-
|
|
8608
|
+
setIsLoading(true);
|
|
8609
|
+
setError(null);
|
|
8327
8610
|
}
|
|
8611
|
+
await fetchAndStoreShiftConfig(supabase, lineId, fallbackConfig);
|
|
8328
8612
|
} catch (err) {
|
|
8329
8613
|
console.error("[useShiftConfig] Error fetching shift config:", err);
|
|
8330
8614
|
if (mounted) {
|
|
8331
8615
|
setError(err instanceof Error ? err.message : "Unknown error occurred");
|
|
8332
|
-
setShiftConfig(fallbackConfig || null);
|
|
8333
|
-
setIsLoading(false);
|
|
8616
|
+
setShiftConfig(shiftConfigStore.get(lineId) || fallbackConfig || null);
|
|
8334
8617
|
}
|
|
8618
|
+
} finally {
|
|
8619
|
+
if (mounted) setIsLoading(false);
|
|
8335
8620
|
}
|
|
8336
8621
|
};
|
|
8337
|
-
|
|
8338
|
-
const subscription = supabase.channel(`shift_config_${lineId}`).on(
|
|
8339
|
-
"postgres_changes",
|
|
8340
|
-
{
|
|
8341
|
-
event: "*",
|
|
8342
|
-
// Listen to all events (INSERT, UPDATE, DELETE)
|
|
8343
|
-
schema: "public",
|
|
8344
|
-
table: "line_operating_hours",
|
|
8345
|
-
filter: `line_id=eq.${lineId}`
|
|
8346
|
-
},
|
|
8347
|
-
(payload) => {
|
|
8348
|
-
console.log("[useShiftConfig] Real-time update received:", payload);
|
|
8349
|
-
fetchShiftConfig();
|
|
8350
|
-
}
|
|
8351
|
-
).subscribe();
|
|
8622
|
+
ensureLoaded();
|
|
8352
8623
|
return () => {
|
|
8353
8624
|
mounted = false;
|
|
8354
|
-
|
|
8625
|
+
unsubscribeStore();
|
|
8626
|
+
unsubscribeRealtime();
|
|
8355
8627
|
};
|
|
8356
8628
|
}, [lineId, supabase]);
|
|
8357
8629
|
return {
|
|
@@ -8442,8 +8714,8 @@ var useLineWorkspaceMetrics = (lineId, options) => {
|
|
|
8442
8714
|
queryShiftId,
|
|
8443
8715
|
metricsTable
|
|
8444
8716
|
});
|
|
8445
|
-
const
|
|
8446
|
-
const enabledWorkspaceIds =
|
|
8717
|
+
const enabledWorkspaces = await workspaceService.getEnabledWorkspaces(lineId);
|
|
8718
|
+
const enabledWorkspaceIds = enabledWorkspaces.map((ws) => ws.id);
|
|
8447
8719
|
if (enabledWorkspaceIds.length === 0) {
|
|
8448
8720
|
setWorkspaces([]);
|
|
8449
8721
|
setInitialized(true);
|
|
@@ -8541,14 +8813,6 @@ var useHistoricWorkspaceMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
8541
8813
|
isFetchingRef.current = true;
|
|
8542
8814
|
setIsLoading(true);
|
|
8543
8815
|
setError(null);
|
|
8544
|
-
const { data: { session } } = await supabase.auth.getSession();
|
|
8545
|
-
if (!session?.access_token) {
|
|
8546
|
-
throw new Error("No authentication token available");
|
|
8547
|
-
}
|
|
8548
|
-
const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
8549
|
-
if (!apiUrl) {
|
|
8550
|
-
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
8551
|
-
}
|
|
8552
8816
|
const params = new URLSearchParams({
|
|
8553
8817
|
company_id: entityConfig.companyId || "",
|
|
8554
8818
|
date
|
|
@@ -8556,20 +8820,10 @@ var useHistoricWorkspaceMetrics = (workspaceId, date, shiftId, options) => {
|
|
|
8556
8820
|
if (shiftId !== void 0) {
|
|
8557
8821
|
params.append("shift_id", shiftId.toString());
|
|
8558
8822
|
}
|
|
8559
|
-
const
|
|
8560
|
-
|
|
8561
|
-
{
|
|
8562
|
-
headers: {
|
|
8563
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
8564
|
-
"Content-Type": "application/json"
|
|
8565
|
-
}
|
|
8566
|
-
}
|
|
8823
|
+
const data = await fetchBackendJson(
|
|
8824
|
+
supabase,
|
|
8825
|
+
`/api/dashboard/workspace/${workspaceId}/metrics?${params.toString()}`
|
|
8567
8826
|
);
|
|
8568
|
-
if (!response.ok) {
|
|
8569
|
-
const errorText = await response.text();
|
|
8570
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
8571
|
-
}
|
|
8572
|
-
const data = await response.json();
|
|
8573
8827
|
const fetchedMetrics = data.metrics;
|
|
8574
8828
|
if (!fetchedMetrics) {
|
|
8575
8829
|
setMetrics(null);
|
|
@@ -8789,14 +9043,6 @@ var useLeaderboardMetrics = (date, shiftId, limit = 10, filter2 = "all") => {
|
|
|
8789
9043
|
const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
|
|
8790
9044
|
const queryDate = date || currentShift.date;
|
|
8791
9045
|
const queryShiftId = shiftId !== void 0 ? shiftId : currentShift.shiftId;
|
|
8792
|
-
const { data: { session } } = await supabase.auth.getSession();
|
|
8793
|
-
if (!session?.access_token) {
|
|
8794
|
-
throw new Error("No authentication token available");
|
|
8795
|
-
}
|
|
8796
|
-
const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
8797
|
-
if (!apiUrl) {
|
|
8798
|
-
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
8799
|
-
}
|
|
8800
9046
|
const params = new URLSearchParams({
|
|
8801
9047
|
date: queryDate,
|
|
8802
9048
|
shift_id: queryShiftId.toString(),
|
|
@@ -8804,20 +9050,10 @@ var useLeaderboardMetrics = (date, shiftId, limit = 10, filter2 = "all") => {
|
|
|
8804
9050
|
limit: limit.toString(),
|
|
8805
9051
|
filter: filter2
|
|
8806
9052
|
});
|
|
8807
|
-
const
|
|
8808
|
-
|
|
8809
|
-
{
|
|
8810
|
-
headers: {
|
|
8811
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
8812
|
-
"Content-Type": "application/json"
|
|
8813
|
-
}
|
|
8814
|
-
}
|
|
9053
|
+
const data = await fetchBackendJson(
|
|
9054
|
+
supabase,
|
|
9055
|
+
`/api/dashboard/leaderboard?${params.toString()}`
|
|
8815
9056
|
);
|
|
8816
|
-
if (!response.ok) {
|
|
8817
|
-
const errorText = await response.text();
|
|
8818
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
8819
|
-
}
|
|
8820
|
-
const data = await response.json();
|
|
8821
9057
|
setLeaderboard(data.leaderboard || []);
|
|
8822
9058
|
} catch (err) {
|
|
8823
9059
|
console.error("[useLeaderboardMetrics] Error fetching leaderboard:", err);
|
|
@@ -8838,27 +9074,201 @@ var useLeaderboardMetrics = (date, shiftId, limit = 10, filter2 = "all") => {
|
|
|
8838
9074
|
refetch: fetchLeaderboard
|
|
8839
9075
|
};
|
|
8840
9076
|
};
|
|
9077
|
+
var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
|
|
9078
|
+
const [shiftConfigMap, setShiftConfigMap] = useState(/* @__PURE__ */ new Map());
|
|
9079
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
9080
|
+
const [error, setError] = useState(null);
|
|
9081
|
+
const supabase = useSupabase();
|
|
9082
|
+
const lineIdsKey = useMemo(() => lineIds.slice().sort().join(","), [lineIds]);
|
|
9083
|
+
useEffect(() => {
|
|
9084
|
+
if (!lineIds || lineIds.length === 0) {
|
|
9085
|
+
setShiftConfigMap(/* @__PURE__ */ new Map());
|
|
9086
|
+
setIsLoading(false);
|
|
9087
|
+
setError(null);
|
|
9088
|
+
return;
|
|
9089
|
+
}
|
|
9090
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9091
|
+
const validLineIds = lineIds.filter((id3) => uuidRegex.test(id3));
|
|
9092
|
+
if (validLineIds.length === 0) {
|
|
9093
|
+
console.warn("[useMultiLineShiftConfigs] No valid line IDs provided");
|
|
9094
|
+
setShiftConfigMap(/* @__PURE__ */ new Map());
|
|
9095
|
+
setIsLoading(false);
|
|
9096
|
+
setError(null);
|
|
9097
|
+
return;
|
|
9098
|
+
}
|
|
9099
|
+
let mounted = true;
|
|
9100
|
+
const syncFromStore = (changedLineId) => {
|
|
9101
|
+
setShiftConfigMap((prev) => {
|
|
9102
|
+
const next = new Map(prev);
|
|
9103
|
+
const updateLine = (lineId) => {
|
|
9104
|
+
const config = shiftConfigStore.get(lineId) || fallbackConfig;
|
|
9105
|
+
if (config) next.set(lineId, config);
|
|
9106
|
+
};
|
|
9107
|
+
if (!changedLineId || changedLineId === "*") {
|
|
9108
|
+
validLineIds.forEach(updateLine);
|
|
9109
|
+
} else if (validLineIds.includes(changedLineId)) {
|
|
9110
|
+
updateLine(changedLineId);
|
|
9111
|
+
}
|
|
9112
|
+
return next;
|
|
9113
|
+
});
|
|
9114
|
+
};
|
|
9115
|
+
const initialMap = /* @__PURE__ */ new Map();
|
|
9116
|
+
validLineIds.forEach((lineId) => {
|
|
9117
|
+
const config = shiftConfigStore.get(lineId) || fallbackConfig;
|
|
9118
|
+
if (config) initialMap.set(lineId, config);
|
|
9119
|
+
});
|
|
9120
|
+
setShiftConfigMap(initialMap);
|
|
9121
|
+
const unsubscribeStore = shiftConfigStore.subscribe((changedLineId) => {
|
|
9122
|
+
if (!mounted) return;
|
|
9123
|
+
if (changedLineId === "*" || validLineIds.includes(changedLineId)) {
|
|
9124
|
+
syncFromStore(changedLineId);
|
|
9125
|
+
}
|
|
9126
|
+
});
|
|
9127
|
+
const unsubscribeRealtimeList = validLineIds.map(
|
|
9128
|
+
(lineId) => ensureShiftConfigSubscription(supabase, lineId, fallbackConfig)
|
|
9129
|
+
);
|
|
9130
|
+
const fetchAllConfigs = async () => {
|
|
9131
|
+
try {
|
|
9132
|
+
const missingLineIds = validLineIds.filter((lineId) => !shiftConfigStore.get(lineId));
|
|
9133
|
+
if (missingLineIds.length === 0) {
|
|
9134
|
+
if (mounted) {
|
|
9135
|
+
setIsLoading(false);
|
|
9136
|
+
setError(null);
|
|
9137
|
+
}
|
|
9138
|
+
return;
|
|
9139
|
+
}
|
|
9140
|
+
if (mounted) {
|
|
9141
|
+
setIsLoading(true);
|
|
9142
|
+
setError(null);
|
|
9143
|
+
}
|
|
9144
|
+
console.log(`[useMultiLineShiftConfigs] Fetching shift configs for ${missingLineIds.length} lines`);
|
|
9145
|
+
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);
|
|
9146
|
+
if (fetchError) {
|
|
9147
|
+
console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
|
|
9148
|
+
throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
|
|
9149
|
+
}
|
|
9150
|
+
const lineShiftsMap = /* @__PURE__ */ new Map();
|
|
9151
|
+
data?.forEach((row) => {
|
|
9152
|
+
if (!lineShiftsMap.has(row.line_id)) {
|
|
9153
|
+
lineShiftsMap.set(row.line_id, []);
|
|
9154
|
+
}
|
|
9155
|
+
lineShiftsMap.get(row.line_id).push(row);
|
|
9156
|
+
});
|
|
9157
|
+
lineShiftsMap.forEach((shifts, lineId) => {
|
|
9158
|
+
shiftConfigStore.setFromOperatingHoursRows(lineId, shifts, fallbackConfig);
|
|
9159
|
+
});
|
|
9160
|
+
missingLineIds.forEach((lineId) => {
|
|
9161
|
+
if (!lineShiftsMap.has(lineId) && fallbackConfig) {
|
|
9162
|
+
console.warn(`[useMultiLineShiftConfigs] No config found for line ${lineId}, using fallback`);
|
|
9163
|
+
shiftConfigStore.set(lineId, fallbackConfig);
|
|
9164
|
+
}
|
|
9165
|
+
});
|
|
9166
|
+
console.log(`[useMultiLineShiftConfigs] Stored configs for ${lineShiftsMap.size} lines`);
|
|
9167
|
+
if (mounted) {
|
|
9168
|
+
setIsLoading(false);
|
|
9169
|
+
}
|
|
9170
|
+
} catch (err) {
|
|
9171
|
+
console.error("[useMultiLineShiftConfigs] Error:", err);
|
|
9172
|
+
if (mounted) {
|
|
9173
|
+
setError(err instanceof Error ? err.message : "Failed to fetch shift configs");
|
|
9174
|
+
setIsLoading(false);
|
|
9175
|
+
}
|
|
9176
|
+
}
|
|
9177
|
+
};
|
|
9178
|
+
fetchAllConfigs();
|
|
9179
|
+
return () => {
|
|
9180
|
+
mounted = false;
|
|
9181
|
+
unsubscribeStore();
|
|
9182
|
+
unsubscribeRealtimeList.forEach((unsub) => unsub());
|
|
9183
|
+
};
|
|
9184
|
+
}, [lineIdsKey, supabase]);
|
|
9185
|
+
return {
|
|
9186
|
+
shiftConfigMap,
|
|
9187
|
+
isLoading,
|
|
9188
|
+
error
|
|
9189
|
+
};
|
|
9190
|
+
};
|
|
9191
|
+
|
|
9192
|
+
// src/lib/utils/shiftGrouping.ts
|
|
9193
|
+
var getCurrentShiftForLine = (lineId, shiftConfig, timezone, now2 = /* @__PURE__ */ new Date()) => {
|
|
9194
|
+
const currentShift = getCurrentShift(timezone, shiftConfig, now2);
|
|
9195
|
+
return {
|
|
9196
|
+
lineId,
|
|
9197
|
+
shiftId: currentShift.shiftId,
|
|
9198
|
+
date: currentShift.date,
|
|
9199
|
+
shiftName: currentShift.shiftName || `Shift ${currentShift.shiftId}`
|
|
9200
|
+
};
|
|
9201
|
+
};
|
|
9202
|
+
var groupLinesByShift = (shiftConfigMap, timezone, now2 = /* @__PURE__ */ new Date()) => {
|
|
9203
|
+
const lineShiftInfos = [];
|
|
9204
|
+
shiftConfigMap.forEach((shiftConfig, lineId) => {
|
|
9205
|
+
const info = getCurrentShiftForLine(lineId, shiftConfig, timezone, now2);
|
|
9206
|
+
lineShiftInfos.push(info);
|
|
9207
|
+
});
|
|
9208
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
9209
|
+
lineShiftInfos.forEach((info) => {
|
|
9210
|
+
const key = `${info.shiftId}-${info.date}`;
|
|
9211
|
+
if (!groupMap.has(key)) {
|
|
9212
|
+
groupMap.set(key, {
|
|
9213
|
+
shiftId: info.shiftId,
|
|
9214
|
+
date: info.date,
|
|
9215
|
+
shiftName: info.shiftName,
|
|
9216
|
+
lineIds: []
|
|
9217
|
+
});
|
|
9218
|
+
}
|
|
9219
|
+
groupMap.get(key).lineIds.push(info.lineId);
|
|
9220
|
+
});
|
|
9221
|
+
return Array.from(groupMap.values()).sort((a, b) => a.shiftId - b.shiftId);
|
|
9222
|
+
};
|
|
9223
|
+
var areAllLinesOnSameShift = (shiftConfigMap, timezone, now2 = /* @__PURE__ */ new Date()) => {
|
|
9224
|
+
if (shiftConfigMap.size <= 1) return true;
|
|
9225
|
+
const groups = groupLinesByShift(shiftConfigMap, timezone, now2);
|
|
9226
|
+
return groups.length === 1;
|
|
9227
|
+
};
|
|
9228
|
+
var getUniformShiftGroup = (shiftConfigMap, timezone, now2 = /* @__PURE__ */ new Date()) => {
|
|
9229
|
+
const groups = groupLinesByShift(shiftConfigMap, timezone, now2);
|
|
9230
|
+
return groups.length === 1 ? groups[0] : null;
|
|
9231
|
+
};
|
|
9232
|
+
|
|
9233
|
+
// src/lib/hooks/useDashboardMetrics.ts
|
|
8841
9234
|
var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds }) => {
|
|
8842
9235
|
const { supabaseUrl, supabaseKey } = useDashboardConfig();
|
|
8843
9236
|
const entityConfig = useEntityConfig();
|
|
8844
9237
|
const databaseConfig = useDatabaseConfig();
|
|
8845
9238
|
const dateTimeConfig = useDateTimeConfig();
|
|
8846
9239
|
const isFactoryView = lineId === (entityConfig.factoryViewId || "factory");
|
|
8847
|
-
const
|
|
8848
|
-
|
|
8849
|
-
|
|
9240
|
+
const appTimezone = useAppTimezone();
|
|
9241
|
+
const defaultTimezone = appTimezone || dateTimeConfig?.defaultTimezone || "UTC";
|
|
9242
|
+
const configuredLineIds = useMemo(() => {
|
|
9243
|
+
return getConfiguredLineIds(entityConfig);
|
|
8850
9244
|
}, [entityConfig]);
|
|
8851
|
-
const
|
|
8852
|
-
const {
|
|
9245
|
+
const { shiftConfig: staticShiftConfig } = useDashboardConfig();
|
|
9246
|
+
const {
|
|
9247
|
+
shiftConfigMap: multiLineShiftConfigMap,
|
|
9248
|
+
isLoading: isMultiLineShiftConfigLoading
|
|
9249
|
+
} = useMultiLineShiftConfigs(
|
|
9250
|
+
isFactoryView ? configuredLineIds : [],
|
|
9251
|
+
staticShiftConfig
|
|
9252
|
+
);
|
|
9253
|
+
const {
|
|
9254
|
+
shiftConfig: singleLineShiftConfig,
|
|
9255
|
+
isLoading: isSingleLineShiftConfigLoading,
|
|
9256
|
+
isFromDatabase
|
|
9257
|
+
} = useDynamicShiftConfig(isFactoryView ? void 0 : lineId);
|
|
9258
|
+
const shiftLoading = isFactoryView ? isMultiLineShiftConfigLoading : isSingleLineShiftConfigLoading;
|
|
9259
|
+
const shiftGroups = useMemo(() => {
|
|
9260
|
+
if (!isFactoryView) return [];
|
|
9261
|
+
if (isMultiLineShiftConfigLoading || multiLineShiftConfigMap.size === 0) return [];
|
|
9262
|
+
return groupLinesByShift(multiLineShiftConfigMap, defaultTimezone);
|
|
9263
|
+
}, [isFactoryView, isMultiLineShiftConfigLoading, multiLineShiftConfigMap, defaultTimezone]);
|
|
9264
|
+
const shiftConfig = isFactoryView ? null : singleLineShiftConfig;
|
|
8853
9265
|
console.log(`[useDashboardMetrics] \u{1F3AF} Shift config for line ${lineId}:`, {
|
|
8854
9266
|
isFactoryView,
|
|
8855
|
-
lineIdForShiftConfig,
|
|
8856
|
-
firstLineId,
|
|
8857
9267
|
isFromDatabase,
|
|
8858
|
-
shiftConfig: shiftConfig ? { shifts: shiftConfig.shifts?.length, timezone: shiftConfig.timezone } : null
|
|
9268
|
+
shiftConfig: shiftConfig ? { shifts: shiftConfig.shifts?.length, timezone: shiftConfig.timezone } : null,
|
|
9269
|
+
shiftGroupsCount: shiftGroups.length,
|
|
9270
|
+
shiftGroups: shiftGroups.map((g) => ({ shiftId: g.shiftId, date: g.date, lineCount: g.lineIds.length }))
|
|
8859
9271
|
});
|
|
8860
|
-
const appTimezone = useAppTimezone();
|
|
8861
|
-
const defaultTimezone = appTimezone || dateTimeConfig?.defaultTimezone || "UTC";
|
|
8862
9272
|
const configuredLineMetricsTable = databaseConfig?.tables?.lineMetrics ?? "line_metrics";
|
|
8863
9273
|
const schema = databaseConfig?.schema ?? "public";
|
|
8864
9274
|
const supabase = useSupabase();
|
|
@@ -8895,16 +9305,6 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
8895
9305
|
setIsLoading(true);
|
|
8896
9306
|
setError(null);
|
|
8897
9307
|
try {
|
|
8898
|
-
const currentShiftDetails = getCurrentShift(defaultTimezone, shiftConfig);
|
|
8899
|
-
const operationalDate = getOperationalDate(defaultTimezone);
|
|
8900
|
-
const targetLineIds = currentLineIdToUse === (entityConfig.factoryViewId || "factory") ? userAccessibleLineIds || getConfiguredLineIds(entityConfig) : [currentLineIdToUse];
|
|
8901
|
-
if (targetLineIds.length === 0 && currentLineIdToUse === (entityConfig.factoryViewId || "factory")) {
|
|
8902
|
-
throw new Error("Factory view selected, but no lines are configured in entityConfig.");
|
|
8903
|
-
}
|
|
8904
|
-
if (targetLineIds.length === 0) {
|
|
8905
|
-
throw new Error("No target line IDs available for fetching metrics.");
|
|
8906
|
-
}
|
|
8907
|
-
const isFactoryView2 = currentLineIdToUse === (entityConfig.factoryViewId || "factory");
|
|
8908
9308
|
const { data: { session } } = await supabase.auth.getSession();
|
|
8909
9309
|
console.log("[useDashboardMetrics] Session check:", {
|
|
8910
9310
|
hasSession: !!session,
|
|
@@ -8918,42 +9318,98 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
8918
9318
|
if (!apiUrl) {
|
|
8919
9319
|
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
8920
9320
|
}
|
|
8921
|
-
const
|
|
8922
|
-
const
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
lineIdsParam,
|
|
8927
|
-
operationalDate,
|
|
8928
|
-
shiftId: currentShiftDetails.shiftId,
|
|
8929
|
-
companyId: entityConfig.companyId
|
|
8930
|
-
});
|
|
8931
|
-
const response = await fetch(url, {
|
|
8932
|
-
method: "GET",
|
|
8933
|
-
headers: {
|
|
8934
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
8935
|
-
"Content-Type": "application/json"
|
|
8936
|
-
}
|
|
8937
|
-
});
|
|
8938
|
-
console.log("[useDashboardMetrics] Response status:", response.status, response.statusText);
|
|
8939
|
-
if (!response.ok) {
|
|
8940
|
-
const errorText = await response.text();
|
|
8941
|
-
console.error("[useDashboardMetrics] Backend API error response:", errorText);
|
|
8942
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
9321
|
+
const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
|
|
9322
|
+
const isFactory = currentLineIdToUse === factoryViewIdentifier;
|
|
9323
|
+
const targetLineIds = isFactory ? userAccessibleLineIds || configuredLineIds : [currentLineIdToUse];
|
|
9324
|
+
if (targetLineIds.length === 0) {
|
|
9325
|
+
throw new Error("No target line IDs available for fetching metrics.");
|
|
8943
9326
|
}
|
|
8944
|
-
|
|
8945
|
-
let
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
9327
|
+
let allWorkspaceMetrics = [];
|
|
9328
|
+
let allLineMetrics = [];
|
|
9329
|
+
if (isFactory && shiftGroups.length > 0) {
|
|
9330
|
+
console.log(`[useDashboardMetrics] \u{1F3ED} Factory view: Fetching for ${shiftGroups.length} shift group(s)`);
|
|
9331
|
+
const metricsPromises = shiftGroups.map(async (group) => {
|
|
9332
|
+
const lineIdsParam = `line_ids=${group.lineIds.join(",")}`;
|
|
9333
|
+
const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${group.date}&shift_id=${group.shiftId}&company_id=${entityConfig.companyId}`;
|
|
9334
|
+
console.log(`[useDashboardMetrics] \u{1F4CA} Fetching for shift ${group.shiftId} (${group.shiftName}):`, {
|
|
9335
|
+
lineIds: group.lineIds,
|
|
9336
|
+
date: group.date
|
|
9337
|
+
});
|
|
9338
|
+
const response = await fetch(url, {
|
|
9339
|
+
method: "GET",
|
|
9340
|
+
headers: {
|
|
9341
|
+
"Authorization": `Bearer ${session.access_token}`,
|
|
9342
|
+
"Content-Type": "application/json"
|
|
9343
|
+
}
|
|
9344
|
+
});
|
|
9345
|
+
if (!response.ok) {
|
|
9346
|
+
const errorText = await response.text();
|
|
9347
|
+
console.error(`[useDashboardMetrics] Backend API error for shift ${group.shiftId}:`, errorText);
|
|
9348
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
9349
|
+
}
|
|
9350
|
+
const responseText = await response.text();
|
|
9351
|
+
try {
|
|
9352
|
+
return JSON.parse(responseText);
|
|
9353
|
+
} catch (parseError) {
|
|
9354
|
+
console.error("[useDashboardMetrics] Failed to parse response:", responseText.substring(0, 500));
|
|
9355
|
+
throw new Error(`Invalid JSON response from backend`);
|
|
9356
|
+
}
|
|
9357
|
+
});
|
|
9358
|
+
const results = await Promise.all(metricsPromises);
|
|
9359
|
+
results.forEach((result) => {
|
|
9360
|
+
if (result.workspace_metrics) {
|
|
9361
|
+
allWorkspaceMetrics.push(...result.workspace_metrics);
|
|
9362
|
+
}
|
|
9363
|
+
if (result.line_metrics) {
|
|
9364
|
+
allLineMetrics.push(...result.line_metrics);
|
|
9365
|
+
}
|
|
9366
|
+
});
|
|
9367
|
+
console.log(`[useDashboardMetrics] \u{1F4CA} Merged metrics from ${results.length} shift groups:`, {
|
|
9368
|
+
workspaceCount: allWorkspaceMetrics.length,
|
|
9369
|
+
lineMetricsCount: allLineMetrics.length
|
|
9370
|
+
});
|
|
9371
|
+
} else {
|
|
9372
|
+
const currentShiftDetails = shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : shiftGroups.length === 1 ? { date: shiftGroups[0].date, shiftId: shiftGroups[0].shiftId } : getCurrentShift(defaultTimezone, staticShiftConfig);
|
|
9373
|
+
const operationalDate = currentShiftDetails.date;
|
|
9374
|
+
const lineIdsParam = isFactory ? `line_ids=${targetLineIds.join(",")}` : `line_id=${targetLineIds[0]}`;
|
|
9375
|
+
const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${operationalDate}&shift_id=${currentShiftDetails.shiftId}&company_id=${entityConfig.companyId}`;
|
|
9376
|
+
console.log("[useDashboardMetrics] Calling backend API:", {
|
|
9377
|
+
url,
|
|
9378
|
+
apiUrl,
|
|
9379
|
+
lineIdsParam,
|
|
9380
|
+
operationalDate,
|
|
9381
|
+
shiftId: currentShiftDetails.shiftId,
|
|
9382
|
+
companyId: entityConfig.companyId
|
|
9383
|
+
});
|
|
9384
|
+
const response = await fetch(url, {
|
|
9385
|
+
method: "GET",
|
|
9386
|
+
headers: {
|
|
9387
|
+
"Authorization": `Bearer ${session.access_token}`,
|
|
9388
|
+
"Content-Type": "application/json"
|
|
9389
|
+
}
|
|
9390
|
+
});
|
|
9391
|
+
console.log("[useDashboardMetrics] Response status:", response.status, response.statusText);
|
|
9392
|
+
if (!response.ok) {
|
|
9393
|
+
const errorText = await response.text();
|
|
9394
|
+
console.error("[useDashboardMetrics] Backend API error response:", errorText);
|
|
9395
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
9396
|
+
}
|
|
9397
|
+
const responseText = await response.text();
|
|
9398
|
+
let backendData;
|
|
9399
|
+
try {
|
|
9400
|
+
backendData = JSON.parse(responseText);
|
|
9401
|
+
} catch (parseError) {
|
|
9402
|
+
console.error("[useDashboardMetrics] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
|
|
9403
|
+
throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
|
|
9404
|
+
}
|
|
9405
|
+
console.log("[useDashboardMetrics] Backend response:", {
|
|
9406
|
+
workspaceCount: backendData.workspace_metrics?.length || 0,
|
|
9407
|
+
lineCount: backendData.line_metrics?.length || 0
|
|
9408
|
+
});
|
|
9409
|
+
allWorkspaceMetrics = backendData.workspace_metrics || [];
|
|
9410
|
+
allLineMetrics = backendData.line_metrics || [];
|
|
8951
9411
|
}
|
|
8952
|
-
|
|
8953
|
-
workspaceCount: backendData.workspace_metrics?.length || 0,
|
|
8954
|
-
lineCount: backendData.line_metrics?.length || 0
|
|
8955
|
-
});
|
|
8956
|
-
const transformedWorkspaceData = (backendData.workspace_metrics || []).map((item) => ({
|
|
9412
|
+
const transformedWorkspaceData = allWorkspaceMetrics.map((item) => ({
|
|
8957
9413
|
company_id: item.company_id || entityConfig.companyId,
|
|
8958
9414
|
line_id: item.line_id,
|
|
8959
9415
|
shift_id: item.shift_id,
|
|
@@ -8976,7 +9432,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
8976
9432
|
});
|
|
8977
9433
|
const newMetricsState = {
|
|
8978
9434
|
workspaceMetrics: transformedWorkspaceData,
|
|
8979
|
-
lineMetrics:
|
|
9435
|
+
lineMetrics: allLineMetrics || []
|
|
8980
9436
|
};
|
|
8981
9437
|
console.log("[useDashboardMetrics] Setting metrics state:", {
|
|
8982
9438
|
workspaceMetrics: newMetricsState.workspaceMetrics.length,
|
|
@@ -8997,9 +9453,12 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
8997
9453
|
metrics2?.lineMetrics?.length || 0,
|
|
8998
9454
|
companySpecificMetricsTable,
|
|
8999
9455
|
entityConfig,
|
|
9000
|
-
appTimezone,
|
|
9001
9456
|
defaultTimezone,
|
|
9002
9457
|
shiftConfig,
|
|
9458
|
+
shiftGroups,
|
|
9459
|
+
configuredLineIds,
|
|
9460
|
+
staticShiftConfig,
|
|
9461
|
+
userAccessibleLineIds,
|
|
9003
9462
|
shiftLoading
|
|
9004
9463
|
]);
|
|
9005
9464
|
const fetchAllMetricsRef = useRef(fetchAllMetrics);
|
|
@@ -9023,32 +9482,70 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
9023
9482
|
if (!currentLineIdToUse || !supabase || companySpecificMetricsTable.includes("unknown_company") || !entityConfig.companyId) {
|
|
9024
9483
|
return;
|
|
9025
9484
|
}
|
|
9026
|
-
const
|
|
9027
|
-
const
|
|
9028
|
-
const targetLineIds = currentLineIdToUse === (entityConfig.factoryViewId || "factory") ? userAccessibleLineIds || getConfiguredLineIds(entityConfig) : [currentLineIdToUse];
|
|
9029
|
-
if (targetLineIds.length === 0) return;
|
|
9030
|
-
const wsMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
9031
|
-
const lineMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
9485
|
+
const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
|
|
9486
|
+
const isFactory = currentLineIdToUse === factoryViewIdentifier;
|
|
9032
9487
|
const channels = [];
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
{
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9488
|
+
if (isFactory && shiftGroups.length > 0) {
|
|
9489
|
+
console.log(`[useDashboardMetrics] \u{1F4E1} Setting up subscriptions for ${shiftGroups.length} shift group(s)`);
|
|
9490
|
+
shiftGroups.forEach((group, index) => {
|
|
9491
|
+
const baseFilterParts = `date=eq.${group.date},shift_id=eq.${group.shiftId}`;
|
|
9492
|
+
const lineIdFilterPart = `line_id=in.(${group.lineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
9493
|
+
const filter2 = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9494
|
+
const wsChannelName = `dashboard-ws-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9495
|
+
const wsChannel = supabase.channel(wsChannelName).on(
|
|
9496
|
+
"postgres_changes",
|
|
9497
|
+
{ event: "*", schema, table: companySpecificMetricsTable, filter: filter2 },
|
|
9498
|
+
(payload) => {
|
|
9499
|
+
const payloadData = payload.new || payload.old;
|
|
9500
|
+
if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
|
|
9501
|
+
queueUpdate();
|
|
9502
|
+
}
|
|
9042
9503
|
}
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
9051
|
-
|
|
9504
|
+
).subscribe();
|
|
9505
|
+
channels.push(wsChannel);
|
|
9506
|
+
const lmChannelName = `dashboard-lm-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9507
|
+
const lmChannel = supabase.channel(lmChannelName).on(
|
|
9508
|
+
"postgres_changes",
|
|
9509
|
+
{ event: "*", schema, table: configuredLineMetricsTable, filter: filter2 },
|
|
9510
|
+
(payload) => {
|
|
9511
|
+
const payloadData = payload.new || payload.old;
|
|
9512
|
+
if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
|
|
9513
|
+
queueUpdate();
|
|
9514
|
+
onLineMetricsUpdateRef.current?.();
|
|
9515
|
+
}
|
|
9516
|
+
}
|
|
9517
|
+
).subscribe();
|
|
9518
|
+
channels.push(lmChannel);
|
|
9519
|
+
});
|
|
9520
|
+
} else {
|
|
9521
|
+
const currentShiftDetails = shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : getCurrentShift(defaultTimezone, staticShiftConfig);
|
|
9522
|
+
const operationalDateForSubscription = currentShiftDetails.date;
|
|
9523
|
+
const targetLineIds = [currentLineIdToUse];
|
|
9524
|
+
if (targetLineIds.length === 0) return;
|
|
9525
|
+
const baseFilterParts = `date=eq.${operationalDateForSubscription},shift_id=eq.${currentShiftDetails.shiftId}`;
|
|
9526
|
+
const lineIdFilterPart = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
9527
|
+
const wsMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9528
|
+
const lineMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9529
|
+
const createSubscription = (table, filter2, channelNameBase, callback) => {
|
|
9530
|
+
const channelName = `${channelNameBase}-${Date.now()}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9531
|
+
const channel = supabase.channel(channelName).on(
|
|
9532
|
+
"postgres_changes",
|
|
9533
|
+
{ event: "*", schema, table, filter: filter2 },
|
|
9534
|
+
(payload) => {
|
|
9535
|
+
const payloadData = payload.new;
|
|
9536
|
+
if (payloadData?.date === operationalDateForSubscription && payloadData?.shift_id === currentShiftDetails.shiftId) {
|
|
9537
|
+
callback();
|
|
9538
|
+
}
|
|
9539
|
+
}
|
|
9540
|
+
).subscribe();
|
|
9541
|
+
channels.push(channel);
|
|
9542
|
+
};
|
|
9543
|
+
createSubscription(companySpecificMetricsTable, wsMetricsFilter, "dashboard-ws-metrics", queueUpdate);
|
|
9544
|
+
createSubscription(configuredLineMetricsTable, lineMetricsFilter, "dashboard-line-metrics", () => {
|
|
9545
|
+
queueUpdate();
|
|
9546
|
+
onLineMetricsUpdateRef.current?.();
|
|
9547
|
+
});
|
|
9548
|
+
}
|
|
9052
9549
|
return () => {
|
|
9053
9550
|
channels.forEach((channel) => {
|
|
9054
9551
|
supabase?.removeChannel(channel);
|
|
@@ -9062,9 +9559,10 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
|
|
|
9062
9559
|
schema,
|
|
9063
9560
|
entityConfig?.companyId,
|
|
9064
9561
|
entityConfig?.factoryViewId,
|
|
9065
|
-
appTimezone,
|
|
9066
9562
|
defaultTimezone,
|
|
9067
9563
|
shiftConfig,
|
|
9564
|
+
staticShiftConfig,
|
|
9565
|
+
shiftGroups,
|
|
9068
9566
|
lineId,
|
|
9069
9567
|
userAccessibleLineIds
|
|
9070
9568
|
]);
|
|
@@ -9082,20 +9580,37 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9082
9580
|
const isFactoryView = lineId === (entityConfig.factoryViewId || "factory");
|
|
9083
9581
|
const databaseConfig = useDatabaseConfig();
|
|
9084
9582
|
const dateTimeConfig = useDateTimeConfig();
|
|
9085
|
-
useShiftConfig();
|
|
9086
|
-
const
|
|
9087
|
-
|
|
9088
|
-
|
|
9583
|
+
const staticShiftConfig = useShiftConfig();
|
|
9584
|
+
const appTimezone = useAppTimezone();
|
|
9585
|
+
const timezone = appTimezone || dateTimeConfig.defaultTimezone || "UTC";
|
|
9586
|
+
const configuredLineIds = useMemo(() => {
|
|
9587
|
+
return getConfiguredLineIds(entityConfig);
|
|
9089
9588
|
}, [entityConfig]);
|
|
9090
|
-
const
|
|
9091
|
-
|
|
9092
|
-
|
|
9589
|
+
const {
|
|
9590
|
+
shiftConfigMap: multiLineShiftConfigMap,
|
|
9591
|
+
isLoading: isMultiLineShiftConfigLoading
|
|
9592
|
+
} = useMultiLineShiftConfigs(
|
|
9593
|
+
isFactoryView ? configuredLineIds : [],
|
|
9594
|
+
staticShiftConfig
|
|
9595
|
+
);
|
|
9596
|
+
const {
|
|
9597
|
+
shiftConfig: singleLineShiftConfig,
|
|
9598
|
+
isFromDatabase,
|
|
9599
|
+
isLoading: isSingleLineShiftConfigLoading
|
|
9600
|
+
} = useDynamicShiftConfig(isFactoryView ? void 0 : lineId);
|
|
9601
|
+
const isShiftConfigLoading = isFactoryView ? isMultiLineShiftConfigLoading : isSingleLineShiftConfigLoading;
|
|
9602
|
+
const shiftGroups = useMemo(() => {
|
|
9603
|
+
if (!isFactoryView) return [];
|
|
9604
|
+
if (isMultiLineShiftConfigLoading || multiLineShiftConfigMap.size === 0) return [];
|
|
9605
|
+
return groupLinesByShift(multiLineShiftConfigMap, timezone);
|
|
9606
|
+
}, [isFactoryView, isMultiLineShiftConfigLoading, multiLineShiftConfigMap, timezone]);
|
|
9607
|
+
const shiftConfig = isFactoryView ? null : singleLineShiftConfig;
|
|
9093
9608
|
console.log(`[useLineKPIs] \u{1F3AF} Shift config for line ${lineId}:`, {
|
|
9094
9609
|
isFactoryView,
|
|
9095
|
-
lineIdForShiftConfig,
|
|
9096
|
-
firstLineId,
|
|
9097
9610
|
isFromDatabase,
|
|
9098
|
-
shiftConfig
|
|
9611
|
+
shiftConfig,
|
|
9612
|
+
shiftGroupsCount: shiftGroups.length,
|
|
9613
|
+
shiftGroups: shiftGroups.map((g) => ({ shiftId: g.shiftId, date: g.date, lineCount: g.lineIds.length }))
|
|
9099
9614
|
});
|
|
9100
9615
|
const supabase = useSupabase();
|
|
9101
9616
|
useMemo(() => {
|
|
@@ -9109,8 +9624,6 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9109
9624
|
const updateQueueRef = useRef(false);
|
|
9110
9625
|
const updateTimeoutRef = useRef(null);
|
|
9111
9626
|
const queueUpdateRef = useRef(void 0);
|
|
9112
|
-
const appTimezone = useAppTimezone();
|
|
9113
|
-
const timezone = appTimezone || dateTimeConfig.defaultTimezone || "UTC";
|
|
9114
9627
|
const schema = databaseConfig.schema ?? "public";
|
|
9115
9628
|
const lineMetricsTable = databaseConfig.tables?.lineMetrics ?? "line_metrics";
|
|
9116
9629
|
const companySpecificMetricsTable = useMemo(
|
|
@@ -9129,8 +9642,6 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9129
9642
|
setIsLoading(true);
|
|
9130
9643
|
setError(null);
|
|
9131
9644
|
try {
|
|
9132
|
-
const currentShiftDetails = getCurrentShift(timezone, shiftConfig ?? void 0);
|
|
9133
|
-
const operationalDate = currentShiftDetails.date;
|
|
9134
9645
|
const { data: { session } } = await supabase.auth.getSession();
|
|
9135
9646
|
if (!session?.access_token) {
|
|
9136
9647
|
throw new Error("No authentication token available");
|
|
@@ -9141,33 +9652,68 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9141
9652
|
}
|
|
9142
9653
|
const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
|
|
9143
9654
|
const isFactory = currentLineId === factoryViewIdentifier;
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9655
|
+
if (isFactory && shiftGroups.length > 0) {
|
|
9656
|
+
console.log(`[useLineKPIs] \u{1F3ED} Factory view: Fetching KPIs for ${shiftGroups.length} shift group(s)`);
|
|
9657
|
+
const kpiPromises = shiftGroups.map(async (group) => {
|
|
9658
|
+
const lineIdsParam = `line_ids=${group.lineIds.join(",")}`;
|
|
9659
|
+
const url = `${apiUrl}/api/dashboard/line-kpis?${lineIdsParam}&date=${group.date}&shift_id=${group.shiftId}&company_id=${entityConfig.companyId}`;
|
|
9660
|
+
console.log(`[useLineKPIs] \u{1F4CA} Fetching for shift ${group.shiftId} (${group.shiftName}):`, {
|
|
9661
|
+
lineIds: group.lineIds,
|
|
9662
|
+
date: group.date
|
|
9663
|
+
});
|
|
9664
|
+
const response = await fetch(url, {
|
|
9665
|
+
headers: {
|
|
9666
|
+
"Authorization": `Bearer ${session.access_token}`,
|
|
9667
|
+
"Content-Type": "application/json"
|
|
9668
|
+
}
|
|
9669
|
+
});
|
|
9670
|
+
if (!response.ok) {
|
|
9671
|
+
const errorText = await response.text();
|
|
9672
|
+
console.error(`[useLineKPIs] Backend API error for shift ${group.shiftId}:`, errorText);
|
|
9673
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
9151
9674
|
}
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
} catch (parseError) {
|
|
9164
|
-
console.error("[useLineKPIs] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
|
|
9165
|
-
throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
|
|
9166
|
-
}
|
|
9167
|
-
if (backendData.kpis) {
|
|
9168
|
-
setKPIs(backendData.kpis);
|
|
9675
|
+
const responseText = await response.text();
|
|
9676
|
+
try {
|
|
9677
|
+
return JSON.parse(responseText);
|
|
9678
|
+
} catch (parseError) {
|
|
9679
|
+
console.error("[useLineKPIs] Failed to parse response as JSON:", responseText.substring(0, 500));
|
|
9680
|
+
throw new Error(`Invalid JSON response from backend`);
|
|
9681
|
+
}
|
|
9682
|
+
});
|
|
9683
|
+
const results = await Promise.all(kpiPromises);
|
|
9684
|
+
const aggregatedKPIs = aggregateKPIResults(results);
|
|
9685
|
+
setKPIs(aggregatedKPIs);
|
|
9169
9686
|
} else {
|
|
9170
|
-
|
|
9687
|
+
const currentShiftDetails = shiftConfig ? getCurrentShift(timezone, shiftConfig) : shiftGroups.length === 1 ? { date: shiftGroups[0].date, shiftId: shiftGroups[0].shiftId } : getCurrentShift(timezone, staticShiftConfig);
|
|
9688
|
+
const operationalDate = currentShiftDetails.date;
|
|
9689
|
+
const lineParam = isFactory ? `line_ids=${configuredLineIds.join(",")}` : `line_id=${currentLineId}`;
|
|
9690
|
+
const response = await fetch(
|
|
9691
|
+
`${apiUrl}/api/dashboard/line-kpis?${lineParam}&date=${operationalDate}&shift_id=${currentShiftDetails.shiftId}&company_id=${entityConfig.companyId}`,
|
|
9692
|
+
{
|
|
9693
|
+
headers: {
|
|
9694
|
+
"Authorization": `Bearer ${session.access_token}`,
|
|
9695
|
+
"Content-Type": "application/json"
|
|
9696
|
+
}
|
|
9697
|
+
}
|
|
9698
|
+
);
|
|
9699
|
+
if (!response.ok) {
|
|
9700
|
+
const errorText = await response.text();
|
|
9701
|
+
console.error("[useLineKPIs] Backend API error response:", errorText);
|
|
9702
|
+
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
9703
|
+
}
|
|
9704
|
+
const responseText = await response.text();
|
|
9705
|
+
let backendData;
|
|
9706
|
+
try {
|
|
9707
|
+
backendData = JSON.parse(responseText);
|
|
9708
|
+
} catch (parseError) {
|
|
9709
|
+
console.error("[useLineKPIs] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
|
|
9710
|
+
throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
|
|
9711
|
+
}
|
|
9712
|
+
if (backendData.kpis) {
|
|
9713
|
+
setKPIs(backendData.kpis);
|
|
9714
|
+
} else {
|
|
9715
|
+
setKPIs(null);
|
|
9716
|
+
}
|
|
9171
9717
|
}
|
|
9172
9718
|
} catch (err) {
|
|
9173
9719
|
console.error("[useLineKPIs] Error fetching KPIs:", err);
|
|
@@ -9178,7 +9724,80 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9178
9724
|
isFetchingRef.current = false;
|
|
9179
9725
|
updateQueueRef.current = false;
|
|
9180
9726
|
}
|
|
9181
|
-
}, [timezone, shiftConfig, entityConfig, supabase, enabled, isShiftConfigLoading]);
|
|
9727
|
+
}, [timezone, shiftConfig, shiftGroups, configuredLineIds, staticShiftConfig, entityConfig, supabase, enabled, isShiftConfigLoading]);
|
|
9728
|
+
const aggregateKPIResults = (results) => {
|
|
9729
|
+
const validResults = results.filter((r2) => r2?.kpis);
|
|
9730
|
+
if (validResults.length === 0) return null;
|
|
9731
|
+
if (validResults.length === 1) return validResults[0].kpis;
|
|
9732
|
+
let totalEfficiency = 0;
|
|
9733
|
+
let totalEfficiencyWeight = 0;
|
|
9734
|
+
let totalOutputCurrent = 0;
|
|
9735
|
+
let totalOutputTarget = 0;
|
|
9736
|
+
let totalIdealOutput = 0;
|
|
9737
|
+
let totalUnderperformingCurrent = 0;
|
|
9738
|
+
let totalUnderperformingTotal = 0;
|
|
9739
|
+
let totalCycleTimeSum = 0;
|
|
9740
|
+
let totalCycleTimeCount = 0;
|
|
9741
|
+
let totalQualitySum = 0;
|
|
9742
|
+
let totalQualityCount = 0;
|
|
9743
|
+
validResults.forEach((result) => {
|
|
9744
|
+
const kpis2 = result.kpis;
|
|
9745
|
+
if (kpis2.efficiency?.value !== void 0) {
|
|
9746
|
+
const weight = kpis2.outputProgress?.current || 1;
|
|
9747
|
+
totalEfficiency += kpis2.efficiency.value * weight;
|
|
9748
|
+
totalEfficiencyWeight += weight;
|
|
9749
|
+
}
|
|
9750
|
+
if (kpis2.outputProgress) {
|
|
9751
|
+
totalOutputCurrent += kpis2.outputProgress.current || 0;
|
|
9752
|
+
totalOutputTarget += kpis2.outputProgress.target || 0;
|
|
9753
|
+
totalIdealOutput += kpis2.outputProgress.idealOutput || 0;
|
|
9754
|
+
}
|
|
9755
|
+
if (kpis2.underperformingWorkers) {
|
|
9756
|
+
totalUnderperformingCurrent += kpis2.underperformingWorkers.current || 0;
|
|
9757
|
+
totalUnderperformingTotal += kpis2.underperformingWorkers.total || 0;
|
|
9758
|
+
}
|
|
9759
|
+
if (kpis2.avgCycleTime?.value !== void 0) {
|
|
9760
|
+
totalCycleTimeSum += kpis2.avgCycleTime.value;
|
|
9761
|
+
totalCycleTimeCount++;
|
|
9762
|
+
}
|
|
9763
|
+
if (kpis2.qualityCompliance?.value !== void 0) {
|
|
9764
|
+
totalQualitySum += kpis2.qualityCompliance.value;
|
|
9765
|
+
totalQualityCount++;
|
|
9766
|
+
}
|
|
9767
|
+
});
|
|
9768
|
+
const aggregated = {
|
|
9769
|
+
efficiency: {
|
|
9770
|
+
value: totalEfficiencyWeight > 0 ? totalEfficiency / totalEfficiencyWeight : 0,
|
|
9771
|
+
change: 0
|
|
9772
|
+
// Change not meaningful when aggregating across shifts
|
|
9773
|
+
},
|
|
9774
|
+
outputProgress: {
|
|
9775
|
+
current: totalOutputCurrent,
|
|
9776
|
+
target: totalOutputTarget,
|
|
9777
|
+
change: 0,
|
|
9778
|
+
// Change not meaningful when aggregating across shifts
|
|
9779
|
+
idealOutput: totalIdealOutput
|
|
9780
|
+
},
|
|
9781
|
+
underperformingWorkers: {
|
|
9782
|
+
current: totalUnderperformingCurrent,
|
|
9783
|
+
total: totalUnderperformingTotal,
|
|
9784
|
+
change: 0
|
|
9785
|
+
// Change not meaningful when aggregating across shifts
|
|
9786
|
+
},
|
|
9787
|
+
avgCycleTime: {
|
|
9788
|
+
value: totalCycleTimeCount > 0 ? totalCycleTimeSum / totalCycleTimeCount : 0,
|
|
9789
|
+
change: 0
|
|
9790
|
+
// Change not meaningful when aggregating across shifts
|
|
9791
|
+
},
|
|
9792
|
+
qualityCompliance: {
|
|
9793
|
+
value: totalQualityCount > 0 ? totalQualitySum / totalQualityCount : 0,
|
|
9794
|
+
change: 0
|
|
9795
|
+
// Change not meaningful when aggregating across shifts
|
|
9796
|
+
}
|
|
9797
|
+
};
|
|
9798
|
+
console.log("[useLineKPIs] \u{1F4CA} Aggregated KPIs from", validResults.length, "shift groups:", aggregated);
|
|
9799
|
+
return aggregated;
|
|
9800
|
+
};
|
|
9182
9801
|
const queueUpdate = useCallback(() => {
|
|
9183
9802
|
if (updateTimeoutRef.current) {
|
|
9184
9803
|
clearTimeout(updateTimeoutRef.current);
|
|
@@ -9198,40 +9817,66 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9198
9817
|
return;
|
|
9199
9818
|
}
|
|
9200
9819
|
fetchKPIs();
|
|
9201
|
-
const currentShiftDetails = getCurrentShift(timezone, shiftConfig ?? void 0);
|
|
9202
|
-
const operationalDate = currentShiftDetails.date;
|
|
9203
9820
|
const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
|
|
9204
|
-
const
|
|
9205
|
-
if (targetLineIds.length === 0) {
|
|
9206
|
-
console.warn("[useLineKPIs] No target line IDs for subscription. LineId:", currentLineId);
|
|
9207
|
-
return;
|
|
9208
|
-
}
|
|
9209
|
-
const baseFilterParts = `date=eq.${operationalDate},shift_id=eq.${currentShiftDetails.shiftId}`;
|
|
9210
|
-
const lineIdFilterPart = `line_id=in.(${targetLineIds.join(",")})`;
|
|
9211
|
-
const lineMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9212
|
-
const companyTableFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9821
|
+
const isFactory = currentLineId === factoryViewIdentifier;
|
|
9213
9822
|
const activeChannels = [];
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
const
|
|
9220
|
-
|
|
9221
|
-
|
|
9823
|
+
if (isFactory && shiftGroups.length > 0) {
|
|
9824
|
+
console.log(`[useLineKPIs] \u{1F4E1} Setting up subscriptions for ${shiftGroups.length} shift group(s)`);
|
|
9825
|
+
shiftGroups.forEach((group, index) => {
|
|
9826
|
+
const baseFilterParts = `date=eq.${group.date},shift_id=eq.${group.shiftId}`;
|
|
9827
|
+
const lineIdFilterPart = `line_id=in.(${group.lineIds.join(",")})`;
|
|
9828
|
+
const filter2 = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9829
|
+
const lmChannelName = `kpi-lm-factory-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9830
|
+
const lmChannel = supabase.channel(lmChannelName).on(
|
|
9831
|
+
"postgres_changes",
|
|
9832
|
+
{ event: "*", schema, table: lineMetricsTable, filter: filter2 },
|
|
9833
|
+
(payload) => {
|
|
9834
|
+
const payloadData = payload.new || payload.old;
|
|
9835
|
+
if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
|
|
9836
|
+
queueUpdateRef.current?.();
|
|
9837
|
+
}
|
|
9838
|
+
}
|
|
9839
|
+
).subscribe((status, err) => {
|
|
9840
|
+
if (status === REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
|
|
9841
|
+
console.error(`[useLineKPIs] Subscription to ${lineMetricsTable} (group ${index}) FAILED:`, status, err);
|
|
9842
|
+
}
|
|
9843
|
+
});
|
|
9844
|
+
activeChannels.push(lmChannel);
|
|
9845
|
+
if (companySpecificMetricsTable && !companySpecificMetricsTable.includes("unknown_company")) {
|
|
9846
|
+
const csChannelName = `kpi-cs-factory-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9847
|
+
const csChannel = supabase.channel(csChannelName).on(
|
|
9848
|
+
"postgres_changes",
|
|
9849
|
+
{ event: "*", schema, table: companySpecificMetricsTable, filter: filter2 },
|
|
9850
|
+
(payload) => {
|
|
9851
|
+
const payloadData = payload.new || payload.old;
|
|
9852
|
+
if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
|
|
9853
|
+
queueUpdateRef.current?.();
|
|
9854
|
+
}
|
|
9855
|
+
}
|
|
9856
|
+
).subscribe((status, err) => {
|
|
9857
|
+
if (status === REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
|
|
9858
|
+
console.error(`[useLineKPIs] Subscription to ${companySpecificMetricsTable} (group ${index}) FAILED:`, status, err);
|
|
9859
|
+
}
|
|
9860
|
+
});
|
|
9861
|
+
activeChannels.push(csChannel);
|
|
9222
9862
|
}
|
|
9863
|
+
});
|
|
9864
|
+
} else {
|
|
9865
|
+
const currentShiftDetails = shiftConfig ? getCurrentShift(timezone, shiftConfig) : getCurrentShift(timezone, staticShiftConfig);
|
|
9866
|
+
const operationalDate = currentShiftDetails.date;
|
|
9867
|
+
const targetLineIds = [currentLineId];
|
|
9868
|
+
if (targetLineIds.length === 0) {
|
|
9869
|
+
console.warn("[useLineKPIs] No target line IDs for subscription. LineId:", currentLineId);
|
|
9870
|
+
return;
|
|
9223
9871
|
}
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
}
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
if (companySpecificMetricsTable && !companySpecificMetricsTable.includes("unknown_company")) {
|
|
9231
|
-
const csChannelName = `kpi-cs-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9232
|
-
const csChannel = supabase.channel(csChannelName).on(
|
|
9872
|
+
const baseFilterParts = `date=eq.${operationalDate},shift_id=eq.${currentShiftDetails.shiftId}`;
|
|
9873
|
+
const lineIdFilterPart = `line_id=in.(${targetLineIds.join(",")})`;
|
|
9874
|
+
const lineMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9875
|
+
const companyTableFilter = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9876
|
+
const lmChannelName = `kpi-lm-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9877
|
+
const lmChannel = supabase.channel(lmChannelName).on(
|
|
9233
9878
|
"postgres_changes",
|
|
9234
|
-
{ event: "*", schema, table:
|
|
9879
|
+
{ event: "*", schema, table: lineMetricsTable, filter: lineMetricsFilter },
|
|
9235
9880
|
(payload) => {
|
|
9236
9881
|
const payloadData = payload.new || payload.old;
|
|
9237
9882
|
if (payloadData?.date === operationalDate && payloadData?.shift_id === currentShiftDetails.shiftId) {
|
|
@@ -9240,10 +9885,28 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9240
9885
|
}
|
|
9241
9886
|
).subscribe((status, err) => {
|
|
9242
9887
|
if (status === REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
|
|
9243
|
-
console.error(`[useLineKPIs] Subscription to ${
|
|
9888
|
+
console.error(`[useLineKPIs] Subscription to ${lineMetricsTable} FAILED: ${status}`, err || "");
|
|
9244
9889
|
}
|
|
9245
9890
|
});
|
|
9246
|
-
activeChannels.push(
|
|
9891
|
+
activeChannels.push(lmChannel);
|
|
9892
|
+
if (companySpecificMetricsTable && !companySpecificMetricsTable.includes("unknown_company")) {
|
|
9893
|
+
const csChannelName = `kpi-cs-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
9894
|
+
const csChannel = supabase.channel(csChannelName).on(
|
|
9895
|
+
"postgres_changes",
|
|
9896
|
+
{ event: "*", schema, table: companySpecificMetricsTable, filter: companyTableFilter },
|
|
9897
|
+
(payload) => {
|
|
9898
|
+
const payloadData = payload.new || payload.old;
|
|
9899
|
+
if (payloadData?.date === operationalDate && payloadData?.shift_id === currentShiftDetails.shiftId) {
|
|
9900
|
+
queueUpdateRef.current?.();
|
|
9901
|
+
}
|
|
9902
|
+
}
|
|
9903
|
+
).subscribe((status, err) => {
|
|
9904
|
+
if (status === REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
|
|
9905
|
+
console.error(`[useLineKPIs] Subscription to ${companySpecificMetricsTable} FAILED: ${status}`, err || "");
|
|
9906
|
+
}
|
|
9907
|
+
});
|
|
9908
|
+
activeChannels.push(csChannel);
|
|
9909
|
+
}
|
|
9247
9910
|
}
|
|
9248
9911
|
return () => {
|
|
9249
9912
|
activeChannels.forEach((ch) => supabase.removeChannel(ch).catch((err) => console.error("[useLineKPIs] Error removing KPI channel:", err)));
|
|
@@ -9251,7 +9914,7 @@ var useLineKPIs = ({ lineId, enabled }) => {
|
|
|
9251
9914
|
clearTimeout(updateTimeoutRef.current);
|
|
9252
9915
|
}
|
|
9253
9916
|
};
|
|
9254
|
-
}, [lineId, supabase, entityConfig, schema, lineMetricsTable, companySpecificMetricsTable, timezone, shiftConfig, isShiftConfigLoading]);
|
|
9917
|
+
}, [lineId, supabase, entityConfig, schema, lineMetricsTable, companySpecificMetricsTable, timezone, shiftConfig, staticShiftConfig, shiftGroups, isShiftConfigLoading]);
|
|
9255
9918
|
return {
|
|
9256
9919
|
kpis,
|
|
9257
9920
|
isLoading,
|
|
@@ -9339,12 +10002,12 @@ var useRealtimeLineMetrics = ({
|
|
|
9339
10002
|
const companyId = entityConfig.companyId;
|
|
9340
10003
|
const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
|
|
9341
10004
|
const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
10005
|
+
const enabledWorkspaceLists = await Promise.all(
|
|
10006
|
+
targetLineIds.map((lineId2) => workspaceService.getEnabledWorkspaces(lineId2))
|
|
10007
|
+
);
|
|
10008
|
+
const enabledWorkspaceIds = Array.from(
|
|
10009
|
+
new Set(enabledWorkspaceLists.flatMap((workspaces) => workspaces.map((ws) => ws.id)))
|
|
10010
|
+
);
|
|
9348
10011
|
const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
|
|
9349
10012
|
line_id,
|
|
9350
10013
|
workspace_id,
|
|
@@ -9404,8 +10067,8 @@ var useRealtimeLineMetrics = ({
|
|
|
9404
10067
|
const companyId = entityConfig.companyId;
|
|
9405
10068
|
const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
|
|
9406
10069
|
const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
|
|
9407
|
-
const
|
|
9408
|
-
const enabledWorkspaceIds =
|
|
10070
|
+
const enabledWorkspaces = await workspaceService.getEnabledWorkspaces(lineIdRef.current);
|
|
10071
|
+
const enabledWorkspaceIds = enabledWorkspaces.map((ws) => ws.id);
|
|
9409
10072
|
const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
|
|
9410
10073
|
workspace_id,
|
|
9411
10074
|
workspace_name,
|
|
@@ -9525,20 +10188,23 @@ var useRealtimeLineMetrics = ({
|
|
|
9525
10188
|
const companyId = entityConfig.companyId;
|
|
9526
10189
|
const metricsTablePrefix = getMetricsTablePrefix();
|
|
9527
10190
|
const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
|
|
10191
|
+
const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
|
|
10192
|
+
const baseFilterParts = `date=eq.${currentDate},shift_id=eq.${shiftId}`;
|
|
10193
|
+
const lineIdFilterPart = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
10194
|
+
const filter2 = `${baseFilterParts},${lineIdFilterPart}`;
|
|
9528
10195
|
const lineMetricsChannel = supabase.channel(`line-metrics-${timestamp}`).on(
|
|
9529
10196
|
"postgres_changes",
|
|
9530
10197
|
{
|
|
9531
10198
|
event: "*",
|
|
9532
10199
|
schema: "public",
|
|
9533
10200
|
table: "line_metrics",
|
|
9534
|
-
filter:
|
|
10201
|
+
filter: filter2
|
|
9535
10202
|
},
|
|
9536
10203
|
async (payload) => {
|
|
9537
10204
|
const payloadData = payload.new;
|
|
9538
10205
|
if (process.env.NODE_ENV === "development") {
|
|
9539
10206
|
console.log("Line metrics update received:", payloadData);
|
|
9540
10207
|
}
|
|
9541
|
-
const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
|
|
9542
10208
|
if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
|
|
9543
10209
|
queueUpdate();
|
|
9544
10210
|
}
|
|
@@ -9554,14 +10220,13 @@ var useRealtimeLineMetrics = ({
|
|
|
9554
10220
|
event: "*",
|
|
9555
10221
|
schema: "public",
|
|
9556
10222
|
table: metricsTable,
|
|
9557
|
-
filter:
|
|
10223
|
+
filter: filter2
|
|
9558
10224
|
},
|
|
9559
10225
|
async (payload) => {
|
|
9560
10226
|
const payloadData = payload.new;
|
|
9561
10227
|
if (process.env.NODE_ENV === "development") {
|
|
9562
10228
|
console.log(`${metricsTablePrefix} update received:`, payloadData);
|
|
9563
10229
|
}
|
|
9564
|
-
const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
|
|
9565
10230
|
if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
|
|
9566
10231
|
queueUpdate();
|
|
9567
10232
|
}
|
|
@@ -9572,7 +10237,7 @@ var useRealtimeLineMetrics = ({
|
|
|
9572
10237
|
}
|
|
9573
10238
|
});
|
|
9574
10239
|
channelsRef.current = [lineMetricsChannel, metricsChannel];
|
|
9575
|
-
}, [supabase, queueUpdate, urlDate, shiftId, entityConfig, dateTimeConfig.defaultTimezone]);
|
|
10240
|
+
}, [supabase, queueUpdate, urlDate, shiftId, entityConfig, timezone, dateTimeConfig.defaultTimezone]);
|
|
9576
10241
|
const prevShiftIdRef = useRef(void 0);
|
|
9577
10242
|
useEffect(() => {
|
|
9578
10243
|
if (!lineId) return;
|
|
@@ -10351,34 +11016,16 @@ var useFactoryOverviewMetrics = (date, shiftId) => {
|
|
|
10351
11016
|
if (lineIds.length === 0) {
|
|
10352
11017
|
throw new Error("No lines configured in entityConfig");
|
|
10353
11018
|
}
|
|
10354
|
-
const { data: { session } } = await supabase.auth.getSession();
|
|
10355
|
-
if (!session?.access_token) {
|
|
10356
|
-
throw new Error("No authentication token available");
|
|
10357
|
-
}
|
|
10358
|
-
const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
10359
|
-
if (!apiUrl) {
|
|
10360
|
-
throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
|
|
10361
|
-
}
|
|
10362
11019
|
const params = new URLSearchParams({
|
|
10363
11020
|
line_ids: lineIds.join(","),
|
|
10364
11021
|
date: queryDate,
|
|
10365
11022
|
shift_id: queryShiftId.toString(),
|
|
10366
11023
|
company_id: entityConfig.companyId || ""
|
|
10367
11024
|
});
|
|
10368
|
-
const
|
|
10369
|
-
|
|
10370
|
-
{
|
|
10371
|
-
headers: {
|
|
10372
|
-
"Authorization": `Bearer ${session.access_token}`,
|
|
10373
|
-
"Content-Type": "application/json"
|
|
10374
|
-
}
|
|
10375
|
-
}
|
|
11025
|
+
const data = await fetchBackendJson(
|
|
11026
|
+
supabase,
|
|
11027
|
+
`/api/dashboard/factory-overview?${params.toString()}`
|
|
10376
11028
|
);
|
|
10377
|
-
if (!response.ok) {
|
|
10378
|
-
const errorText = await response.text();
|
|
10379
|
-
throw new Error(`Backend API error (${response.status}): ${errorText}`);
|
|
10380
|
-
}
|
|
10381
|
-
const data = await response.json();
|
|
10382
11029
|
setMetrics(data);
|
|
10383
11030
|
} catch (err) {
|
|
10384
11031
|
console.error("[useFactoryOverviewMetrics] Error fetching factory overview:", err);
|
|
@@ -10407,6 +11054,46 @@ var isInitialized = false;
|
|
|
10407
11054
|
var isInitializing = false;
|
|
10408
11055
|
var initializedWithLineIds = [];
|
|
10409
11056
|
var initializationPromise = null;
|
|
11057
|
+
var workspaceDisplayNamesListeners = /* @__PURE__ */ new Set();
|
|
11058
|
+
var notifyWorkspaceDisplayNamesListeners = (changedLineId) => {
|
|
11059
|
+
workspaceDisplayNamesListeners.forEach((listener) => {
|
|
11060
|
+
try {
|
|
11061
|
+
listener(changedLineId);
|
|
11062
|
+
} catch (err) {
|
|
11063
|
+
console.error("[workspaceDisplayNames] Listener error", err);
|
|
11064
|
+
}
|
|
11065
|
+
});
|
|
11066
|
+
};
|
|
11067
|
+
var subscribeWorkspaceDisplayNames = (listener) => {
|
|
11068
|
+
workspaceDisplayNamesListeners.add(listener);
|
|
11069
|
+
return () => workspaceDisplayNamesListeners.delete(listener);
|
|
11070
|
+
};
|
|
11071
|
+
var getAllWorkspaceDisplayNamesSnapshot = (lineId) => {
|
|
11072
|
+
if (lineId && runtimeWorkspaceDisplayNames[lineId]) {
|
|
11073
|
+
return { ...runtimeWorkspaceDisplayNames[lineId] };
|
|
11074
|
+
}
|
|
11075
|
+
if (lineId) return {};
|
|
11076
|
+
const allNames = {};
|
|
11077
|
+
Object.entries(runtimeWorkspaceDisplayNames).forEach(([cachedLineId, lineNames]) => {
|
|
11078
|
+
Object.entries(lineNames).forEach(([workspaceId, displayName]) => {
|
|
11079
|
+
allNames[`${cachedLineId}_${workspaceId}`] = displayName;
|
|
11080
|
+
});
|
|
11081
|
+
});
|
|
11082
|
+
return allNames;
|
|
11083
|
+
};
|
|
11084
|
+
var upsertWorkspaceDisplayNameInCache = (params) => {
|
|
11085
|
+
const { lineId, workspaceId, displayName, enabled } = params;
|
|
11086
|
+
if (!lineId || !workspaceId) return;
|
|
11087
|
+
if (!runtimeWorkspaceDisplayNames[lineId]) {
|
|
11088
|
+
runtimeWorkspaceDisplayNames[lineId] = {};
|
|
11089
|
+
}
|
|
11090
|
+
if (enabled === false || !displayName) {
|
|
11091
|
+
delete runtimeWorkspaceDisplayNames[lineId][workspaceId];
|
|
11092
|
+
} else {
|
|
11093
|
+
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
11094
|
+
}
|
|
11095
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
11096
|
+
};
|
|
10410
11097
|
function getCurrentLineIds() {
|
|
10411
11098
|
try {
|
|
10412
11099
|
const config = _getDashboardConfigInstance();
|
|
@@ -10446,15 +11133,20 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
|
|
|
10446
11133
|
console.log("\u{1F504} Target line IDs for workspace filtering:", targetLineIds);
|
|
10447
11134
|
runtimeWorkspaceDisplayNames = {};
|
|
10448
11135
|
if (targetLineIds.length > 0) {
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
11136
|
+
const results = await Promise.all(
|
|
11137
|
+
targetLineIds.map(async (lineId) => {
|
|
11138
|
+
console.log(`\u{1F504} Fetching workspaces for line: ${lineId}`);
|
|
11139
|
+
const lineDisplayNamesMap = await workspaceService.getWorkspaceDisplayNames(void 0, lineId);
|
|
11140
|
+
return { lineId, lineDisplayNamesMap };
|
|
11141
|
+
})
|
|
11142
|
+
);
|
|
11143
|
+
results.forEach(({ lineId, lineDisplayNamesMap }) => {
|
|
10452
11144
|
runtimeWorkspaceDisplayNames[lineId] = {};
|
|
10453
11145
|
lineDisplayNamesMap.forEach((displayName, workspaceId) => {
|
|
10454
11146
|
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
10455
11147
|
});
|
|
10456
11148
|
console.log(`\u2705 Stored ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
|
|
10457
|
-
}
|
|
11149
|
+
});
|
|
10458
11150
|
} else {
|
|
10459
11151
|
console.warn("\u26A0\uFE0F No line IDs found, fetching all workspaces (less efficient)");
|
|
10460
11152
|
const allWorkspacesMap = await workspaceService.getWorkspaceDisplayNames();
|
|
@@ -10467,6 +11159,7 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
|
|
|
10467
11159
|
initializedWithLineIds = targetLineIds;
|
|
10468
11160
|
console.log("\u2705 Workspace display names initialized from Supabase:", runtimeWorkspaceDisplayNames);
|
|
10469
11161
|
console.log("\u2705 Initialized with line IDs:", initializedWithLineIds);
|
|
11162
|
+
notifyWorkspaceDisplayNamesListeners(explicitLineId);
|
|
10470
11163
|
} catch (error) {
|
|
10471
11164
|
console.error("\u274C Failed to initialize workspace display names from Supabase:", error);
|
|
10472
11165
|
} finally {
|
|
@@ -10489,6 +11182,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
|
|
|
10489
11182
|
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
10490
11183
|
});
|
|
10491
11184
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
|
|
11185
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
10492
11186
|
} catch (error) {
|
|
10493
11187
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
10494
11188
|
}
|
|
@@ -10507,6 +11201,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
|
|
|
10507
11201
|
runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
|
|
10508
11202
|
});
|
|
10509
11203
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
|
|
11204
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
10510
11205
|
} catch (error) {
|
|
10511
11206
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
10512
11207
|
}
|
|
@@ -10546,6 +11241,7 @@ var getWorkspaceDisplayName = (workspaceId, lineId) => {
|
|
|
10546
11241
|
runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
|
|
10547
11242
|
});
|
|
10548
11243
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
|
|
11244
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
10549
11245
|
}).catch((error) => {
|
|
10550
11246
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
10551
11247
|
});
|
|
@@ -10586,6 +11282,7 @@ var getShortWorkspaceDisplayName = (workspaceId, lineId) => {
|
|
|
10586
11282
|
runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
|
|
10587
11283
|
});
|
|
10588
11284
|
console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
|
|
11285
|
+
notifyWorkspaceDisplayNamesListeners(lineId);
|
|
10589
11286
|
}).catch((error) => {
|
|
10590
11287
|
console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
|
|
10591
11288
|
});
|
|
@@ -10627,6 +11324,9 @@ var getAllWorkspaceDisplayNamesAsync = async (companyId, lineId) => {
|
|
|
10627
11324
|
while (isInitializing) {
|
|
10628
11325
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
10629
11326
|
}
|
|
11327
|
+
if (lineId && isInitialized && !runtimeWorkspaceDisplayNames[lineId]) {
|
|
11328
|
+
await preInitializeWorkspaceDisplayNames(lineId);
|
|
11329
|
+
}
|
|
10630
11330
|
if (lineId && runtimeWorkspaceDisplayNames[lineId]) {
|
|
10631
11331
|
return { ...runtimeWorkspaceDisplayNames[lineId] };
|
|
10632
11332
|
}
|
|
@@ -10659,6 +11359,7 @@ var refreshWorkspaceDisplayNames = async (companyId) => {
|
|
|
10659
11359
|
runtimeWorkspaceDisplayNames = {};
|
|
10660
11360
|
isInitialized = false;
|
|
10661
11361
|
await initializeWorkspaceDisplayNames();
|
|
11362
|
+
notifyWorkspaceDisplayNamesListeners();
|
|
10662
11363
|
};
|
|
10663
11364
|
var clearWorkspaceDisplayNamesCache = () => {
|
|
10664
11365
|
workspaceService.clearWorkspaceDisplayNamesCache();
|
|
@@ -10667,6 +11368,7 @@ var clearWorkspaceDisplayNamesCache = () => {
|
|
|
10667
11368
|
isInitializing = false;
|
|
10668
11369
|
initializedWithLineIds = [];
|
|
10669
11370
|
initializationPromise = null;
|
|
11371
|
+
notifyWorkspaceDisplayNamesListeners();
|
|
10670
11372
|
};
|
|
10671
11373
|
|
|
10672
11374
|
// src/lib/hooks/useWorkspaceDisplayNames.ts
|
|
@@ -10689,6 +11391,23 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
|
|
|
10689
11391
|
useEffect(() => {
|
|
10690
11392
|
fetchDisplayNames();
|
|
10691
11393
|
}, [fetchDisplayNames]);
|
|
11394
|
+
useEffect(() => {
|
|
11395
|
+
const snapshot = getAllWorkspaceDisplayNamesSnapshot(lineId);
|
|
11396
|
+
if (Object.keys(snapshot).length > 0) {
|
|
11397
|
+
setDisplayNames(snapshot);
|
|
11398
|
+
setLoading(false);
|
|
11399
|
+
}
|
|
11400
|
+
const unsubscribe = subscribeWorkspaceDisplayNames((changedLineId) => {
|
|
11401
|
+
if (!lineId) {
|
|
11402
|
+
setDisplayNames(getAllWorkspaceDisplayNamesSnapshot());
|
|
11403
|
+
return;
|
|
11404
|
+
}
|
|
11405
|
+
if (!changedLineId || changedLineId === "*" || changedLineId === lineId) {
|
|
11406
|
+
setDisplayNames(getAllWorkspaceDisplayNamesSnapshot(lineId));
|
|
11407
|
+
}
|
|
11408
|
+
});
|
|
11409
|
+
return unsubscribe;
|
|
11410
|
+
}, [lineId]);
|
|
10692
11411
|
return {
|
|
10693
11412
|
displayNames,
|
|
10694
11413
|
loading,
|
|
@@ -10697,7 +11416,7 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
|
|
|
10697
11416
|
};
|
|
10698
11417
|
};
|
|
10699
11418
|
var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
|
|
10700
|
-
const [displayName, setDisplayName] = useState(workspaceId);
|
|
11419
|
+
const [displayName, setDisplayName] = useState(() => getWorkspaceDisplayName(workspaceId, lineId));
|
|
10701
11420
|
const [loading, setLoading] = useState(true);
|
|
10702
11421
|
const [error, setError] = useState(null);
|
|
10703
11422
|
const fetchDisplayName = useCallback(async () => {
|
|
@@ -10716,6 +11435,18 @@ var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
|
|
|
10716
11435
|
useEffect(() => {
|
|
10717
11436
|
fetchDisplayName();
|
|
10718
11437
|
}, [fetchDisplayName]);
|
|
11438
|
+
useEffect(() => {
|
|
11439
|
+
const unsubscribe = subscribeWorkspaceDisplayNames((changedLineId) => {
|
|
11440
|
+
if (!lineId) {
|
|
11441
|
+
setDisplayName(getWorkspaceDisplayName(workspaceId));
|
|
11442
|
+
return;
|
|
11443
|
+
}
|
|
11444
|
+
if (!changedLineId || changedLineId === "*" || changedLineId === lineId) {
|
|
11445
|
+
setDisplayName(getWorkspaceDisplayName(workspaceId, lineId));
|
|
11446
|
+
}
|
|
11447
|
+
});
|
|
11448
|
+
return unsubscribe;
|
|
11449
|
+
}, [workspaceId, lineId]);
|
|
10719
11450
|
return {
|
|
10720
11451
|
displayName,
|
|
10721
11452
|
loading,
|
|
@@ -10746,6 +11477,18 @@ var useWorkspaceDisplayNamesMap = (workspaceIds, lineId, companyId) => {
|
|
|
10746
11477
|
useEffect(() => {
|
|
10747
11478
|
fetchDisplayNames();
|
|
10748
11479
|
}, [fetchDisplayNames]);
|
|
11480
|
+
useEffect(() => {
|
|
11481
|
+
const unsubscribe = subscribeWorkspaceDisplayNames((changedLineId) => {
|
|
11482
|
+
if (lineId && changedLineId && changedLineId !== "*" && changedLineId !== lineId) return;
|
|
11483
|
+
const snapshot = getAllWorkspaceDisplayNamesSnapshot(lineId);
|
|
11484
|
+
const next = {};
|
|
11485
|
+
workspaceIds.forEach((id3) => {
|
|
11486
|
+
if (snapshot[id3]) next[id3] = snapshot[id3];
|
|
11487
|
+
});
|
|
11488
|
+
setDisplayNames(next);
|
|
11489
|
+
});
|
|
11490
|
+
return unsubscribe;
|
|
11491
|
+
}, [workspaceIds, lineId]);
|
|
10749
11492
|
return {
|
|
10750
11493
|
displayNames,
|
|
10751
11494
|
loading,
|
|
@@ -10924,10 +11667,26 @@ var useAllWorkspaceMetrics = (options) => {
|
|
|
10924
11667
|
const dateTimeConfig = useDateTimeConfig();
|
|
10925
11668
|
const staticShiftConfig = useShiftConfig();
|
|
10926
11669
|
const timezone = useAppTimezone();
|
|
10927
|
-
const configuredLineIds = useMemo(() =>
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
11670
|
+
const configuredLineIds = useMemo(() => {
|
|
11671
|
+
const allLineIds = getConfiguredLineIds(entityConfig);
|
|
11672
|
+
if (options?.allowedLineIds) {
|
|
11673
|
+
return allLineIds.filter((id3) => options.allowedLineIds.includes(id3));
|
|
11674
|
+
}
|
|
11675
|
+
return allLineIds;
|
|
11676
|
+
}, [entityConfig, options?.allowedLineIds]);
|
|
11677
|
+
const {
|
|
11678
|
+
shiftConfigMap: multiLineShiftConfigMap,
|
|
11679
|
+
isLoading: isShiftConfigLoading
|
|
11680
|
+
} = useMultiLineShiftConfigs(configuredLineIds, staticShiftConfig);
|
|
11681
|
+
const shiftGroups = useMemo(() => {
|
|
11682
|
+
if (isShiftConfigLoading || multiLineShiftConfigMap.size === 0) return [];
|
|
11683
|
+
return groupLinesByShift(multiLineShiftConfigMap, timezone || dateTimeConfig.defaultTimezone || "Asia/Kolkata");
|
|
11684
|
+
}, [isShiftConfigLoading, multiLineShiftConfigMap, timezone, dateTimeConfig.defaultTimezone]);
|
|
11685
|
+
console.log(`[useAllWorkspaceMetrics] \u{1F4CA} Shift groups:`, shiftGroups.map((g) => ({
|
|
11686
|
+
shiftId: g.shiftId,
|
|
11687
|
+
date: g.date,
|
|
11688
|
+
lineIds: g.lineIds
|
|
11689
|
+
})));
|
|
10931
11690
|
const supabase = useSupabase();
|
|
10932
11691
|
const [workspaces, setWorkspaces] = useState([]);
|
|
10933
11692
|
const [loading, setLoading] = useState(true);
|
|
@@ -10935,19 +11694,29 @@ var useAllWorkspaceMetrics = (options) => {
|
|
|
10935
11694
|
const [initialized, setInitialized] = useState(false);
|
|
10936
11695
|
const fetchTimeoutRef = useRef(null);
|
|
10937
11696
|
const isFetchingRef = useRef(false);
|
|
10938
|
-
const
|
|
11697
|
+
const hasSpecificDateShift = options?.initialDate !== void 0 || options?.initialShiftId !== void 0;
|
|
11698
|
+
const fallbackQueryShiftId = useMemo(() => {
|
|
10939
11699
|
if (options?.initialShiftId !== void 0) {
|
|
10940
11700
|
return options.initialShiftId;
|
|
10941
11701
|
}
|
|
11702
|
+
if (shiftGroups.length > 0) {
|
|
11703
|
+
return shiftGroups[0].shiftId;
|
|
11704
|
+
}
|
|
10942
11705
|
const currentShift = getCurrentShift(
|
|
10943
11706
|
timezone || dateTimeConfig.defaultTimezone || "Asia/Kolkata",
|
|
10944
|
-
|
|
11707
|
+
staticShiftConfig
|
|
10945
11708
|
);
|
|
10946
11709
|
return currentShift.shiftId;
|
|
10947
|
-
}, [options?.initialShiftId, timezone, dateTimeConfig.defaultTimezone,
|
|
10948
|
-
const
|
|
10949
|
-
|
|
10950
|
-
|
|
11710
|
+
}, [options?.initialShiftId, shiftGroups, timezone, dateTimeConfig.defaultTimezone, staticShiftConfig]);
|
|
11711
|
+
const fallbackQueryDate = useMemo(() => {
|
|
11712
|
+
if (options?.initialDate) {
|
|
11713
|
+
return options.initialDate;
|
|
11714
|
+
}
|
|
11715
|
+
if (shiftGroups.length > 0) {
|
|
11716
|
+
return shiftGroups[0].date;
|
|
11717
|
+
}
|
|
11718
|
+
return getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
|
|
11719
|
+
}, [options?.initialDate, shiftGroups, timezone, dateTimeConfig.defaultTimezone]);
|
|
10951
11720
|
const metricsTable = useMemo(() => {
|
|
10952
11721
|
const companyId = entityConfig.companyId;
|
|
10953
11722
|
if (!companyId) return "";
|
|
@@ -10965,49 +11734,105 @@ var useAllWorkspaceMetrics = (options) => {
|
|
|
10965
11734
|
}
|
|
10966
11735
|
setError(null);
|
|
10967
11736
|
try {
|
|
10968
|
-
const
|
|
10969
|
-
const
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
11737
|
+
const lineWorkspaceMap = /* @__PURE__ */ new Map();
|
|
11738
|
+
const allEnabledWorkspaceIdSet = /* @__PURE__ */ new Set();
|
|
11739
|
+
const perLineEnabledIds = await Promise.all(
|
|
11740
|
+
configuredLineIds.map(async (lineId) => {
|
|
11741
|
+
const enabledWorkspaces = await workspaceService.getEnabledWorkspaces(lineId);
|
|
11742
|
+
const enabledIds = enabledWorkspaces.map((ws) => ws.id);
|
|
11743
|
+
return { lineId, enabledIds };
|
|
11744
|
+
})
|
|
11745
|
+
);
|
|
11746
|
+
perLineEnabledIds.forEach(({ lineId, enabledIds }) => {
|
|
11747
|
+
lineWorkspaceMap.set(lineId, enabledIds);
|
|
11748
|
+
enabledIds.forEach((id3) => allEnabledWorkspaceIdSet.add(id3));
|
|
11749
|
+
});
|
|
11750
|
+
const allEnabledWorkspaceIds = Array.from(allEnabledWorkspaceIdSet);
|
|
11751
|
+
if (allEnabledWorkspaceIds.length === 0) {
|
|
10977
11752
|
setWorkspaces([]);
|
|
10978
11753
|
setInitialized(true);
|
|
10979
11754
|
setLoading(false);
|
|
10980
11755
|
return;
|
|
10981
11756
|
}
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
11010
|
-
|
|
11757
|
+
let transformedData = [];
|
|
11758
|
+
if (hasSpecificDateShift) {
|
|
11759
|
+
console.log(`[useAllWorkspaceMetrics] \u{1F4CA} Using specific date/shift: ${fallbackQueryDate} / ${fallbackQueryShiftId}`);
|
|
11760
|
+
const { data, error: fetchError } = await supabase.from(metricsTable).select(`
|
|
11761
|
+
workspace_name,
|
|
11762
|
+
total_output,
|
|
11763
|
+
avg_pph,
|
|
11764
|
+
efficiency,
|
|
11765
|
+
workspace_id,
|
|
11766
|
+
avg_cycle_time,
|
|
11767
|
+
performance_score,
|
|
11768
|
+
trend_score,
|
|
11769
|
+
line_id,
|
|
11770
|
+
total_day_output
|
|
11771
|
+
`).eq("date", fallbackQueryDate).eq("shift_id", fallbackQueryShiftId).in("workspace_id", allEnabledWorkspaceIds).order("efficiency", { ascending: false });
|
|
11772
|
+
if (fetchError) throw fetchError;
|
|
11773
|
+
transformedData = (data || []).map((item) => ({
|
|
11774
|
+
company_id: entityConfig.companyId || "unknown",
|
|
11775
|
+
line_id: item.line_id,
|
|
11776
|
+
shift_id: fallbackQueryShiftId,
|
|
11777
|
+
date: fallbackQueryDate,
|
|
11778
|
+
workspace_uuid: item.workspace_id,
|
|
11779
|
+
workspace_name: item.workspace_name,
|
|
11780
|
+
action_count: item.total_output || 0,
|
|
11781
|
+
pph: item.avg_pph || 0,
|
|
11782
|
+
performance_score: item.performance_score || 0,
|
|
11783
|
+
avg_cycle_time: item.avg_cycle_time || 0,
|
|
11784
|
+
trend: item.trend_score === 1 ? 2 : 0,
|
|
11785
|
+
predicted_output: 0,
|
|
11786
|
+
efficiency: item.efficiency || 0,
|
|
11787
|
+
action_threshold: item.total_day_output || 0
|
|
11788
|
+
}));
|
|
11789
|
+
} else if (shiftGroups.length > 0) {
|
|
11790
|
+
console.log(`[useAllWorkspaceMetrics] \u{1F3ED} Fetching per-shift: ${shiftGroups.length} group(s)`);
|
|
11791
|
+
const queryPromises = shiftGroups.map(async (group) => {
|
|
11792
|
+
const groupWorkspaceIds = group.lineIds.flatMap(
|
|
11793
|
+
(lineId) => lineWorkspaceMap.get(lineId) || []
|
|
11794
|
+
);
|
|
11795
|
+
if (groupWorkspaceIds.length === 0) {
|
|
11796
|
+
return [];
|
|
11797
|
+
}
|
|
11798
|
+
console.log(`[useAllWorkspaceMetrics] \u{1F4CA} Fetching shift ${group.shiftId} (${group.shiftName}):`, {
|
|
11799
|
+
lineIds: group.lineIds,
|
|
11800
|
+
date: group.date,
|
|
11801
|
+
workspaceCount: groupWorkspaceIds.length
|
|
11802
|
+
});
|
|
11803
|
+
const { data, error: fetchError } = await supabase.from(metricsTable).select(`
|
|
11804
|
+
workspace_name,
|
|
11805
|
+
total_output,
|
|
11806
|
+
avg_pph,
|
|
11807
|
+
efficiency,
|
|
11808
|
+
workspace_id,
|
|
11809
|
+
avg_cycle_time,
|
|
11810
|
+
performance_score,
|
|
11811
|
+
trend_score,
|
|
11812
|
+
line_id,
|
|
11813
|
+
total_day_output
|
|
11814
|
+
`).eq("date", group.date).eq("shift_id", group.shiftId).in("workspace_id", groupWorkspaceIds);
|
|
11815
|
+
if (fetchError) throw fetchError;
|
|
11816
|
+
return (data || []).map((item) => ({
|
|
11817
|
+
company_id: entityConfig.companyId || "unknown",
|
|
11818
|
+
line_id: item.line_id,
|
|
11819
|
+
shift_id: group.shiftId,
|
|
11820
|
+
date: group.date,
|
|
11821
|
+
workspace_uuid: item.workspace_id,
|
|
11822
|
+
workspace_name: item.workspace_name,
|
|
11823
|
+
action_count: item.total_output || 0,
|
|
11824
|
+
pph: item.avg_pph || 0,
|
|
11825
|
+
performance_score: item.performance_score || 0,
|
|
11826
|
+
avg_cycle_time: item.avg_cycle_time || 0,
|
|
11827
|
+
trend: item.trend_score === 1 ? 2 : 0,
|
|
11828
|
+
predicted_output: 0,
|
|
11829
|
+
efficiency: item.efficiency || 0,
|
|
11830
|
+
action_threshold: item.total_day_output || 0
|
|
11831
|
+
}));
|
|
11832
|
+
});
|
|
11833
|
+
const results = await Promise.all(queryPromises);
|
|
11834
|
+
transformedData = results.flat().sort((a, b) => (b.efficiency || 0) - (a.efficiency || 0));
|
|
11835
|
+
}
|
|
11011
11836
|
setWorkspaces((prevWorkspaces) => {
|
|
11012
11837
|
if (prevWorkspaces.length !== transformedData.length) {
|
|
11013
11838
|
return transformedData;
|
|
@@ -11029,51 +11854,78 @@ var useAllWorkspaceMetrics = (options) => {
|
|
|
11029
11854
|
setLoading(false);
|
|
11030
11855
|
isFetchingRef.current = false;
|
|
11031
11856
|
}
|
|
11032
|
-
}, [
|
|
11857
|
+
}, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, supabase, entityConfig.companyId, configuredLineIds, options?.enabled, isShiftConfigLoading]);
|
|
11033
11858
|
useEffect(() => {
|
|
11034
11859
|
if (!initialized) {
|
|
11035
11860
|
fetchWorkspaceMetrics();
|
|
11036
11861
|
}
|
|
11862
|
+
const validDateShiftCombos = /* @__PURE__ */ new Set();
|
|
11863
|
+
if (hasSpecificDateShift) {
|
|
11864
|
+
validDateShiftCombos.add(`${fallbackQueryDate}-${fallbackQueryShiftId}`);
|
|
11865
|
+
} else {
|
|
11866
|
+
shiftGroups.forEach((group) => {
|
|
11867
|
+
validDateShiftCombos.add(`${group.date}-${group.shiftId}`);
|
|
11868
|
+
});
|
|
11869
|
+
}
|
|
11037
11870
|
const setupSubscription = () => {
|
|
11038
|
-
|
|
11039
|
-
|
|
11871
|
+
if (!metricsTable || configuredLineIds.length === 0) return [];
|
|
11872
|
+
const groupsToSubscribe = hasSpecificDateShift || shiftGroups.length === 0 ? [
|
|
11040
11873
|
{
|
|
11041
|
-
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
}
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11049
|
-
|
|
11050
|
-
|
|
11051
|
-
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11874
|
+
date: fallbackQueryDate,
|
|
11875
|
+
shiftId: fallbackQueryShiftId,
|
|
11876
|
+
lineIds: configuredLineIds
|
|
11877
|
+
}
|
|
11878
|
+
] : shiftGroups.map((group) => ({
|
|
11879
|
+
date: group.date,
|
|
11880
|
+
shiftId: group.shiftId,
|
|
11881
|
+
lineIds: group.lineIds
|
|
11882
|
+
}));
|
|
11883
|
+
return groupsToSubscribe.map((group, index) => {
|
|
11884
|
+
const lineIdFilterPart = `line_id=in.(${group.lineIds.map((id3) => `"${id3}"`).join(",")})`;
|
|
11885
|
+
const filter2 = `date=eq.${group.date},shift_id=eq.${group.shiftId},${lineIdFilterPart}`;
|
|
11886
|
+
const channelName = `all-workspace-metrics-g${index}-${group.date}-${group.shiftId}-${Date.now()}`.replace(
|
|
11887
|
+
/[^a-zA-Z0-9_-]/g,
|
|
11888
|
+
""
|
|
11889
|
+
);
|
|
11890
|
+
return supabase.channel(channelName).on(
|
|
11891
|
+
"postgres_changes",
|
|
11892
|
+
{
|
|
11893
|
+
event: "*",
|
|
11894
|
+
schema,
|
|
11895
|
+
table: metricsTable,
|
|
11896
|
+
filter: filter2
|
|
11897
|
+
},
|
|
11898
|
+
async (payload) => {
|
|
11899
|
+
const data = payload.new || payload.old;
|
|
11900
|
+
const dataKey = `${data?.date}-${data?.shift_id}`;
|
|
11901
|
+
if (!validDateShiftCombos.has(dataKey)) {
|
|
11902
|
+
return;
|
|
11056
11903
|
}
|
|
11057
|
-
fetchTimeoutRef.current
|
|
11058
|
-
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
|
|
11904
|
+
if (fetchTimeoutRef.current) {
|
|
11905
|
+
clearTimeout(fetchTimeoutRef.current);
|
|
11906
|
+
}
|
|
11907
|
+
fetchTimeoutRef.current = setTimeout(async () => {
|
|
11908
|
+
if (!isFetchingRef.current) {
|
|
11909
|
+
await fetchWorkspaceMetrics();
|
|
11910
|
+
}
|
|
11911
|
+
fetchTimeoutRef.current = null;
|
|
11912
|
+
}, 300);
|
|
11913
|
+
}
|
|
11914
|
+
).subscribe();
|
|
11915
|
+
});
|
|
11062
11916
|
};
|
|
11063
|
-
const
|
|
11917
|
+
const channels = setupSubscription();
|
|
11064
11918
|
return () => {
|
|
11065
11919
|
if (fetchTimeoutRef.current) {
|
|
11066
11920
|
clearTimeout(fetchTimeoutRef.current);
|
|
11067
11921
|
fetchTimeoutRef.current = null;
|
|
11068
11922
|
}
|
|
11069
|
-
|
|
11070
|
-
supabase.removeChannel(channel);
|
|
11071
|
-
}
|
|
11923
|
+
channels.forEach((channel) => supabase.removeChannel(channel));
|
|
11072
11924
|
};
|
|
11073
|
-
}, [
|
|
11925
|
+
}, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId, configuredLineIds]);
|
|
11074
11926
|
useEffect(() => {
|
|
11075
11927
|
setInitialized(false);
|
|
11076
|
-
}, [
|
|
11928
|
+
}, [fallbackQueryDate, fallbackQueryShiftId, shiftGroups]);
|
|
11077
11929
|
const refreshWorkspaces = useCallback(() => fetchWorkspaceMetrics(), [fetchWorkspaceMetrics]);
|
|
11078
11930
|
return useMemo(
|
|
11079
11931
|
() => ({ workspaces, loading, error, refreshWorkspaces }),
|
|
@@ -11562,127 +12414,6 @@ function useClipTypesWithCounts(workspaceId, date, shiftId, totalOutput, options
|
|
|
11562
12414
|
counts
|
|
11563
12415
|
};
|
|
11564
12416
|
}
|
|
11565
|
-
var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
|
|
11566
|
-
const [shiftConfigMap, setShiftConfigMap] = useState(/* @__PURE__ */ new Map());
|
|
11567
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
11568
|
-
const [error, setError] = useState(null);
|
|
11569
|
-
const supabase = useSupabase();
|
|
11570
|
-
const lineIdsKey = useMemo(() => lineIds.sort().join(","), [lineIds]);
|
|
11571
|
-
useEffect(() => {
|
|
11572
|
-
if (!lineIds || lineIds.length === 0) {
|
|
11573
|
-
setShiftConfigMap(/* @__PURE__ */ new Map());
|
|
11574
|
-
setIsLoading(false);
|
|
11575
|
-
return;
|
|
11576
|
-
}
|
|
11577
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11578
|
-
const validLineIds = lineIds.filter((id3) => uuidRegex.test(id3));
|
|
11579
|
-
if (validLineIds.length === 0) {
|
|
11580
|
-
console.warn("[useMultiLineShiftConfigs] No valid line IDs provided");
|
|
11581
|
-
setShiftConfigMap(/* @__PURE__ */ new Map());
|
|
11582
|
-
setIsLoading(false);
|
|
11583
|
-
return;
|
|
11584
|
-
}
|
|
11585
|
-
let mounted = true;
|
|
11586
|
-
const calculateBreakDuration2 = (startTime, endTime) => {
|
|
11587
|
-
const [sh, sm] = startTime.split(":").map(Number);
|
|
11588
|
-
const [eh, em] = endTime.split(":").map(Number);
|
|
11589
|
-
let startMinutes = sh * 60 + sm;
|
|
11590
|
-
let endMinutes = eh * 60 + em;
|
|
11591
|
-
if (endMinutes < startMinutes) {
|
|
11592
|
-
endMinutes += 24 * 60;
|
|
11593
|
-
}
|
|
11594
|
-
return endMinutes - startMinutes;
|
|
11595
|
-
};
|
|
11596
|
-
const stripSeconds = (timeStr) => {
|
|
11597
|
-
if (!timeStr) return timeStr;
|
|
11598
|
-
return timeStr.substring(0, 5);
|
|
11599
|
-
};
|
|
11600
|
-
const buildShiftConfigFromRows = (shifts, fallback) => {
|
|
11601
|
-
const mapped = shifts.map((shift) => ({
|
|
11602
|
-
shiftId: shift.shift_id,
|
|
11603
|
-
shiftName: shift.shift_name || `Shift ${shift.shift_id}`,
|
|
11604
|
-
startTime: stripSeconds(shift.start_time),
|
|
11605
|
-
endTime: stripSeconds(shift.end_time),
|
|
11606
|
-
breaks: Array.isArray(shift.breaks) ? shift.breaks.map((b) => ({
|
|
11607
|
-
startTime: b.start || b.startTime || "00:00",
|
|
11608
|
-
endTime: b.end || b.endTime || "00:00",
|
|
11609
|
-
duration: calculateBreakDuration2(
|
|
11610
|
-
b.start || b.startTime || "00:00",
|
|
11611
|
-
b.end || b.endTime || "00:00"
|
|
11612
|
-
),
|
|
11613
|
-
remarks: b.remarks || b.name || ""
|
|
11614
|
-
})) : [],
|
|
11615
|
-
timezone: shift.timezone
|
|
11616
|
-
}));
|
|
11617
|
-
const day = mapped.find((s) => s.shiftId === 0);
|
|
11618
|
-
const night = mapped.find((s) => s.shiftId === 1);
|
|
11619
|
-
return {
|
|
11620
|
-
shifts: mapped,
|
|
11621
|
-
timezone: mapped[0]?.timezone,
|
|
11622
|
-
dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallback?.dayShift,
|
|
11623
|
-
nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallback?.nightShift,
|
|
11624
|
-
transitionPeriodMinutes: fallback?.transitionPeriodMinutes || 0
|
|
11625
|
-
};
|
|
11626
|
-
};
|
|
11627
|
-
const fetchAllConfigs = async () => {
|
|
11628
|
-
try {
|
|
11629
|
-
setIsLoading(true);
|
|
11630
|
-
setError(null);
|
|
11631
|
-
console.log(`[useMultiLineShiftConfigs] Fetching shift configs for ${validLineIds.length} lines`);
|
|
11632
|
-
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);
|
|
11633
|
-
if (fetchError) {
|
|
11634
|
-
console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
|
|
11635
|
-
throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
|
|
11636
|
-
}
|
|
11637
|
-
const lineShiftsMap = /* @__PURE__ */ new Map();
|
|
11638
|
-
data?.forEach((row) => {
|
|
11639
|
-
if (!lineShiftsMap.has(row.line_id)) {
|
|
11640
|
-
lineShiftsMap.set(row.line_id, []);
|
|
11641
|
-
}
|
|
11642
|
-
lineShiftsMap.get(row.line_id).push(row);
|
|
11643
|
-
});
|
|
11644
|
-
const configMap = /* @__PURE__ */ new Map();
|
|
11645
|
-
lineShiftsMap.forEach((shifts, lineId) => {
|
|
11646
|
-
const config = buildShiftConfigFromRows(shifts, fallbackConfig);
|
|
11647
|
-
configMap.set(lineId, config);
|
|
11648
|
-
});
|
|
11649
|
-
validLineIds.forEach((lineId) => {
|
|
11650
|
-
if (!configMap.has(lineId) && fallbackConfig) {
|
|
11651
|
-
console.warn(`[useMultiLineShiftConfigs] No config found for line ${lineId}, using fallback`);
|
|
11652
|
-
configMap.set(lineId, fallbackConfig);
|
|
11653
|
-
}
|
|
11654
|
-
});
|
|
11655
|
-
console.log(`[useMultiLineShiftConfigs] Built configs for ${configMap.size} lines`);
|
|
11656
|
-
if (mounted) {
|
|
11657
|
-
setShiftConfigMap(configMap);
|
|
11658
|
-
setIsLoading(false);
|
|
11659
|
-
}
|
|
11660
|
-
} catch (err) {
|
|
11661
|
-
console.error("[useMultiLineShiftConfigs] Error:", err);
|
|
11662
|
-
if (mounted) {
|
|
11663
|
-
setError(err instanceof Error ? err.message : "Failed to fetch shift configs");
|
|
11664
|
-
if (fallbackConfig) {
|
|
11665
|
-
const fallbackMap = /* @__PURE__ */ new Map();
|
|
11666
|
-
validLineIds.forEach((lineId) => {
|
|
11667
|
-
fallbackMap.set(lineId, fallbackConfig);
|
|
11668
|
-
});
|
|
11669
|
-
setShiftConfigMap(fallbackMap);
|
|
11670
|
-
}
|
|
11671
|
-
setIsLoading(false);
|
|
11672
|
-
}
|
|
11673
|
-
}
|
|
11674
|
-
};
|
|
11675
|
-
fetchAllConfigs();
|
|
11676
|
-
return () => {
|
|
11677
|
-
mounted = false;
|
|
11678
|
-
};
|
|
11679
|
-
}, [lineIdsKey, supabase]);
|
|
11680
|
-
return {
|
|
11681
|
-
shiftConfigMap,
|
|
11682
|
-
isLoading,
|
|
11683
|
-
error
|
|
11684
|
-
};
|
|
11685
|
-
};
|
|
11686
12417
|
var MAX_RETRIES = 10;
|
|
11687
12418
|
var RETRY_DELAY = 500;
|
|
11688
12419
|
function useNavigation(customNavigate) {
|
|
@@ -12629,6 +13360,85 @@ function useLineSupervisor(lineId) {
|
|
|
12629
13360
|
error
|
|
12630
13361
|
};
|
|
12631
13362
|
}
|
|
13363
|
+
var useSupervisorsByLineIds = (lineIds, options) => {
|
|
13364
|
+
const supabase = useSupabase();
|
|
13365
|
+
const enabled = options?.enabled ?? true;
|
|
13366
|
+
const lineIdsKey = useMemo(() => (lineIds || []).slice().sort().join(","), [lineIds]);
|
|
13367
|
+
const targetLineIdSet = useMemo(() => new Set(lineIds || []), [lineIdsKey]);
|
|
13368
|
+
const [supervisorsByLineId, setSupervisorsByLineId] = useState(/* @__PURE__ */ new Map());
|
|
13369
|
+
const [supervisorNamesByLineId, setSupervisorNamesByLineId] = useState(/* @__PURE__ */ new Map());
|
|
13370
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
13371
|
+
const [error, setError] = useState(null);
|
|
13372
|
+
const fetchSupervisors = useCallback(async () => {
|
|
13373
|
+
if (!enabled || !supabase) {
|
|
13374
|
+
setIsLoading(false);
|
|
13375
|
+
return;
|
|
13376
|
+
}
|
|
13377
|
+
try {
|
|
13378
|
+
setIsLoading(true);
|
|
13379
|
+
setError(null);
|
|
13380
|
+
const { data, error: fetchError } = await supabase.from("user_roles").select("user_id, email, properties").eq("role_level", "supervisor");
|
|
13381
|
+
if (fetchError) {
|
|
13382
|
+
throw fetchError;
|
|
13383
|
+
}
|
|
13384
|
+
const nextSupervisorsByLineId = /* @__PURE__ */ new Map();
|
|
13385
|
+
(data || []).forEach((row) => {
|
|
13386
|
+
const assignedLineIds = row?.properties?.line_id || row?.properties?.line_ids || [];
|
|
13387
|
+
if (!Array.isArray(assignedLineIds) || assignedLineIds.length === 0) return;
|
|
13388
|
+
const email = row.email || "";
|
|
13389
|
+
const displayName = (typeof email === "string" && email.includes("@") ? email.split("@")[0] : email) || email;
|
|
13390
|
+
const supervisor = {
|
|
13391
|
+
userId: row.user_id,
|
|
13392
|
+
email,
|
|
13393
|
+
displayName
|
|
13394
|
+
};
|
|
13395
|
+
assignedLineIds.forEach((lineId) => {
|
|
13396
|
+
if (typeof lineId !== "string") return;
|
|
13397
|
+
if (targetLineIdSet.size > 0 && !targetLineIdSet.has(lineId)) return;
|
|
13398
|
+
const existing = nextSupervisorsByLineId.get(lineId) || [];
|
|
13399
|
+
existing.push(supervisor);
|
|
13400
|
+
nextSupervisorsByLineId.set(lineId, existing);
|
|
13401
|
+
});
|
|
13402
|
+
});
|
|
13403
|
+
const nextSupervisorNamesByLineId = /* @__PURE__ */ new Map();
|
|
13404
|
+
nextSupervisorsByLineId.forEach((supervisors, lineId) => {
|
|
13405
|
+
nextSupervisorNamesByLineId.set(lineId, supervisors.map((s) => s.displayName).join(", "));
|
|
13406
|
+
});
|
|
13407
|
+
setSupervisorsByLineId(nextSupervisorsByLineId);
|
|
13408
|
+
setSupervisorNamesByLineId(nextSupervisorNamesByLineId);
|
|
13409
|
+
} catch (err) {
|
|
13410
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch supervisors"));
|
|
13411
|
+
setSupervisorsByLineId(/* @__PURE__ */ new Map());
|
|
13412
|
+
setSupervisorNamesByLineId(/* @__PURE__ */ new Map());
|
|
13413
|
+
} finally {
|
|
13414
|
+
setIsLoading(false);
|
|
13415
|
+
}
|
|
13416
|
+
}, [enabled, supabase, targetLineIdSet]);
|
|
13417
|
+
useEffect(() => {
|
|
13418
|
+
fetchSupervisors();
|
|
13419
|
+
}, [fetchSupervisors, lineIdsKey]);
|
|
13420
|
+
useEffect(() => {
|
|
13421
|
+
if (!enabled || !supabase) return;
|
|
13422
|
+
let channel = null;
|
|
13423
|
+
channel = supabase.channel("supervisors-by-line").on(
|
|
13424
|
+
"postgres_changes",
|
|
13425
|
+
{ event: "*", schema: "public", table: "user_roles", filter: "role_level=eq.supervisor" },
|
|
13426
|
+
() => {
|
|
13427
|
+
fetchSupervisors();
|
|
13428
|
+
}
|
|
13429
|
+
).subscribe();
|
|
13430
|
+
return () => {
|
|
13431
|
+
if (channel) supabase.removeChannel(channel);
|
|
13432
|
+
};
|
|
13433
|
+
}, [enabled, supabase, fetchSupervisors]);
|
|
13434
|
+
return {
|
|
13435
|
+
supervisorNamesByLineId,
|
|
13436
|
+
supervisorsByLineId,
|
|
13437
|
+
isLoading,
|
|
13438
|
+
error,
|
|
13439
|
+
refetch: fetchSupervisors
|
|
13440
|
+
};
|
|
13441
|
+
};
|
|
12632
13442
|
function useIdleTimeReasons({
|
|
12633
13443
|
workspaceId,
|
|
12634
13444
|
lineId,
|
|
@@ -13034,6 +13844,102 @@ var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig, li
|
|
|
13034
13844
|
return fullName;
|
|
13035
13845
|
};
|
|
13036
13846
|
|
|
13847
|
+
// src/lib/utils/kpis.ts
|
|
13848
|
+
var toNumber = (value) => {
|
|
13849
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
13850
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
13851
|
+
const parsed = Number(value);
|
|
13852
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
13853
|
+
}
|
|
13854
|
+
return 0;
|
|
13855
|
+
};
|
|
13856
|
+
var createDefaultKPIs = () => ({
|
|
13857
|
+
underperformingWorkers: { current: 0, total: 0, change: 0 },
|
|
13858
|
+
efficiency: { value: 0, change: 0 },
|
|
13859
|
+
outputProgress: { current: 0, target: 0, idealOutput: 0, change: 0 },
|
|
13860
|
+
avgCycleTime: { value: 0, change: 0 },
|
|
13861
|
+
qualityCompliance: { value: 95, change: 0 }
|
|
13862
|
+
});
|
|
13863
|
+
var buildKPIsFromLineMetricsRow = (row) => {
|
|
13864
|
+
if (!row) return createDefaultKPIs();
|
|
13865
|
+
const avgEfficiency = toNumber(row.avg_efficiency);
|
|
13866
|
+
const avgCycleTime = toNumber(row.avg_cycle_time);
|
|
13867
|
+
const currentOutput = toNumber(row.current_output);
|
|
13868
|
+
const lineThreshold = toNumber(row.line_threshold);
|
|
13869
|
+
const idealOutput = toNumber(row.ideal_output) || lineThreshold || 0;
|
|
13870
|
+
const underperformingWorkspaces = toNumber(row.underperforming_workspaces);
|
|
13871
|
+
const totalWorkspaces = toNumber(row.total_workspaces);
|
|
13872
|
+
return {
|
|
13873
|
+
underperformingWorkers: {
|
|
13874
|
+
current: underperformingWorkspaces,
|
|
13875
|
+
total: totalWorkspaces,
|
|
13876
|
+
change: 0
|
|
13877
|
+
},
|
|
13878
|
+
efficiency: {
|
|
13879
|
+
value: avgEfficiency,
|
|
13880
|
+
change: 0
|
|
13881
|
+
},
|
|
13882
|
+
outputProgress: {
|
|
13883
|
+
current: currentOutput,
|
|
13884
|
+
target: lineThreshold,
|
|
13885
|
+
idealOutput,
|
|
13886
|
+
change: 0
|
|
13887
|
+
},
|
|
13888
|
+
avgCycleTime: {
|
|
13889
|
+
value: avgCycleTime,
|
|
13890
|
+
change: 0
|
|
13891
|
+
},
|
|
13892
|
+
qualityCompliance: {
|
|
13893
|
+
value: 95,
|
|
13894
|
+
change: 0
|
|
13895
|
+
}
|
|
13896
|
+
};
|
|
13897
|
+
};
|
|
13898
|
+
var aggregateKPIsFromLineMetricsRows = (rows) => {
|
|
13899
|
+
if (!rows || rows.length === 0) return createDefaultKPIs();
|
|
13900
|
+
const currentOutputSum = rows.reduce((sum, row) => sum + toNumber(row.current_output), 0);
|
|
13901
|
+
const lineThresholdSum = rows.reduce((sum, row) => sum + toNumber(row.line_threshold), 0);
|
|
13902
|
+
const idealOutputSum = rows.reduce(
|
|
13903
|
+
(sum, row) => sum + (toNumber(row.ideal_output) || toNumber(row.line_threshold)),
|
|
13904
|
+
0
|
|
13905
|
+
);
|
|
13906
|
+
const totalWorkspacesAll = rows.reduce((sum, row) => sum + toNumber(row.total_workspaces), 0);
|
|
13907
|
+
const weightedEfficiencySum = rows.reduce(
|
|
13908
|
+
(sum, row) => sum + toNumber(row.avg_efficiency) * toNumber(row.total_workspaces),
|
|
13909
|
+
0
|
|
13910
|
+
);
|
|
13911
|
+
const avgEfficiency = totalWorkspacesAll > 0 ? weightedEfficiencySum / totalWorkspacesAll : 0;
|
|
13912
|
+
const numLines = rows.length;
|
|
13913
|
+
const avgCycleTime = numLines > 0 ? rows.reduce((sum, row) => sum + toNumber(row.avg_cycle_time), 0) / numLines : 0;
|
|
13914
|
+
const totalUnderperforming = rows.reduce((sum, row) => sum + toNumber(row.underperforming_workspaces), 0);
|
|
13915
|
+
const totalWorkspaces = rows.reduce((sum, row) => sum + toNumber(row.total_workspaces), 0);
|
|
13916
|
+
return {
|
|
13917
|
+
underperformingWorkers: {
|
|
13918
|
+
current: totalUnderperforming,
|
|
13919
|
+
total: totalWorkspaces,
|
|
13920
|
+
change: 0
|
|
13921
|
+
},
|
|
13922
|
+
efficiency: {
|
|
13923
|
+
value: avgEfficiency,
|
|
13924
|
+
change: 0
|
|
13925
|
+
},
|
|
13926
|
+
outputProgress: {
|
|
13927
|
+
current: currentOutputSum,
|
|
13928
|
+
target: lineThresholdSum,
|
|
13929
|
+
idealOutput: idealOutputSum,
|
|
13930
|
+
change: 0
|
|
13931
|
+
},
|
|
13932
|
+
avgCycleTime: {
|
|
13933
|
+
value: avgCycleTime,
|
|
13934
|
+
change: 0
|
|
13935
|
+
},
|
|
13936
|
+
qualityCompliance: {
|
|
13937
|
+
value: 95,
|
|
13938
|
+
change: 0
|
|
13939
|
+
}
|
|
13940
|
+
};
|
|
13941
|
+
};
|
|
13942
|
+
|
|
13037
13943
|
// ../../node_modules/clsx/dist/clsx.mjs
|
|
13038
13944
|
function r(e) {
|
|
13039
13945
|
var t, f, n = "";
|
|
@@ -28070,6 +28976,7 @@ if (typeof document !== "undefined") {
|
|
|
28070
28976
|
}
|
|
28071
28977
|
}
|
|
28072
28978
|
var BASE_HLS_CONFIG = {
|
|
28979
|
+
// Keep buffer small to reduce wasted downloads on slow links
|
|
28073
28980
|
maxBufferLength: 3,
|
|
28074
28981
|
maxMaxBufferLength: 8,
|
|
28075
28982
|
maxBufferSize: 50 * 1e3 * 1e3,
|
|
@@ -28077,10 +28984,10 @@ var BASE_HLS_CONFIG = {
|
|
|
28077
28984
|
manifestLoadingTimeOut: 15e3,
|
|
28078
28985
|
manifestLoadingMaxRetry: 3,
|
|
28079
28986
|
manifestLoadingRetryDelay: 500,
|
|
28080
|
-
levelLoadingTimeOut:
|
|
28987
|
+
levelLoadingTimeOut: 2e4,
|
|
28081
28988
|
levelLoadingMaxRetry: 5,
|
|
28082
28989
|
levelLoadingRetryDelay: 500,
|
|
28083
|
-
fragLoadingTimeOut:
|
|
28990
|
+
fragLoadingTimeOut: 2e4,
|
|
28084
28991
|
fragLoadingMaxRetry: 5,
|
|
28085
28992
|
fragLoadingRetryDelay: 500,
|
|
28086
28993
|
startPosition: -1,
|
|
@@ -28093,7 +29000,10 @@ var BASE_HLS_CONFIG = {
|
|
|
28093
29000
|
abrBandWidthFactor: 0.95,
|
|
28094
29001
|
abrBandWidthUpFactor: 0.7,
|
|
28095
29002
|
abrMaxWithRealBitrate: false,
|
|
28096
|
-
|
|
29003
|
+
// Favor a conservative first rendition on constrained networks
|
|
29004
|
+
abrEwmaDefaultEstimate: 1e6,
|
|
29005
|
+
startLevel: 0,
|
|
29006
|
+
capLevelToPlayerSize: true
|
|
28097
29007
|
};
|
|
28098
29008
|
var HlsVideoPlayer = forwardRef(({
|
|
28099
29009
|
src,
|
|
@@ -29177,7 +30087,7 @@ var getSupabaseClient2 = () => {
|
|
|
29177
30087
|
}
|
|
29178
30088
|
return createClient(url, key);
|
|
29179
30089
|
};
|
|
29180
|
-
var
|
|
30090
|
+
var getAuthToken4 = async () => {
|
|
29181
30091
|
try {
|
|
29182
30092
|
const supabase = getSupabaseClient2();
|
|
29183
30093
|
const { data: { session } } = await supabase.auth.getSession();
|
|
@@ -29200,7 +30110,7 @@ function useWorkspaceCrop(workspaceId) {
|
|
|
29200
30110
|
setIsLoading(true);
|
|
29201
30111
|
setError(null);
|
|
29202
30112
|
try {
|
|
29203
|
-
const token = await
|
|
30113
|
+
const token = await getAuthToken4();
|
|
29204
30114
|
if (!token) {
|
|
29205
30115
|
throw new Error("Authentication required");
|
|
29206
30116
|
}
|
|
@@ -30249,7 +31159,7 @@ var FileManagerFilters = ({
|
|
|
30249
31159
|
const ROOT_CAUSE_OPTIONS = [
|
|
30250
31160
|
"Operator Absent",
|
|
30251
31161
|
"Operator Idle",
|
|
30252
|
-
"Machine
|
|
31162
|
+
"Machine Downtime",
|
|
30253
31163
|
"No Material"
|
|
30254
31164
|
];
|
|
30255
31165
|
const getIdleTimeRootCause = useCallback((clipId) => {
|
|
@@ -30289,7 +31199,7 @@ var FileManagerFilters = ({
|
|
|
30289
31199
|
method: "POST",
|
|
30290
31200
|
headers: {
|
|
30291
31201
|
"Content-Type": "application/json",
|
|
30292
|
-
"Authorization": `Bearer ${await
|
|
31202
|
+
"Authorization": `Bearer ${await getAuthToken5()}`
|
|
30293
31203
|
},
|
|
30294
31204
|
body: JSON.stringify({
|
|
30295
31205
|
action: "clip-metadata",
|
|
@@ -30322,7 +31232,7 @@ var FileManagerFilters = ({
|
|
|
30322
31232
|
});
|
|
30323
31233
|
}
|
|
30324
31234
|
}, [workspaceId, date, shift]);
|
|
30325
|
-
const
|
|
31235
|
+
const getAuthToken5 = async () => {
|
|
30326
31236
|
try {
|
|
30327
31237
|
const { createClient: createClient5 } = await import('@supabase/supabase-js');
|
|
30328
31238
|
const supabase = createClient5(
|
|
@@ -30348,7 +31258,7 @@ var FileManagerFilters = ({
|
|
|
30348
31258
|
method: "POST",
|
|
30349
31259
|
headers: {
|
|
30350
31260
|
"Content-Type": "application/json",
|
|
30351
|
-
"Authorization": `Bearer ${await
|
|
31261
|
+
"Authorization": `Bearer ${await getAuthToken5()}`
|
|
30352
31262
|
},
|
|
30353
31263
|
body: JSON.stringify({
|
|
30354
31264
|
action: "percentile-clips",
|
|
@@ -31992,7 +32902,7 @@ var BottlenecksContent = ({
|
|
|
31992
32902
|
fetchClipCounts();
|
|
31993
32903
|
}
|
|
31994
32904
|
}, [workspaceId, effectiveDateString, effectiveShiftId, s3ClipsService, fetchClipCounts, isEffectiveShiftReady]);
|
|
31995
|
-
const
|
|
32905
|
+
const getAuthToken5 = useCallback(async () => {
|
|
31996
32906
|
try {
|
|
31997
32907
|
const { createClient: createClient5 } = await import('@supabase/supabase-js');
|
|
31998
32908
|
const supabase = createClient5(
|
|
@@ -32011,7 +32921,7 @@ var BottlenecksContent = ({
|
|
|
32011
32921
|
const fetchTriageClips = async () => {
|
|
32012
32922
|
setIsLoadingTriageClips(true);
|
|
32013
32923
|
try {
|
|
32014
|
-
const token = await
|
|
32924
|
+
const token = await getAuthToken5();
|
|
32015
32925
|
if (!token) {
|
|
32016
32926
|
console.error("[BottlenecksContent] No auth token available");
|
|
32017
32927
|
return;
|
|
@@ -32059,7 +32969,7 @@ var BottlenecksContent = ({
|
|
|
32059
32969
|
}
|
|
32060
32970
|
};
|
|
32061
32971
|
fetchTriageClips();
|
|
32062
|
-
}, [triageMode, workspaceId, effectiveDateString, effectiveShiftId,
|
|
32972
|
+
}, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken5, isEffectiveShiftReady]);
|
|
32063
32973
|
useEffect(() => {
|
|
32064
32974
|
if (!triageMode || triageClips.length === 0 || !session?.access_token) {
|
|
32065
32975
|
return;
|
|
@@ -32390,7 +33300,20 @@ var BottlenecksContent = ({
|
|
|
32390
33300
|
updateActiveFilter(categoryId);
|
|
32391
33301
|
}
|
|
32392
33302
|
try {
|
|
32393
|
-
|
|
33303
|
+
const metadataPromise = loadCategoryMetadata(categoryId, false).catch((err) => {
|
|
33304
|
+
console.warn(`[BottlenecksContent] Metadata fetch error (non-blocking):`, err);
|
|
33305
|
+
return null;
|
|
33306
|
+
});
|
|
33307
|
+
const video = await s3ClipsService.getClipById(clipId);
|
|
33308
|
+
if (video) {
|
|
33309
|
+
setPendingVideo(video);
|
|
33310
|
+
setCurrentClipId(clipId);
|
|
33311
|
+
setAllVideos([video]);
|
|
33312
|
+
setCurrentIndex(0);
|
|
33313
|
+
} else {
|
|
33314
|
+
throw new Error(`Failed to load video data for clip ${clipId}`);
|
|
33315
|
+
}
|
|
33316
|
+
await metadataPromise;
|
|
32394
33317
|
let metadataArray = categoryMetadataRef.current;
|
|
32395
33318
|
const clipExistsInMetadata = metadataArray.some((clip) => clip.clipId === clipId);
|
|
32396
33319
|
if (metadataArray.length === 0 || !clipExistsInMetadata) {
|
|
@@ -32407,16 +33330,7 @@ var BottlenecksContent = ({
|
|
|
32407
33330
|
}
|
|
32408
33331
|
setCurrentMetadataIndex(clickedClipIndex);
|
|
32409
33332
|
currentMetadataIndexRef.current = clickedClipIndex;
|
|
32410
|
-
|
|
32411
|
-
if (video) {
|
|
32412
|
-
setPendingVideo(video);
|
|
32413
|
-
setCurrentClipId(clipId);
|
|
32414
|
-
setAllVideos([video]);
|
|
32415
|
-
setCurrentIndex(0);
|
|
32416
|
-
console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
|
|
32417
|
-
} else {
|
|
32418
|
-
throw new Error(`Failed to load video data for clip ${clipId}`);
|
|
32419
|
-
}
|
|
33333
|
+
console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
|
|
32420
33334
|
} catch (error2) {
|
|
32421
33335
|
console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
|
|
32422
33336
|
if (isMountedRef.current) {
|
|
@@ -35303,7 +36217,7 @@ var STATIC_COLORS = {
|
|
|
35303
36217
|
// red-600 - Critical/Urgent
|
|
35304
36218
|
"No Material": "#f59e0b",
|
|
35305
36219
|
// amber-500 - Warning/Supply Chain
|
|
35306
|
-
"Machine
|
|
36220
|
+
"Machine Downtime": "#3b82f6",
|
|
35307
36221
|
// blue-500 - Scheduled/Technical
|
|
35308
36222
|
"Operator Idle": "#8b5cf6"
|
|
35309
36223
|
// violet-500 - Low Priority/Behavioral
|
|
@@ -37688,7 +38602,7 @@ var WorkspaceWhatsAppShareButton = ({
|
|
|
37688
38602
|
}
|
|
37689
38603
|
);
|
|
37690
38604
|
};
|
|
37691
|
-
var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
38605
|
+
var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
|
|
37692
38606
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
37693
38607
|
const entityConfig = useEntityConfig();
|
|
37694
38608
|
const generatePDF = async () => {
|
|
@@ -37763,8 +38677,10 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
37763
38677
|
doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
|
|
37764
38678
|
};
|
|
37765
38679
|
const perfOverviewStartY = 93;
|
|
38680
|
+
const hasIdleTimeReason = idleTimeReasons && idleTimeReasons.length > 0;
|
|
38681
|
+
const perfOverviewHeight = hasIdleTimeReason ? 80 : 70;
|
|
37766
38682
|
doc.setFillColor(245, 245, 245);
|
|
37767
|
-
doc.roundedRect(15, perfOverviewStartY, 180,
|
|
38683
|
+
doc.roundedRect(15, perfOverviewStartY, 180, perfOverviewHeight, 3, 3, "F");
|
|
37768
38684
|
doc.setFontSize(18);
|
|
37769
38685
|
doc.setFont("helvetica", "bold");
|
|
37770
38686
|
doc.setTextColor(40, 40, 40);
|
|
@@ -37788,32 +38704,68 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
|
|
|
37788
38704
|
doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 2);
|
|
37789
38705
|
doc.setFont("helvetica", "bold");
|
|
37790
38706
|
doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing * 2);
|
|
38707
|
+
createKPIBox(kpiStartY + kpiSpacing * 3);
|
|
38708
|
+
doc.setFont("helvetica", "normal");
|
|
38709
|
+
doc.text("Total Idle Time:", 25, kpiStartY + kpiSpacing * 3);
|
|
38710
|
+
doc.setFont("helvetica", "bold");
|
|
38711
|
+
const idleTimeFormatted = formatIdleTime(workspace.idle_time);
|
|
38712
|
+
doc.text(idleTimeFormatted, 120, kpiStartY + kpiSpacing * 3);
|
|
38713
|
+
if (hasIdleTimeReason) {
|
|
38714
|
+
createKPIBox(kpiStartY + kpiSpacing * 4);
|
|
38715
|
+
doc.setFont("helvetica", "normal");
|
|
38716
|
+
doc.text("Top Idle Reason:", 25, kpiStartY + kpiSpacing * 4);
|
|
38717
|
+
doc.setFont("helvetica", "bold");
|
|
38718
|
+
const topReason = idleTimeReasons[0];
|
|
38719
|
+
const reasonName = topReason.name.replace(/_/g, " ");
|
|
38720
|
+
const reasonText = `${reasonName} (${topReason.value.toFixed(1)}%)`;
|
|
38721
|
+
doc.text(reasonText, 120, kpiStartY + kpiSpacing * 4);
|
|
38722
|
+
}
|
|
38723
|
+
const separatorBeforeHourlyY = hasIdleTimeReason ? 183 : 173;
|
|
37791
38724
|
doc.setDrawColor(180, 180, 180);
|
|
37792
38725
|
doc.setLineWidth(0.8);
|
|
37793
|
-
doc.line(20,
|
|
37794
|
-
const hourlyPerfStartY =
|
|
38726
|
+
doc.line(20, separatorBeforeHourlyY, 190, separatorBeforeHourlyY);
|
|
38727
|
+
const hourlyPerfStartY = hasIdleTimeReason ? 188 : 178;
|
|
37795
38728
|
const hourlyData = workspace.hourly_action_counts || [];
|
|
37796
38729
|
const hourlyTarget = workspace.pph_threshold;
|
|
37797
|
-
const
|
|
37798
|
-
const
|
|
38730
|
+
const pageHeight = doc.internal.pageSize.height;
|
|
38731
|
+
const maxContentY = pageHeight - 15;
|
|
38732
|
+
const baseTableStartY = hasIdleTimeReason ? 219 : 209;
|
|
38733
|
+
let tableStartY = baseTableStartY;
|
|
38734
|
+
let rowHeight = 8;
|
|
38735
|
+
let headerFontSize = 11;
|
|
38736
|
+
let contentFontSize = 11;
|
|
38737
|
+
let titleFontSize = 18;
|
|
37799
38738
|
const bottomPadding = 8;
|
|
38739
|
+
const estimatedEndY = tableStartY + hourlyData.length * rowHeight + bottomPadding;
|
|
38740
|
+
if (estimatedEndY > maxContentY) {
|
|
38741
|
+
rowHeight = 6.5;
|
|
38742
|
+
headerFontSize = 9;
|
|
38743
|
+
contentFontSize = 9;
|
|
38744
|
+
titleFontSize = 16;
|
|
38745
|
+
tableStartY = hasIdleTimeReason ? 215 : 205;
|
|
38746
|
+
}
|
|
37800
38747
|
const backgroundHeight = tableStartY - hourlyPerfStartY + hourlyData.length * rowHeight + bottomPadding;
|
|
37801
38748
|
doc.setFillColor(245, 245, 245);
|
|
37802
38749
|
doc.roundedRect(15, hourlyPerfStartY, 180, backgroundHeight, 3, 3, "F");
|
|
37803
|
-
doc.setFontSize(
|
|
38750
|
+
doc.setFontSize(titleFontSize);
|
|
37804
38751
|
doc.setFont("helvetica", "bold");
|
|
37805
38752
|
doc.setTextColor(40, 40, 40);
|
|
37806
|
-
|
|
38753
|
+
const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
|
|
38754
|
+
doc.text("Hourly Performance", 20, hourlyTitleY);
|
|
37807
38755
|
doc.setTextColor(0, 0, 0);
|
|
37808
|
-
doc.setFontSize(
|
|
38756
|
+
doc.setFontSize(headerFontSize);
|
|
37809
38757
|
doc.setFont("helvetica", "bold");
|
|
37810
|
-
|
|
37811
|
-
|
|
37812
|
-
doc.text("
|
|
37813
|
-
doc.text("
|
|
38758
|
+
const baseHeaderY = hasIdleTimeReason ? 208 : 198;
|
|
38759
|
+
const headerY = titleFontSize === 16 ? hasIdleTimeReason ? 206 : 196 : baseHeaderY;
|
|
38760
|
+
doc.text("Time Range", 25, headerY);
|
|
38761
|
+
doc.text("Output", 95, headerY);
|
|
38762
|
+
doc.text("Target", 135, headerY);
|
|
38763
|
+
doc.text("Status", 170, headerY);
|
|
37814
38764
|
doc.setLineWidth(0.3);
|
|
37815
38765
|
doc.setDrawColor(200, 200, 200);
|
|
37816
|
-
|
|
38766
|
+
const separatorY = headerY + 3;
|
|
38767
|
+
doc.line(20, separatorY, 190, separatorY);
|
|
38768
|
+
doc.setFontSize(contentFontSize);
|
|
37817
38769
|
doc.setFont("helvetica", "normal");
|
|
37818
38770
|
let yPos = tableStartY;
|
|
37819
38771
|
const workspaceDate = new Date(workspace.date);
|
|
@@ -45237,6 +46189,10 @@ function HomeView({
|
|
|
45237
46189
|
const [diagnosisModalOpen, setDiagnosisModalOpen] = useState(false);
|
|
45238
46190
|
const [diagnoses, setDiagnoses] = useState([]);
|
|
45239
46191
|
const timezone = useAppTimezone();
|
|
46192
|
+
const { shiftConfigMap: lineShiftConfigs } = useMultiLineShiftConfigs(
|
|
46193
|
+
allLineIds,
|
|
46194
|
+
dashboardConfig?.shiftConfig
|
|
46195
|
+
);
|
|
45240
46196
|
useEffect(() => {
|
|
45241
46197
|
const initDisplayNames = async () => {
|
|
45242
46198
|
try {
|
|
@@ -45261,23 +46217,6 @@ function HomeView({
|
|
|
45261
46217
|
loading: displayNamesLoading,
|
|
45262
46218
|
error: displayNamesError
|
|
45263
46219
|
} = useWorkspaceDisplayNames(displayNameLineId, void 0);
|
|
45264
|
-
useCallback(() => {
|
|
45265
|
-
console.log("Refetching KPIs after line metrics update");
|
|
45266
|
-
}, []);
|
|
45267
|
-
const {
|
|
45268
|
-
kpis,
|
|
45269
|
-
isLoading: kpisLoading,
|
|
45270
|
-
error: kpisError,
|
|
45271
|
-
refetch: refetchKPIs
|
|
45272
|
-
} = useLineKPIs({
|
|
45273
|
-
lineId: selectedLineId
|
|
45274
|
-
});
|
|
45275
|
-
const onLineMetricsUpdate = useCallback(() => {
|
|
45276
|
-
const timer = setTimeout(() => {
|
|
45277
|
-
refetchKPIs();
|
|
45278
|
-
}, 1e3);
|
|
45279
|
-
return () => clearTimeout(timer);
|
|
45280
|
-
}, [refetchKPIs]);
|
|
45281
46220
|
const {
|
|
45282
46221
|
workspaceMetrics,
|
|
45283
46222
|
lineMetrics,
|
|
@@ -45286,10 +46225,22 @@ function HomeView({
|
|
|
45286
46225
|
refetch: refetchMetrics
|
|
45287
46226
|
} = useDashboardMetrics({
|
|
45288
46227
|
lineId: selectedLineId,
|
|
45289
|
-
onLineMetricsUpdate,
|
|
45290
46228
|
userAccessibleLineIds: allLineIds
|
|
45291
46229
|
// Pass user's accessible lines for supervisor filtering
|
|
45292
46230
|
});
|
|
46231
|
+
const kpis = useMemo(() => {
|
|
46232
|
+
const lineMetricsRows = lineMetrics || [];
|
|
46233
|
+
if (selectedLineId === factoryViewId) {
|
|
46234
|
+
if (metricsLoading && lineMetricsRows.length === 0) return null;
|
|
46235
|
+
return aggregateKPIsFromLineMetricsRows(lineMetricsRows);
|
|
46236
|
+
}
|
|
46237
|
+
const row = lineMetricsRows.find((r2) => r2?.line_id === selectedLineId);
|
|
46238
|
+
if (!row) {
|
|
46239
|
+
if (metricsLoading) return null;
|
|
46240
|
+
return buildKPIsFromLineMetricsRow(null);
|
|
46241
|
+
}
|
|
46242
|
+
return buildKPIsFromLineMetricsRow(row);
|
|
46243
|
+
}, [selectedLineId, factoryViewId, lineMetrics, metricsLoading]);
|
|
45293
46244
|
const {
|
|
45294
46245
|
activeBreaks: allActiveBreaks,
|
|
45295
46246
|
isLoading: breaksLoading,
|
|
@@ -45334,12 +46285,18 @@ function HomeView({
|
|
|
45334
46285
|
label: "Diagnose",
|
|
45335
46286
|
onClick: async () => {
|
|
45336
46287
|
console.log("\u{1F50D} Investigating bottleneck:", bottleneck.log_number);
|
|
45337
|
-
const operationalDate = getOperationalDate(
|
|
45338
|
-
timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC"
|
|
45339
|
-
);
|
|
45340
46288
|
const tz = timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC";
|
|
45341
|
-
const
|
|
46289
|
+
const lineShiftConfig = bottleneck.line_id && lineShiftConfigs.get(bottleneck.line_id);
|
|
46290
|
+
const effectiveShiftConfig = lineShiftConfig || dashboardConfig?.shiftConfig;
|
|
46291
|
+
const shiftResult = getCurrentShift(tz, effectiveShiftConfig);
|
|
45342
46292
|
const currentShift = shiftResult.shiftId;
|
|
46293
|
+
const operationalDate = shiftResult.date;
|
|
46294
|
+
console.log("\u{1F550} Using shift config for line:", {
|
|
46295
|
+
lineId: bottleneck.line_id,
|
|
46296
|
+
hasLineConfig: !!lineShiftConfig,
|
|
46297
|
+
shiftId: currentShift,
|
|
46298
|
+
date: operationalDate
|
|
46299
|
+
});
|
|
45343
46300
|
console.log("\u{1F3AC} [Investigate] Opening clips modal with data:", {
|
|
45344
46301
|
workspaceId: bottleneck.workspace_id,
|
|
45345
46302
|
ticketId: bottleneck.id,
|
|
@@ -45409,12 +46366,18 @@ function HomeView({
|
|
|
45409
46366
|
state,
|
|
45410
46367
|
priority: bottleneck.priority
|
|
45411
46368
|
});
|
|
45412
|
-
const operationalDate = getOperationalDate(
|
|
45413
|
-
timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC"
|
|
45414
|
-
);
|
|
45415
46369
|
const tz = timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC";
|
|
45416
|
-
const
|
|
46370
|
+
const lineShiftConfig = bottleneck.line_id && lineShiftConfigs.get(bottleneck.line_id);
|
|
46371
|
+
const effectiveShiftConfig = lineShiftConfig || dashboardConfig?.shiftConfig;
|
|
46372
|
+
const shiftResult = getCurrentShift(tz, effectiveShiftConfig);
|
|
45417
46373
|
const currentShift = shiftResult.shiftId;
|
|
46374
|
+
const operationalDate = shiftResult.date;
|
|
46375
|
+
console.log("\u{1F550} Using shift config for line:", {
|
|
46376
|
+
lineId: bottleneck.line_id,
|
|
46377
|
+
hasLineConfig: !!lineShiftConfig,
|
|
46378
|
+
shiftId: currentShift,
|
|
46379
|
+
date: operationalDate
|
|
46380
|
+
});
|
|
45418
46381
|
setBottleneckModalData({
|
|
45419
46382
|
workspaceId: bottleneck.workspace_id,
|
|
45420
46383
|
workspaceName: bottleneck_workspace.workspace_name || "Unknown Workspace",
|
|
@@ -45471,7 +46434,7 @@ function HomeView({
|
|
|
45471
46434
|
};
|
|
45472
46435
|
setBottleneckNotification(errorNotification);
|
|
45473
46436
|
}
|
|
45474
|
-
}, [notificationService, timezone, dashboardConfig]);
|
|
46437
|
+
}, [notificationService, timezone, dashboardConfig, lineShiftConfigs]);
|
|
45475
46438
|
useEffect(() => {
|
|
45476
46439
|
const ticketsEnabled = dashboardConfig?.ticketsConfig?.enabled ?? true;
|
|
45477
46440
|
if (!ticketsEnabled) {
|
|
@@ -45596,12 +46559,10 @@ function HomeView({
|
|
|
45596
46559
|
useEffect(() => {
|
|
45597
46560
|
if (metricsError) {
|
|
45598
46561
|
setErrorMessage(metricsError.message);
|
|
45599
|
-
} else if (kpisError) {
|
|
45600
|
-
setErrorMessage(kpisError.message);
|
|
45601
46562
|
} else {
|
|
45602
46563
|
setErrorMessage(null);
|
|
45603
46564
|
}
|
|
45604
|
-
}, [metricsError
|
|
46565
|
+
}, [metricsError]);
|
|
45605
46566
|
const handleLineChange = useCallback((value) => {
|
|
45606
46567
|
setIsChangingFilter(true);
|
|
45607
46568
|
setSelectedLineId(value);
|
|
@@ -45617,14 +46578,14 @@ function HomeView({
|
|
|
45617
46578
|
}
|
|
45618
46579
|
}, [LINE_FILTER_STORAGE_KEY]);
|
|
45619
46580
|
useEffect(() => {
|
|
45620
|
-
if (!metricsLoading &&
|
|
46581
|
+
if (!metricsLoading && isChangingFilter) {
|
|
45621
46582
|
if (workspaceMetrics.length > 0 || selectedLineId === factoryViewId) {
|
|
45622
46583
|
setIsChangingFilter(false);
|
|
45623
46584
|
}
|
|
45624
46585
|
}
|
|
45625
|
-
}, [metricsLoading,
|
|
46586
|
+
}, [metricsLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
|
|
45626
46587
|
useEffect(() => {
|
|
45627
|
-
if (!metricsLoading && !
|
|
46588
|
+
if (!metricsLoading && !hasInitialDataLoaded) {
|
|
45628
46589
|
setHasInitialDataLoaded(true);
|
|
45629
46590
|
trackCoreEvent("Home View Loaded", {
|
|
45630
46591
|
default_line_id: defaultLineId,
|
|
@@ -45632,7 +46593,7 @@ function HomeView({
|
|
|
45632
46593
|
is_supervisor: isSupervisor
|
|
45633
46594
|
});
|
|
45634
46595
|
}
|
|
45635
|
-
}, [metricsLoading,
|
|
46596
|
+
}, [metricsLoading, hasInitialDataLoaded, defaultLineId, factoryViewId, isSupervisor]);
|
|
45636
46597
|
const lineTitle = useMemo(() => {
|
|
45637
46598
|
return factoryName;
|
|
45638
46599
|
}, [factoryName]);
|
|
@@ -45646,7 +46607,7 @@ function HomeView({
|
|
|
45646
46607
|
] });
|
|
45647
46608
|
}, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
|
|
45648
46609
|
const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
|
|
45649
|
-
const isDataLoading = metricsLoading
|
|
46610
|
+
const isDataLoading = metricsLoading;
|
|
45650
46611
|
if (isInitialLoading) {
|
|
45651
46612
|
return /* @__PURE__ */ jsx(LoadingPageCmp, { message: "Loading Dashboard..." });
|
|
45652
46613
|
}
|
|
@@ -45790,7 +46751,7 @@ function withWorkspaceDisplayNames(Component3, options = {}) {
|
|
|
45790
46751
|
const lineIdsKey = useMemo(() => {
|
|
45791
46752
|
if (!props.lineIds) return "";
|
|
45792
46753
|
if (Array.isArray(props.lineIds)) {
|
|
45793
|
-
return props.lineIds.sort().join(",");
|
|
46754
|
+
return props.lineIds.slice().sort().join(",");
|
|
45794
46755
|
}
|
|
45795
46756
|
const values = Object.values(props.lineIds).filter(Boolean);
|
|
45796
46757
|
return values.sort().join(",");
|
|
@@ -46913,9 +47874,15 @@ var KPIDetailView = ({
|
|
|
46913
47874
|
};
|
|
46914
47875
|
var KPIDetailViewWithDisplayNames = withSelectedLineDisplayNames(KPIDetailView);
|
|
46915
47876
|
var KPIDetailView_default = KPIDetailViewWithDisplayNames;
|
|
46916
|
-
var LineCard = ({
|
|
46917
|
-
|
|
46918
|
-
|
|
47877
|
+
var LineCard = ({
|
|
47878
|
+
line,
|
|
47879
|
+
kpis,
|
|
47880
|
+
isLoading,
|
|
47881
|
+
error,
|
|
47882
|
+
onClick,
|
|
47883
|
+
supervisorEnabled = false,
|
|
47884
|
+
supervisorName
|
|
47885
|
+
}) => {
|
|
46919
47886
|
const isOnTrack = React24__default.useMemo(() => {
|
|
46920
47887
|
if (!kpis) return null;
|
|
46921
47888
|
return kpis.efficiency.value > 90;
|
|
@@ -47024,6 +47991,7 @@ var KPIsOverviewView = ({
|
|
|
47024
47991
|
const [error, setError] = useState(null);
|
|
47025
47992
|
const supabase = useSupabase();
|
|
47026
47993
|
const dashboardConfig = useDashboardConfig();
|
|
47994
|
+
const entityConfig = useEntityConfig();
|
|
47027
47995
|
const navigation = useNavigation(navigate);
|
|
47028
47996
|
const dateTimeConfig = useDateTimeConfig();
|
|
47029
47997
|
const representativeLineId = lineIds?.[0] || lines[0]?.id;
|
|
@@ -47031,6 +47999,27 @@ var KPIsOverviewView = ({
|
|
|
47031
47999
|
const supervisorEnabled = dashboardConfig?.supervisorConfig?.enabled || false;
|
|
47032
48000
|
const dbTimezone = useAppTimezone();
|
|
47033
48001
|
const configuredTimezone = dbTimezone || dateTimeConfig.defaultTimezone || "UTC";
|
|
48002
|
+
const factoryViewId = entityConfig.factoryViewId || "factory";
|
|
48003
|
+
const {
|
|
48004
|
+
lineMetrics,
|
|
48005
|
+
isLoading: metricsLoading,
|
|
48006
|
+
error: metricsError
|
|
48007
|
+
} = useDashboardMetrics({
|
|
48008
|
+
lineId: factoryViewId,
|
|
48009
|
+
userAccessibleLineIds: lineIds
|
|
48010
|
+
});
|
|
48011
|
+
const defaultKPIs = React24__default.useMemo(() => createDefaultKPIs(), []);
|
|
48012
|
+
const kpisByLineId = React24__default.useMemo(() => {
|
|
48013
|
+
const map = /* @__PURE__ */ new Map();
|
|
48014
|
+
lineMetrics.forEach((row) => {
|
|
48015
|
+
if (row?.line_id) map.set(row.line_id, buildKPIsFromLineMetricsRow(row));
|
|
48016
|
+
});
|
|
48017
|
+
return map;
|
|
48018
|
+
}, [lineMetrics]);
|
|
48019
|
+
const visibleLineIds = React24__default.useMemo(() => lines.map((l) => l.id), [lines]);
|
|
48020
|
+
const { supervisorNamesByLineId } = useSupervisorsByLineIds(visibleLineIds, {
|
|
48021
|
+
enabled: supervisorEnabled && visibleLineIds.length > 0
|
|
48022
|
+
});
|
|
47034
48023
|
useEffect(() => {
|
|
47035
48024
|
trackCorePageView("KPIs Overview");
|
|
47036
48025
|
}, []);
|
|
@@ -47248,8 +48237,12 @@ var KPIsOverviewView = ({
|
|
|
47248
48237
|
LineCard,
|
|
47249
48238
|
{
|
|
47250
48239
|
line,
|
|
48240
|
+
kpis: metricsError ? null : kpisByLineId.get(line.id) ?? (metricsLoading ? null : defaultKPIs),
|
|
48241
|
+
isLoading: metricsLoading,
|
|
48242
|
+
error: metricsError,
|
|
47251
48243
|
onClick: (kpis) => handleLineClick(line, kpis),
|
|
47252
|
-
supervisorEnabled
|
|
48244
|
+
supervisorEnabled,
|
|
48245
|
+
supervisorName: supervisorNamesByLineId.get(line.id) || null
|
|
47253
48246
|
},
|
|
47254
48247
|
line.id
|
|
47255
48248
|
)) }) })
|
|
@@ -47414,6 +48407,7 @@ var LeaderboardDetailView = memo(({
|
|
|
47414
48407
|
const entityConfig = useEntityConfig();
|
|
47415
48408
|
const [sortAscending, setSortAscending] = useState(false);
|
|
47416
48409
|
const timezone = useAppTimezone();
|
|
48410
|
+
const staticShiftConfig = useShiftConfig();
|
|
47417
48411
|
const [isMobile, setIsMobile] = useState(false);
|
|
47418
48412
|
React24__default.useEffect(() => {
|
|
47419
48413
|
const checkMobile = () => setIsMobile(window.innerWidth < 640);
|
|
@@ -47442,9 +48436,27 @@ var LeaderboardDetailView = memo(({
|
|
|
47442
48436
|
() => typeof shift === "number" ? shift : typeof shift === "string" ? parseInt(shift) : void 0,
|
|
47443
48437
|
[shift]
|
|
47444
48438
|
);
|
|
47445
|
-
const
|
|
47446
|
-
|
|
47447
|
-
|
|
48439
|
+
const configuredLineIds = useMemo(() => {
|
|
48440
|
+
const allLineIds = getConfiguredLineIds(entityConfig);
|
|
48441
|
+
if (userAccessibleLineIds) {
|
|
48442
|
+
return allLineIds.filter((id3) => userAccessibleLineIds.includes(id3));
|
|
48443
|
+
}
|
|
48444
|
+
return allLineIds;
|
|
48445
|
+
}, [entityConfig, userAccessibleLineIds]);
|
|
48446
|
+
const shouldFetchShiftConfigs = !date && shiftId === void 0;
|
|
48447
|
+
const {
|
|
48448
|
+
shiftConfigMap: multiLineShiftConfigMap,
|
|
48449
|
+
isLoading: isShiftConfigLoading
|
|
48450
|
+
} = useMultiLineShiftConfigs(
|
|
48451
|
+
shouldFetchShiftConfigs ? configuredLineIds : [],
|
|
48452
|
+
staticShiftConfig
|
|
48453
|
+
);
|
|
48454
|
+
const shiftGroups = useMemo(() => {
|
|
48455
|
+
if (!shouldFetchShiftConfigs || isShiftConfigLoading || multiLineShiftConfigMap.size === 0) {
|
|
48456
|
+
return [];
|
|
48457
|
+
}
|
|
48458
|
+
return groupLinesByShift(multiLineShiftConfigMap, timezone || "Asia/Kolkata");
|
|
48459
|
+
}, [shouldFetchShiftConfigs, isShiftConfigLoading, multiLineShiftConfigMap, timezone]);
|
|
47448
48460
|
const {
|
|
47449
48461
|
workspaces,
|
|
47450
48462
|
loading: workspacesLoading,
|
|
@@ -47452,21 +48464,28 @@ var LeaderboardDetailView = memo(({
|
|
|
47452
48464
|
refreshWorkspaces
|
|
47453
48465
|
} = useAllWorkspaceMetrics({
|
|
47454
48466
|
initialDate: date,
|
|
47455
|
-
initialShiftId:
|
|
48467
|
+
initialShiftId: shiftId,
|
|
47456
48468
|
allowedLineIds: userAccessibleLineIds,
|
|
47457
|
-
// Filter to user's accessible lines only
|
|
47458
48469
|
enabled: !isShiftConfigLoading
|
|
47459
|
-
// Pass enabled flag
|
|
47460
48470
|
});
|
|
47461
48471
|
const getShiftName = useCallback((shiftId2) => {
|
|
47462
48472
|
if (shiftId2 !== void 0) {
|
|
47463
|
-
return getShiftNameById(shiftId2, timezone || "Asia/Kolkata",
|
|
48473
|
+
return getShiftNameById(shiftId2, timezone || "Asia/Kolkata", staticShiftConfig);
|
|
47464
48474
|
}
|
|
47465
|
-
|
|
47466
|
-
|
|
47467
|
-
|
|
48475
|
+
if (shiftGroups.length > 1) {
|
|
48476
|
+
const shiftNames = shiftGroups.map((g) => g.shiftName).join(" & ");
|
|
48477
|
+
return shiftNames;
|
|
48478
|
+
} else if (shiftGroups.length === 1) {
|
|
48479
|
+
return shiftGroups[0].shiftName;
|
|
48480
|
+
}
|
|
48481
|
+
const currentShift = getCurrentShift(timezone || "Asia/Kolkata", staticShiftConfig);
|
|
48482
|
+
return currentShift.shiftName || getShiftNameById(currentShift.shiftId, timezone || "Asia/Kolkata", staticShiftConfig);
|
|
48483
|
+
}, [timezone, staticShiftConfig, shiftGroups]);
|
|
47468
48484
|
const getShiftIcon = useCallback((shiftId2) => {
|
|
47469
|
-
|
|
48485
|
+
if (shiftId2 === void 0 && shiftGroups.length > 1) {
|
|
48486
|
+
return /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) });
|
|
48487
|
+
}
|
|
48488
|
+
const effectiveShiftId = shiftId2 !== void 0 ? shiftId2 : shiftGroups.length === 1 ? shiftGroups[0].shiftId : getCurrentShift(timezone || "Asia/Kolkata", staticShiftConfig).shiftId;
|
|
47470
48489
|
const shiftNameLower = getShiftName(effectiveShiftId).toLowerCase();
|
|
47471
48490
|
if (shiftNameLower.includes("day") || shiftNameLower.includes("morning")) {
|
|
47472
48491
|
return /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ 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" }) });
|
|
@@ -47478,7 +48497,7 @@ var LeaderboardDetailView = memo(({
|
|
|
47478
48497
|
return /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ 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" }) });
|
|
47479
48498
|
}
|
|
47480
48499
|
return /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) });
|
|
47481
|
-
}, [getShiftName]);
|
|
48500
|
+
}, [getShiftName, shiftGroups, timezone, staticShiftConfig]);
|
|
47482
48501
|
const formatDate2 = useCallback((date2) => {
|
|
47483
48502
|
return new Intl.DateTimeFormat("en-US", {
|
|
47484
48503
|
weekday: "long",
|
|
@@ -48047,7 +49066,7 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
|
|
|
48047
49066
|
const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
|
|
48048
49067
|
return Number(hoursDiff.toFixed(1));
|
|
48049
49068
|
};
|
|
48050
|
-
var
|
|
49069
|
+
var calculateBreakDuration2 = (startTime, endTime) => {
|
|
48051
49070
|
const [startHour, startMinute] = startTime.split(":").map(Number);
|
|
48052
49071
|
const [endHour, endMinute] = endTime.split(":").map(Number);
|
|
48053
49072
|
let startMinutes = startHour * 60 + startMinute;
|
|
@@ -48063,7 +49082,7 @@ var parseBreaksFromDB = (dbBreaks) => {
|
|
|
48063
49082
|
return dbBreaks.map((breakItem) => ({
|
|
48064
49083
|
startTime: breakItem.start || breakItem.startTime || "00:00",
|
|
48065
49084
|
endTime: breakItem.end || breakItem.endTime || "00:00",
|
|
48066
|
-
duration:
|
|
49085
|
+
duration: calculateBreakDuration2(
|
|
48067
49086
|
breakItem.start || breakItem.startTime || "00:00",
|
|
48068
49087
|
breakItem.end || breakItem.endTime || "00:00"
|
|
48069
49088
|
),
|
|
@@ -48073,7 +49092,7 @@ var parseBreaksFromDB = (dbBreaks) => {
|
|
|
48073
49092
|
return dbBreaks.breaks.map((breakItem) => ({
|
|
48074
49093
|
startTime: breakItem.start || breakItem.startTime || "00:00",
|
|
48075
49094
|
endTime: breakItem.end || breakItem.endTime || "00:00",
|
|
48076
|
-
duration:
|
|
49095
|
+
duration: calculateBreakDuration2(
|
|
48077
49096
|
breakItem.start || breakItem.startTime || "00:00",
|
|
48078
49097
|
breakItem.end || breakItem.endTime || "00:00"
|
|
48079
49098
|
),
|
|
@@ -48710,6 +49729,13 @@ var ShiftsView = ({
|
|
|
48710
49729
|
if (nightResult.error) {
|
|
48711
49730
|
throw new Error(`Failed to save night shift: ${nightResult.error.message}`);
|
|
48712
49731
|
}
|
|
49732
|
+
const updatedRows = [
|
|
49733
|
+
...dayResult.data || [],
|
|
49734
|
+
...nightResult.data || []
|
|
49735
|
+
];
|
|
49736
|
+
if (updatedRows.length > 0) {
|
|
49737
|
+
shiftConfigStore.setFromOperatingHoursRows(lineId, updatedRows, shiftConfigStore.get(lineId));
|
|
49738
|
+
}
|
|
48713
49739
|
setLineConfigs((prev) => prev.map(
|
|
48714
49740
|
(config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
|
|
48715
49741
|
));
|
|
@@ -49679,6 +50705,7 @@ var TargetsViewUI = ({
|
|
|
49679
50705
|
onUpdateSelectedSKU,
|
|
49680
50706
|
skuRequired = false
|
|
49681
50707
|
}) => {
|
|
50708
|
+
const { displayNames: workspaceDisplayNames } = useWorkspaceDisplayNames();
|
|
49682
50709
|
if (isLoading) {
|
|
49683
50710
|
return /* @__PURE__ */ jsx("div", { className: "flex h-screen bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading targets..." }) }) });
|
|
49684
50711
|
}
|
|
@@ -49826,7 +50853,7 @@ var TargetsViewUI = ({
|
|
|
49826
50853
|
] })
|
|
49827
50854
|
] }) }),
|
|
49828
50855
|
/* @__PURE__ */ jsx("div", { className: "divide-y divide-gray-100", children: line.workspaces.map((workspace) => {
|
|
49829
|
-
const formattedName = formatWorkspaceName(workspace.name, lineId);
|
|
50856
|
+
const formattedName = workspaceDisplayNames[`${lineId}_${workspace.name}`] || formatWorkspaceName(workspace.name, lineId);
|
|
49830
50857
|
return /* @__PURE__ */ jsxs(
|
|
49831
50858
|
"div",
|
|
49832
50859
|
{
|
|
@@ -50550,8 +51577,17 @@ var TargetsView = ({
|
|
|
50550
51577
|
};
|
|
50551
51578
|
const handleUpdateWorkspaceDisplayName = useCallback(async (workspaceId, displayName) => {
|
|
50552
51579
|
try {
|
|
50553
|
-
await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
50554
|
-
|
|
51580
|
+
const updated = await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
51581
|
+
if (updated?.line_id && updated?.workspace_id) {
|
|
51582
|
+
upsertWorkspaceDisplayNameInCache({
|
|
51583
|
+
lineId: updated.line_id,
|
|
51584
|
+
workspaceId: updated.workspace_id,
|
|
51585
|
+
displayName: updated?.display_name || displayName,
|
|
51586
|
+
enabled: updated?.enable
|
|
51587
|
+
});
|
|
51588
|
+
} else {
|
|
51589
|
+
await forceRefreshWorkspaceDisplayNames();
|
|
51590
|
+
}
|
|
50555
51591
|
toast.success("Workspace name updated successfully");
|
|
50556
51592
|
} catch (error) {
|
|
50557
51593
|
console.error("Error updating workspace display name:", error);
|
|
@@ -51230,7 +52266,13 @@ var WorkspaceDetailView = ({
|
|
|
51230
52266
|
}
|
|
51231
52267
|
)
|
|
51232
52268
|
] }),
|
|
51233
|
-
activeTab === "overview" && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsx(
|
|
52269
|
+
activeTab === "overview" && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsx(
|
|
52270
|
+
WorkspacePdfGenerator,
|
|
52271
|
+
{
|
|
52272
|
+
workspace,
|
|
52273
|
+
idleTimeReasons: idleTimeChartData
|
|
52274
|
+
}
|
|
52275
|
+
) }),
|
|
51234
52276
|
activeTab === "monthly_history" && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: /* @__PURE__ */ jsx(
|
|
51235
52277
|
WorkspaceMonthlyPdfGenerator,
|
|
51236
52278
|
{
|
|
@@ -52285,6 +53327,7 @@ var WorkspaceHealthView = ({
|
|
|
52285
53327
|
const [selectedWorkspace, setSelectedWorkspace] = useState(null);
|
|
52286
53328
|
const [selectedDate, setSelectedDate] = useState(void 0);
|
|
52287
53329
|
const [selectedShiftId, setSelectedShiftId] = useState(void 0);
|
|
53330
|
+
const [showDatePicker, setShowDatePicker] = useState(false);
|
|
52288
53331
|
const effectiveTimezone = timezone || "UTC";
|
|
52289
53332
|
const currentShiftDetails = getCurrentShift(effectiveTimezone, shiftConfig);
|
|
52290
53333
|
const operationalDate = currentShiftDetails.date;
|
|
@@ -52333,28 +53376,6 @@ var WorkspaceHealthView = ({
|
|
|
52333
53376
|
const handleCloseDetails = useCallback(() => {
|
|
52334
53377
|
setSelectedWorkspace(null);
|
|
52335
53378
|
}, []);
|
|
52336
|
-
const handleExport = useCallback(() => {
|
|
52337
|
-
const csv = [
|
|
52338
|
-
["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
|
|
52339
|
-
...workspaces.map((w) => [
|
|
52340
|
-
w.workspace_display_name || "",
|
|
52341
|
-
w.line_name || "",
|
|
52342
|
-
w.company_name || "",
|
|
52343
|
-
w.status,
|
|
52344
|
-
w.last_heartbeat,
|
|
52345
|
-
w.consecutive_misses?.toString() || "0"
|
|
52346
|
-
])
|
|
52347
|
-
].map((row) => row.join(",")).join("\n");
|
|
52348
|
-
const blob = new Blob([csv], { type: "text/csv" });
|
|
52349
|
-
const url = window.URL.createObjectURL(blob);
|
|
52350
|
-
const a = document.createElement("a");
|
|
52351
|
-
a.href = url;
|
|
52352
|
-
a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
|
|
52353
|
-
document.body.appendChild(a);
|
|
52354
|
-
a.click();
|
|
52355
|
-
document.body.removeChild(a);
|
|
52356
|
-
window.URL.revokeObjectURL(url);
|
|
52357
|
-
}, [workspaces]);
|
|
52358
53379
|
const getStatusIcon = (status) => {
|
|
52359
53380
|
switch (status) {
|
|
52360
53381
|
case "healthy":
|
|
@@ -52392,104 +53413,42 @@ var WorkspaceHealthView = ({
|
|
|
52392
53413
|
}
|
|
52393
53414
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
52394
53415
|
/* @__PURE__ */ jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
|
|
52395
|
-
/* @__PURE__ */
|
|
52396
|
-
/* @__PURE__ */
|
|
52397
|
-
|
|
52398
|
-
|
|
52399
|
-
|
|
52400
|
-
|
|
52401
|
-
|
|
52402
|
-
|
|
52403
|
-
|
|
52404
|
-
|
|
52405
|
-
|
|
52406
|
-
|
|
52407
|
-
|
|
52408
|
-
/* @__PURE__ */ jsx(
|
|
52409
|
-
"button",
|
|
52410
|
-
{
|
|
52411
|
-
onClick: () => {
|
|
52412
|
-
refetch();
|
|
52413
|
-
},
|
|
52414
|
-
className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52415
|
-
"aria-label": "Refresh",
|
|
52416
|
-
children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" })
|
|
52417
|
-
}
|
|
52418
|
-
),
|
|
52419
|
-
/* @__PURE__ */ jsx(
|
|
52420
|
-
"button",
|
|
52421
|
-
{
|
|
52422
|
-
onClick: handleExport,
|
|
52423
|
-
className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52424
|
-
"aria-label": "Export CSV",
|
|
52425
|
-
children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" })
|
|
52426
|
-
}
|
|
52427
|
-
)
|
|
52428
|
-
] })
|
|
52429
|
-
] }),
|
|
52430
|
-
/* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
|
|
52431
|
-
/* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900", children: "System Health" }),
|
|
52432
|
-
/* @__PURE__ */ jsxs("div", { className: "relative flex h-2 w-2", children: [
|
|
52433
|
-
/* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
|
|
52434
|
-
/* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-emerald-500" })
|
|
52435
|
-
] })
|
|
52436
|
-
] }) })
|
|
53416
|
+
/* @__PURE__ */ jsx("header", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsx("div", { className: "px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
53417
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx(
|
|
53418
|
+
BackButtonMinimal,
|
|
53419
|
+
{
|
|
53420
|
+
onClick: () => router.push("/"),
|
|
53421
|
+
text: "Back",
|
|
53422
|
+
size: "default",
|
|
53423
|
+
"aria-label": "Navigate back to dashboard"
|
|
53424
|
+
}
|
|
53425
|
+
) }),
|
|
53426
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
|
|
53427
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "System Health" }),
|
|
53428
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-1 text-center", children: "Monitor workspace connectivity and operational status" })
|
|
52437
53429
|
] }),
|
|
52438
|
-
/* @__PURE__ */
|
|
52439
|
-
/* @__PURE__ */ jsx(
|
|
52440
|
-
|
|
53430
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
53431
|
+
/* @__PURE__ */ jsx(
|
|
53432
|
+
"button",
|
|
52441
53433
|
{
|
|
52442
|
-
onClick: () =>
|
|
52443
|
-
text: "
|
|
52444
|
-
|
|
52445
|
-
|
|
53434
|
+
onClick: () => setShowDatePicker(!showDatePicker),
|
|
53435
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
53436
|
+
"aria-label": "Select date",
|
|
53437
|
+
children: /* @__PURE__ */ jsx(Calendar, { className: "h-5 w-5" })
|
|
52446
53438
|
}
|
|
52447
|
-
)
|
|
52448
|
-
/* @__PURE__ */ jsx(
|
|
52449
|
-
|
|
52450
|
-
|
|
52451
|
-
|
|
52452
|
-
|
|
52453
|
-
|
|
52454
|
-
|
|
52455
|
-
|
|
52456
|
-
|
|
52457
|
-
|
|
52458
|
-
|
|
52459
|
-
|
|
52460
|
-
refetch();
|
|
52461
|
-
},
|
|
52462
|
-
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52463
|
-
"aria-label": "Refresh",
|
|
52464
|
-
children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-5 w-5" })
|
|
52465
|
-
}
|
|
52466
|
-
),
|
|
52467
|
-
/* @__PURE__ */ jsx(
|
|
52468
|
-
"button",
|
|
52469
|
-
{
|
|
52470
|
-
onClick: handleExport,
|
|
52471
|
-
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
52472
|
-
"aria-label": "Export CSV",
|
|
52473
|
-
children: /* @__PURE__ */ jsx(Download, { className: "h-5 w-5" })
|
|
52474
|
-
}
|
|
52475
|
-
)
|
|
52476
|
-
] }),
|
|
52477
|
-
/* @__PURE__ */ jsx("div", { className: "w-full h-8" })
|
|
52478
|
-
] }) }),
|
|
52479
|
-
/* @__PURE__ */ jsx("div", { className: "mt-1 sm:mt-2", children: /* @__PURE__ */ jsx(
|
|
52480
|
-
HealthDateShiftSelector,
|
|
52481
|
-
{
|
|
52482
|
-
selectedDate: selectedDate || operationalDate,
|
|
52483
|
-
selectedShiftId: selectedShiftId ?? currentShiftDetails.shiftId,
|
|
52484
|
-
shiftConfig,
|
|
52485
|
-
timezone: effectiveTimezone,
|
|
52486
|
-
onDateChange: setSelectedDate,
|
|
52487
|
-
onShiftChange: setSelectedShiftId,
|
|
52488
|
-
isCurrentShift: isViewingCurrentShift,
|
|
52489
|
-
onReturnToLive: handleReturnToLive
|
|
52490
|
-
}
|
|
52491
|
-
) })
|
|
52492
|
-
] }),
|
|
53439
|
+
),
|
|
53440
|
+
/* @__PURE__ */ jsx(
|
|
53441
|
+
"button",
|
|
53442
|
+
{
|
|
53443
|
+
onClick: () => refetch(),
|
|
53444
|
+
disabled: loading,
|
|
53445
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
53446
|
+
"aria-label": "Refresh",
|
|
53447
|
+
children: /* @__PURE__ */ jsx(RefreshCw, { className: `h-5 w-5 ${loading ? "animate-spin" : ""}` })
|
|
53448
|
+
}
|
|
53449
|
+
)
|
|
53450
|
+
] })
|
|
53451
|
+
] }) }) }),
|
|
52493
53452
|
/* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
52494
53453
|
summary && /* @__PURE__ */ jsxs(
|
|
52495
53454
|
motion.div,
|
|
@@ -52565,6 +53524,72 @@ var WorkspaceHealthView = ({
|
|
|
52565
53524
|
)
|
|
52566
53525
|
] })
|
|
52567
53526
|
] }),
|
|
53527
|
+
showDatePicker && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
53528
|
+
/* @__PURE__ */ jsx(
|
|
53529
|
+
"div",
|
|
53530
|
+
{
|
|
53531
|
+
className: "fixed inset-0 bg-black/20 z-40",
|
|
53532
|
+
onClick: () => setShowDatePicker(false)
|
|
53533
|
+
}
|
|
53534
|
+
),
|
|
53535
|
+
/* @__PURE__ */ 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__ */ jsxs("div", { className: "space-y-4", children: [
|
|
53536
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
53537
|
+
/* @__PURE__ */ jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Date" }),
|
|
53538
|
+
/* @__PURE__ */ jsx(
|
|
53539
|
+
"input",
|
|
53540
|
+
{
|
|
53541
|
+
type: "date",
|
|
53542
|
+
value: selectedDate || operationalDate,
|
|
53543
|
+
max: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
53544
|
+
onChange: (e) => {
|
|
53545
|
+
setSelectedDate(e.target.value);
|
|
53546
|
+
},
|
|
53547
|
+
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"
|
|
53548
|
+
}
|
|
53549
|
+
)
|
|
53550
|
+
] }),
|
|
53551
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
53552
|
+
/* @__PURE__ */ jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Shift" }),
|
|
53553
|
+
/* @__PURE__ */ jsx(
|
|
53554
|
+
"select",
|
|
53555
|
+
{
|
|
53556
|
+
value: selectedShiftId ?? currentShiftDetails.shiftId,
|
|
53557
|
+
onChange: (e) => setSelectedShiftId(Number(e.target.value)),
|
|
53558
|
+
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",
|
|
53559
|
+
children: (shiftConfig?.shifts || []).map((shift) => /* @__PURE__ */ jsxs("option", { value: shift.shiftId, children: [
|
|
53560
|
+
shift.shiftName,
|
|
53561
|
+
" (",
|
|
53562
|
+
shift.startTime,
|
|
53563
|
+
" - ",
|
|
53564
|
+
shift.endTime,
|
|
53565
|
+
")"
|
|
53566
|
+
] }, shift.shiftId))
|
|
53567
|
+
}
|
|
53568
|
+
)
|
|
53569
|
+
] }),
|
|
53570
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-2", children: [
|
|
53571
|
+
!isViewingCurrentShift && /* @__PURE__ */ jsx(
|
|
53572
|
+
"button",
|
|
53573
|
+
{
|
|
53574
|
+
onClick: () => {
|
|
53575
|
+
handleReturnToLive();
|
|
53576
|
+
setShowDatePicker(false);
|
|
53577
|
+
},
|
|
53578
|
+
className: "flex-1 px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors",
|
|
53579
|
+
children: "Return to Live"
|
|
53580
|
+
}
|
|
53581
|
+
),
|
|
53582
|
+
/* @__PURE__ */ jsx(
|
|
53583
|
+
"button",
|
|
53584
|
+
{
|
|
53585
|
+
onClick: () => setShowDatePicker(false),
|
|
53586
|
+
className: "flex-1 px-3 py-2 text-sm text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors",
|
|
53587
|
+
children: "Done"
|
|
53588
|
+
}
|
|
53589
|
+
)
|
|
53590
|
+
] })
|
|
53591
|
+
] }) })
|
|
53592
|
+
] }),
|
|
52568
53593
|
/* @__PURE__ */ jsx(
|
|
52569
53594
|
WorkspaceUptimeDetailModal_default,
|
|
52570
53595
|
{
|
|
@@ -53714,7 +54739,7 @@ function DailyBarChart({
|
|
|
53714
54739
|
axisLine: false,
|
|
53715
54740
|
tick: (props) => {
|
|
53716
54741
|
const { x, y, payload } = props;
|
|
53717
|
-
if (payload.value === 0) return
|
|
54742
|
+
if (payload.value === 0) return /* @__PURE__ */ jsx("g", {});
|
|
53718
54743
|
const hours = Math.round(payload.value / (1e3 * 60 * 60) * 10) / 10;
|
|
53719
54744
|
return /* @__PURE__ */ jsxs("text", { x, y, dy: 4, textAnchor: "end", className: "text-xs fill-gray-400", children: [
|
|
53720
54745
|
hours,
|
|
@@ -54069,7 +55094,7 @@ var UserManagementTable = ({
|
|
|
54069
55094
|
}
|
|
54070
55095
|
),
|
|
54071
55096
|
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Assignments" }),
|
|
54072
|
-
showUsageStats && /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "
|
|
55097
|
+
showUsageStats && /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Average Daily usage" }),
|
|
54073
55098
|
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Actions" })
|
|
54074
55099
|
] }) }),
|
|
54075
55100
|
/* @__PURE__ */ jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: filteredAndSortedUsers.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: showUsageStats ? 5 : 4, className: "px-6 py-12", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center text-gray-400 gap-2", children: [
|
|
@@ -54081,12 +55106,25 @@ var UserManagementTable = ({
|
|
|
54081
55106
|
const canChangeRole = permissions.canChangeRole(user);
|
|
54082
55107
|
const canRemove = permissions.canRemoveUser(user);
|
|
54083
55108
|
const hasActions = canChangeRole || canRemove;
|
|
55109
|
+
const canShowUsageModal = showUsageStats && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55110
|
+
const handleRowClick = (e) => {
|
|
55111
|
+
const target = e.target;
|
|
55112
|
+
if (target.closest("button") || target.closest("select") || target.closest("input") || target.closest('[role="button"]') || target.closest("[data-interactive]")) {
|
|
55113
|
+
return;
|
|
55114
|
+
}
|
|
55115
|
+
if (canShowUsageModal) {
|
|
55116
|
+
setUsageDetailUserId(user.user_id);
|
|
55117
|
+
setShowUsageDetailModal(true);
|
|
55118
|
+
}
|
|
55119
|
+
};
|
|
54084
55120
|
return /* @__PURE__ */ jsxs(
|
|
54085
55121
|
"tr",
|
|
54086
55122
|
{
|
|
55123
|
+
onClick: handleRowClick,
|
|
54087
55124
|
className: cn(
|
|
54088
|
-
"
|
|
54089
|
-
isDeactivated && "opacity-60"
|
|
55125
|
+
"transition-all duration-150",
|
|
55126
|
+
isDeactivated && "opacity-60",
|
|
55127
|
+
canShowUsageModal ? "cursor-pointer hover:bg-blue-50/50 hover:shadow-sm" : "hover:bg-gray-50"
|
|
54090
55128
|
),
|
|
54091
55129
|
children: [
|
|
54092
55130
|
/* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
@@ -54152,10 +55190,7 @@ var UserManagementTable = ({
|
|
|
54152
55190
|
setShowUsageDetailModal(true);
|
|
54153
55191
|
},
|
|
54154
55192
|
className: "group flex items-center gap-3 hover:bg-gray-50 -mx-2 px-2 py-1.5 rounded-lg transition-all duration-200",
|
|
54155
|
-
children: isUsageLoading ? /* @__PURE__ */ jsx("div", { className: "animate-pulse bg-gray-200 h-5 w-20 rounded" }) : /* @__PURE__ */
|
|
54156
|
-
/* @__PURE__ */ 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) }),
|
|
54157
|
-
/* @__PURE__ */ jsx(ArrowRight, { className: "w-4 h-4 text-gray-400 group-hover:text-blue-600 group-hover:translate-x-0.5 transition-all" })
|
|
54158
|
-
] })
|
|
55193
|
+
children: isUsageLoading ? /* @__PURE__ */ jsx("div", { className: "animate-pulse bg-gray-200 h-5 w-20 rounded" }) : /* @__PURE__ */ 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) })
|
|
54159
55194
|
}
|
|
54160
55195
|
) : /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-400", children: "-" }) }),
|
|
54161
55196
|
/* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right", children: hasActions && /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsx(
|
|
@@ -54203,25 +55238,50 @@ var UserManagementTable = ({
|
|
|
54203
55238
|
children: /* @__PURE__ */ jsxs("div", { className: "py-1", children: [
|
|
54204
55239
|
(() => {
|
|
54205
55240
|
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
54206
|
-
const
|
|
54207
|
-
|
|
54208
|
-
return canChangeRole && /* @__PURE__ */ jsxs(
|
|
55241
|
+
const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55242
|
+
return canShowUsage && /* @__PURE__ */ jsxs(
|
|
54209
55243
|
"button",
|
|
54210
55244
|
{
|
|
54211
55245
|
onClick: () => {
|
|
54212
55246
|
if (user) {
|
|
54213
|
-
|
|
55247
|
+
setUsageDetailUserId(user.user_id);
|
|
55248
|
+
setShowUsageDetailModal(true);
|
|
55249
|
+
handleCloseActionMenu();
|
|
54214
55250
|
}
|
|
54215
55251
|
},
|
|
54216
|
-
className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2
|
|
54217
|
-
disabled: isCurrentUser,
|
|
55252
|
+
className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
|
|
54218
55253
|
children: [
|
|
54219
|
-
/* @__PURE__ */ jsx(
|
|
54220
|
-
"
|
|
55254
|
+
/* @__PURE__ */ jsx(BarChart3, { className: "w-4 h-4" }),
|
|
55255
|
+
"View Detailed Usage"
|
|
54221
55256
|
]
|
|
54222
55257
|
}
|
|
54223
55258
|
);
|
|
54224
55259
|
})(),
|
|
55260
|
+
(() => {
|
|
55261
|
+
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
55262
|
+
const isCurrentUser = user?.user_id === currentUserId;
|
|
55263
|
+
const canChangeRole = user && permissions.canChangeRole(user);
|
|
55264
|
+
const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
|
|
55265
|
+
return canChangeRole && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
55266
|
+
canShowUsage && /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200 my-1" }),
|
|
55267
|
+
/* @__PURE__ */ jsxs(
|
|
55268
|
+
"button",
|
|
55269
|
+
{
|
|
55270
|
+
onClick: () => {
|
|
55271
|
+
if (user) {
|
|
55272
|
+
handleChangeRole(user);
|
|
55273
|
+
}
|
|
55274
|
+
},
|
|
55275
|
+
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",
|
|
55276
|
+
disabled: isCurrentUser,
|
|
55277
|
+
children: [
|
|
55278
|
+
/* @__PURE__ */ jsx(UserCog, { className: "w-4 h-4" }),
|
|
55279
|
+
"Change Role"
|
|
55280
|
+
]
|
|
55281
|
+
}
|
|
55282
|
+
)
|
|
55283
|
+
] });
|
|
55284
|
+
})(),
|
|
54225
55285
|
(() => {
|
|
54226
55286
|
const user = users.find((u) => u.user_id === openActionMenuId);
|
|
54227
55287
|
const isCurrentUser = user?.user_id === currentUserId;
|
|
@@ -54763,7 +55823,7 @@ var TeamManagementView = ({
|
|
|
54763
55823
|
}, {});
|
|
54764
55824
|
}, [usageData, usageDateRange.daysElapsed]);
|
|
54765
55825
|
const pageTitle = user?.role_level === "optifye" ? "Platform Users" : "Team Management";
|
|
54766
|
-
const pageDescription = user?.role_level === "optifye" ? "Manage users across all companies" : user?.role_level === "owner" ? "Manage
|
|
55826
|
+
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";
|
|
54767
55827
|
const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "plant_head"].includes(user.role_level) : false;
|
|
54768
55828
|
const loadData = useCallback(async () => {
|
|
54769
55829
|
if (!supabase) {
|
|
@@ -56608,4 +57668,4 @@ function shuffleArray(array) {
|
|
|
56608
57668
|
return shuffled;
|
|
56609
57669
|
}
|
|
56610
57670
|
|
|
56611
|
-
export { ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ClipFilterProvider, CompactWorkspaceHealthCard, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, InlineEditableText, InteractiveOnboardingTour, InvitationService, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3ClipsSupabaseService as S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UserManagementService, UserService, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createInvitationService, createLinesService, createSessionTracker, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, fetchIdleTimeReasons, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getAvailableShiftIds, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getNextUpdateInterval, getOperationalDate, getReasonColor, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isSafari, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useCompanyUsersUsage, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeReasons, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useMessages, useMetrics, useMultiLineShiftConfigs, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, userService, videoPrefetchManager, videoPreloader, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
|
|
57671
|
+
export { ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ClipFilterProvider, CompactWorkspaceHealthCard, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, InlineEditableText, InteractiveOnboardingTour, InvitationService, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3ClipsSupabaseService as S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UserManagementService, UserService, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, aggregateKPIsFromLineMetricsRows, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, buildKPIsFromLineMetricsRow, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, fetchIdleTimeReasons, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAvailableShiftIds, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getNextUpdateInterval, getOperationalDate, getReasonColor, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isSafari, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useCompanyUsersUsage, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeReasons, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useMessages, useMetrics, useMultiLineShiftConfigs, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, userService, videoPrefetchManager, videoPreloader, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
|