@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.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/workspaceService.ts
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 supabase = _getSupabaseInstance();
1831
- if (!supabase) throw new Error("Supabase client not initialized");
1832
- const { data: { session } } = await supabase.auth.getSession();
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
- return session.access_token;
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
- async getWorkspaces(lineId) {
1845
- try {
1846
- const token = await getAuthToken();
1847
- const apiUrl = getBackendUrl();
1848
- const response = await fetch(`${apiUrl}/api/workspaces?line_id=${lineId}`, {
1849
- headers: {
1850
- "Authorization": `Bearer ${token}`,
1851
- "Content-Type": "application/json"
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
- if (!response.ok) {
1855
- const errorText = await response.text();
1856
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
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
- const data = await response.json();
1859
- return data.workspaces || [];
1860
- } catch (error) {
1861
- console.error("Error fetching workspaces:", error);
1862
- throw error;
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 getAuthToken();
1872
- const apiUrl = getBackendUrl();
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 Promise<void>
2078
+ * @returns Updated workspace record from backend
1936
2079
  */
1937
2080
  async updateWorkspaceDisplayName(workspaceId, displayName) {
1938
2081
  try {
1939
- const token = await getAuthToken();
1940
- const apiUrl = getBackendUrl();
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 getAuthToken();
1962
- const apiUrl = getBackendUrl();
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 getAuthToken();
1983
- const apiUrl = getBackendUrl();
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 getAuthToken();
2004
- const apiUrl = getBackendUrl();
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 getAuthToken();
2060
- const apiUrl = getBackendUrl();
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 getAuthToken();
2092
- const apiUrl = getBackendUrl();
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 getAuthToken();
2123
- const apiUrl = getBackendUrl();
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 getAuthToken();
2161
- const apiUrl = getBackendUrl();
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 uptimeDetails = uptimeMap.get(workspace.workspace_id);
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
- uptimeMap.set(record.workspace_id, {
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
- return record.line_id && lineShiftConfigs.has(record.line_id);
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
- uptimeMap.set(record.workspace_id, {
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 getAuthToken2 = async () => {
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 getAuthToken2();
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 Maintenance": {
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 { data: { session } } = await supabase.auth.getSession();
7633
- if (!session?.access_token) {
7634
- throw new Error("No authentication token available");
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 fallbackResponse = await fetch(
7673
- `${apiUrl}/api/dashboard/workspace/${workspaceId}/metrics?company_id=${companyId}&latest=true`,
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(fallbackConfig || null);
8249
- const [isLoading, setIsLoading] = useState(true);
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 calculateBreakDuration2 = (startTime, endTime) => {
8267
- const [sh, sm] = startTime.split(":").map(Number);
8268
- const [eh, em] = endTime.split(":").map(Number);
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
- const fetchShiftConfig = async () => {
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
- setIsLoading(true);
8279
- setError(null);
8280
- console.log(`[useLineShiftConfig] \u{1F50D} Fetching shift config for line: ${lineId}`);
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
- setShiftConfig(config);
8326
- setIsLoading(false);
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
- fetchShiftConfig();
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
- subscription.unsubscribe();
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 workspaces2 = await workspaceService.getWorkspaces(lineId);
8446
- const enabledWorkspaceIds = workspaces2.filter((ws) => ws.enable === true).map((ws) => ws.id);
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 response = await fetch(
8560
- `${apiUrl}/api/dashboard/workspace/${workspaceId}/metrics?${params.toString()}`,
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 response = await fetch(
8808
- `${apiUrl}/api/dashboard/leaderboard?${params.toString()}`,
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 firstLineId = useMemo(() => {
8848
- const configuredLineIds = getConfiguredLineIds(entityConfig);
8849
- return configuredLineIds.length > 0 ? configuredLineIds[0] : void 0;
9240
+ const appTimezone = useAppTimezone();
9241
+ const defaultTimezone = appTimezone || dateTimeConfig?.defaultTimezone || "UTC";
9242
+ const configuredLineIds = useMemo(() => {
9243
+ return getConfiguredLineIds(entityConfig);
8850
9244
  }, [entityConfig]);
8851
- const lineIdForShiftConfig = isFactoryView ? firstLineId : lineId;
8852
- const { shiftConfig, isLoading: shiftLoading, isFromDatabase } = useDynamicShiftConfig(lineIdForShiftConfig);
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 lineIdsParam = isFactoryView2 ? `line_ids=${targetLineIds.join(",")}` : `line_id=${targetLineIds[0]}`;
8922
- const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${operationalDate}&shift_id=${currentShiftDetails.shiftId}&company_id=${entityConfig.companyId}`;
8923
- console.log("[useDashboardMetrics] Calling backend API:", {
8924
- url,
8925
- apiUrl,
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
- const responseText = await response.text();
8945
- let backendData;
8946
- try {
8947
- backendData = JSON.parse(responseText);
8948
- } catch (parseError) {
8949
- console.error("[useDashboardMetrics] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
8950
- throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
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
- console.log("[useDashboardMetrics] Backend response:", {
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: backendData.line_metrics || []
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 currentShiftDetails = getCurrentShift(defaultTimezone, shiftConfig);
9027
- const operationalDateForSubscription = getOperationalDate(defaultTimezone);
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
- const createSubscription = (table, filter2, channelNameBase, callback) => {
9034
- const channelName = `${channelNameBase}-${Date.now()}`.replace(/[^a-zA-Z0-9_-]/g, "");
9035
- const channel = supabase.channel(channelName).on(
9036
- "postgres_changes",
9037
- { event: "*", schema, table, filter: filter2 },
9038
- (payload) => {
9039
- const payloadData = payload.new;
9040
- if (payloadData?.date === operationalDateForSubscription && payloadData?.shift_id === currentShiftDetails.shiftId) {
9041
- callback();
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
- ).subscribe();
9045
- channels.push(channel);
9046
- };
9047
- createSubscription(companySpecificMetricsTable, wsMetricsFilter, "dashboard-ws-metrics", queueUpdate);
9048
- createSubscription(configuredLineMetricsTable, lineMetricsFilter, "dashboard-line-metrics", () => {
9049
- queueUpdate();
9050
- onLineMetricsUpdateRef.current?.();
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 firstLineId = useMemo(() => {
9087
- const configuredLineIds = getConfiguredLineIds(entityConfig);
9088
- return configuredLineIds.length > 0 ? configuredLineIds[0] : void 0;
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 lineIdForShiftConfig = isFactoryView ? firstLineId : lineId;
9091
- const { shiftConfig: dynamicShiftConfig, isFromDatabase, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineIdForShiftConfig);
9092
- const shiftConfig = dynamicShiftConfig;
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
- const lineParam = isFactory ? `line_ids=${getConfiguredLineIds(entityConfig).join(",")}` : `line_id=${currentLineId}`;
9145
- const response = await fetch(
9146
- `${apiUrl}/api/dashboard/line-kpis?${lineParam}&date=${operationalDate}&shift_id=${currentShiftDetails.shiftId}&company_id=${entityConfig.companyId}`,
9147
- {
9148
- headers: {
9149
- "Authorization": `Bearer ${session.access_token}`,
9150
- "Content-Type": "application/json"
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
- if (!response.ok) {
9155
- const errorText = await response.text();
9156
- console.error("[useLineKPIs] Backend API error response:", errorText);
9157
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
9158
- }
9159
- const responseText = await response.text();
9160
- let backendData;
9161
- try {
9162
- backendData = JSON.parse(responseText);
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
- setKPIs(null);
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 targetLineIds = currentLineId === factoryViewIdentifier ? getConfiguredLineIds(entityConfig) : [currentLineId];
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
- const lmChannelName = `kpi-lm-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
9215
- const lmChannel = supabase.channel(lmChannelName).on(
9216
- "postgres_changes",
9217
- { event: "*", schema, table: lineMetricsTable, filter: lineMetricsFilter },
9218
- (payload) => {
9219
- const payloadData = payload.new || payload.old;
9220
- if (payloadData?.date === operationalDate && payloadData?.shift_id === currentShiftDetails.shiftId) {
9221
- queueUpdateRef.current?.();
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
- ).subscribe((status, err) => {
9225
- if (status === REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
9226
- console.error(`[useLineKPIs] Subscription to ${lineMetricsTable} FAILED: ${status}`, err || "");
9227
- }
9228
- });
9229
- activeChannels.push(lmChannel);
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: companySpecificMetricsTable, filter: companyTableFilter },
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 ${companySpecificMetricsTable} FAILED: ${status}`, err || "");
9888
+ console.error(`[useLineKPIs] Subscription to ${lineMetricsTable} FAILED: ${status}`, err || "");
9244
9889
  }
9245
9890
  });
9246
- activeChannels.push(csChannel);
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
- let enabledWorkspaceIds = [];
9343
- for (const lineId2 of targetLineIds) {
9344
- const workspaces = await workspaceService.getWorkspaces(lineId2);
9345
- const enabledIds = workspaces.filter((ws) => ws.enable === true).map((ws) => ws.id);
9346
- enabledWorkspaceIds.push(...enabledIds);
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 workspaces = await workspaceService.getWorkspaces(lineIdRef.current);
9408
- const enabledWorkspaceIds = workspaces.filter((ws) => ws.enable === true).map((ws) => ws.id);
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: `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`
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: `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`
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 response = await fetch(
10369
- `${apiUrl}/api/dashboard/factory-overview?${params.toString()}`,
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
- for (const lineId of targetLineIds) {
10450
- console.log(`\u{1F504} Fetching workspaces for line: ${lineId}`);
10451
- const lineDisplayNamesMap = await workspaceService.getWorkspaceDisplayNames(void 0, lineId);
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(() => getConfiguredLineIds(entityConfig), [entityConfig]);
10928
- const representativeLineId = options?.allowedLineIds?.[0] || configuredLineIds[0];
10929
- const { shiftConfig: dynamicShiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(representativeLineId);
10930
- const shiftConfig = dynamicShiftConfig || staticShiftConfig;
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 queryShiftId = useMemo(() => {
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
- shiftConfig
11707
+ staticShiftConfig
10945
11708
  );
10946
11709
  return currentShift.shiftId;
10947
- }, [options?.initialShiftId, timezone, dateTimeConfig.defaultTimezone, shiftConfig]);
10948
- const queryDate = useMemo(() => {
10949
- return options?.initialDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
10950
- }, [options?.initialDate, timezone, dateTimeConfig.defaultTimezone]);
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 allConfiguredLineIds = getConfiguredLineIds(entityConfig);
10969
- const configuredLineIds2 = options?.allowedLineIds ? allConfiguredLineIds.filter((id3) => options.allowedLineIds.includes(id3)) : allConfiguredLineIds;
10970
- let enabledWorkspaceIds = [];
10971
- for (const lineId of configuredLineIds2) {
10972
- const workspaces2 = await workspaceService.getWorkspaces(lineId);
10973
- const enabledIds = workspaces2.filter((ws) => ws.enable === true).map((ws) => ws.id);
10974
- enabledWorkspaceIds.push(...enabledIds);
10975
- }
10976
- if (enabledWorkspaceIds.length === 0) {
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
- const { data, error: fetchError } = await supabase.from(metricsTable).select(`
10983
- workspace_name,
10984
- total_output,
10985
- avg_pph,
10986
- efficiency,
10987
- workspace_id,
10988
- avg_cycle_time,
10989
- performance_score,
10990
- trend_score,
10991
- line_id,
10992
- total_day_output
10993
- `).eq("date", queryDate).eq("shift_id", queryShiftId).in("workspace_id", enabledWorkspaceIds).order("efficiency", { ascending: false });
10994
- if (fetchError) throw fetchError;
10995
- const transformedData = (data || []).map((item) => ({
10996
- company_id: entityConfig.companyId || "unknown",
10997
- line_id: item.line_id,
10998
- shift_id: queryShiftId,
10999
- date: queryDate,
11000
- workspace_uuid: item.workspace_id,
11001
- workspace_name: item.workspace_name,
11002
- action_count: item.total_output || 0,
11003
- pph: item.avg_pph || 0,
11004
- performance_score: item.performance_score || 0,
11005
- avg_cycle_time: item.avg_cycle_time || 0,
11006
- trend: item.trend_score === 1 ? 2 : 0,
11007
- predicted_output: 0,
11008
- efficiency: item.efficiency || 0,
11009
- action_threshold: item.total_day_output || 0
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
- }, [queryDate, queryShiftId, metricsTable, supabase, entityConfig.companyId, entityConfig, options?.allowedLineIds, options?.enabled, isShiftConfigLoading]);
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
- const channel2 = supabase.channel(`all-workspace-metrics-${Date.now()}`).on(
11039
- "postgres_changes",
11871
+ if (!metricsTable || configuredLineIds.length === 0) return [];
11872
+ const groupsToSubscribe = hasSpecificDateShift || shiftGroups.length === 0 ? [
11040
11873
  {
11041
- event: "*",
11042
- schema,
11043
- table: metricsTable
11044
- },
11045
- async (payload) => {
11046
- const data = payload.new || payload.old;
11047
- if (data?.date !== queryDate || data?.shift_id !== queryShiftId) {
11048
- return;
11049
- }
11050
- if (fetchTimeoutRef.current) {
11051
- clearTimeout(fetchTimeoutRef.current);
11052
- }
11053
- fetchTimeoutRef.current = setTimeout(async () => {
11054
- if (!isFetchingRef.current) {
11055
- await fetchWorkspaceMetrics();
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 = null;
11058
- }, 300);
11059
- }
11060
- ).subscribe();
11061
- return channel2;
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 channel = setupSubscription();
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
- if (channel) {
11070
- supabase.removeChannel(channel);
11071
- }
11923
+ channels.forEach((channel) => supabase.removeChannel(channel));
11072
11924
  };
11073
- }, [queryDate, queryShiftId, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId]);
11925
+ }, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId, configuredLineIds]);
11074
11926
  useEffect(() => {
11075
11927
  setInitialized(false);
11076
- }, [queryDate, queryShiftId]);
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: 6e4,
28987
+ levelLoadingTimeOut: 2e4,
28081
28988
  levelLoadingMaxRetry: 5,
28082
28989
  levelLoadingRetryDelay: 500,
28083
- fragLoadingTimeOut: 6e4,
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
- abrEwmaDefaultEstimate: 5e7
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 getAuthToken3 = async () => {
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 getAuthToken3();
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 Maintenance",
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 getAuthToken4()}`
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 getAuthToken4 = async () => {
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 getAuthToken4()}`
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 getAuthToken4 = useCallback(async () => {
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 getAuthToken4();
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, getAuthToken4, isEffectiveShiftReady]);
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
- await loadCategoryMetadata(categoryId, false);
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
- const video = await s3ClipsService.getClipById(clipId);
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 Maintenance": "#3b82f6",
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, 60, 3, 3, "F");
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, 163, 190, 163);
37794
- const hourlyPerfStartY = 168;
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 tableStartY = 199;
37798
- const rowHeight = 8;
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(18);
38750
+ doc.setFontSize(titleFontSize);
37804
38751
  doc.setFont("helvetica", "bold");
37805
38752
  doc.setTextColor(40, 40, 40);
37806
- doc.text("Hourly Performance", 20, 178);
38753
+ const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
38754
+ doc.text("Hourly Performance", 20, hourlyTitleY);
37807
38755
  doc.setTextColor(0, 0, 0);
37808
- doc.setFontSize(11);
38756
+ doc.setFontSize(headerFontSize);
37809
38757
  doc.setFont("helvetica", "bold");
37810
- doc.text("Time Range", 25, 188);
37811
- doc.text("Output", 95, 188);
37812
- doc.text("Target", 135, 188);
37813
- doc.text("Status", 170, 188);
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
- doc.line(20, 191, 190, 191);
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 shiftResult = getCurrentShift(tz, dashboardConfig?.shiftConfig);
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 shiftResult = getCurrentShift(tz, dashboardConfig?.shiftConfig);
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, kpisError]);
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 && !kpisLoading && isChangingFilter) {
46581
+ if (!metricsLoading && isChangingFilter) {
45621
46582
  if (workspaceMetrics.length > 0 || selectedLineId === factoryViewId) {
45622
46583
  setIsChangingFilter(false);
45623
46584
  }
45624
46585
  }
45625
- }, [metricsLoading, kpisLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
46586
+ }, [metricsLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
45626
46587
  useEffect(() => {
45627
- if (!metricsLoading && !kpisLoading && !hasInitialDataLoaded) {
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, kpisLoading, hasInitialDataLoaded, defaultLineId, factoryViewId, isSupervisor]);
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 || kpisLoading;
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 = ({ line, onClick, supervisorEnabled = false }) => {
46917
- const { kpis, isLoading, error } = useLineKPIs({ lineId: line.id });
46918
- const { supervisorName } = useLineSupervisor(line.id);
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 availableLineId = Object.keys(configuredLineNames)[0];
47446
- const effectiveLineId = lineId || userAccessibleLineIds?.[0] || availableLineId;
47447
- const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(effectiveLineId);
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: typeof shift === "number" ? shift : typeof shift === "string" ? parseInt(shift) : void 0,
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", shiftConfig);
48473
+ return getShiftNameById(shiftId2, timezone || "Asia/Kolkata", staticShiftConfig);
47464
48474
  }
47465
- const currentShift = getCurrentShift(timezone || "Asia/Kolkata", shiftConfig);
47466
- return currentShift.shiftName || getShiftNameById(currentShift.shiftId, timezone || "Asia/Kolkata", shiftConfig);
47467
- }, [timezone, shiftConfig]);
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
- const effectiveShiftId = shiftId2 !== void 0 ? shiftId2 : getCurrentShift(timezone || "Asia/Kolkata", shiftConfig).shiftId;
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 calculateBreakDuration = (startTime, endTime) => {
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: calculateBreakDuration(
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: calculateBreakDuration(
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
- await forceRefreshWorkspaceDisplayNames();
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(WorkspacePdfGenerator, { workspace }) }),
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__ */ jsxs("header", { className: "sticky top-0 z-10 px-3 sm:px-4 md:px-5 lg:px-6 py-2 sm:py-2.5 lg:py-3 flex flex-col shadow-sm bg-white", children: [
52396
- /* @__PURE__ */ jsxs("div", { className: "sm:hidden", children: [
52397
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
52398
- /* @__PURE__ */ jsx(
52399
- BackButtonMinimal,
52400
- {
52401
- onClick: () => router.push("/"),
52402
- text: "Back",
52403
- size: "sm",
52404
- "aria-label": "Navigate back to dashboard"
52405
- }
52406
- ),
52407
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
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__ */ jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
52439
- /* @__PURE__ */ jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsx(
52440
- BackButtonMinimal,
53430
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
53431
+ /* @__PURE__ */ jsx(
53432
+ "button",
52441
53433
  {
52442
- onClick: () => router.push("/"),
52443
- text: "Back",
52444
- size: "default",
52445
- "aria-label": "Navigate back to dashboard"
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("div", { className: "absolute left-1/2 transform -translate-x-1/2 max-w-[calc(100%-200px)]", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
52449
- /* @__PURE__ */ jsx("h1", { className: "text-lg md:text-xl lg:text-2xl xl:text-3xl font-semibold text-gray-900 truncate", children: "System Health" }),
52450
- /* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
52451
- /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
52452
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
52453
- ] })
52454
- ] }) }),
52455
- /* @__PURE__ */ jsxs("div", { className: "absolute right-0 flex gap-2", children: [
52456
- /* @__PURE__ */ jsx(
52457
- "button",
52458
- {
52459
- onClick: () => {
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 null;
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: "Weekly usage" }),
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
- "hover:bg-gray-50 transition-colors",
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__ */ jsxs(Fragment, { children: [
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 isCurrentUser = user?.user_id === currentUserId;
54207
- const canChangeRole = user && permissions.canChangeRole(user);
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
- handleChangeRole(user);
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 disabled:opacity-50 disabled:cursor-not-allowed",
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(UserCog, { className: "w-4 h-4" }),
54220
- "Change Role"
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 your team members" : "Manage supervisors in your factory";
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 };