@optifye/dashboard-core 6.10.1 → 6.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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);
@@ -8843,11 +9079,12 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
8843
9079
  const [isLoading, setIsLoading] = useState(true);
8844
9080
  const [error, setError] = useState(null);
8845
9081
  const supabase = useSupabase();
8846
- const lineIdsKey = useMemo(() => lineIds.sort().join(","), [lineIds]);
9082
+ const lineIdsKey = useMemo(() => lineIds.slice().sort().join(","), [lineIds]);
8847
9083
  useEffect(() => {
8848
9084
  if (!lineIds || lineIds.length === 0) {
8849
9085
  setShiftConfigMap(/* @__PURE__ */ new Map());
8850
9086
  setIsLoading(false);
9087
+ setError(null);
8851
9088
  return;
8852
9089
  }
8853
9090
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -8856,56 +9093,56 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
8856
9093
  console.warn("[useMultiLineShiftConfigs] No valid line IDs provided");
8857
9094
  setShiftConfigMap(/* @__PURE__ */ new Map());
8858
9095
  setIsLoading(false);
9096
+ setError(null);
8859
9097
  return;
8860
9098
  }
8861
9099
  let mounted = true;
8862
- const calculateBreakDuration2 = (startTime, endTime) => {
8863
- const [sh, sm] = startTime.split(":").map(Number);
8864
- const [eh, em] = endTime.split(":").map(Number);
8865
- let startMinutes = sh * 60 + sm;
8866
- let endMinutes = eh * 60 + em;
8867
- if (endMinutes < startMinutes) {
8868
- endMinutes += 24 * 60;
8869
- }
8870
- return endMinutes - startMinutes;
8871
- };
8872
- const stripSeconds = (timeStr) => {
8873
- if (!timeStr) return timeStr;
8874
- return timeStr.substring(0, 5);
8875
- };
8876
- const buildShiftConfigFromRows = (shifts, fallback) => {
8877
- const mapped = shifts.map((shift) => ({
8878
- shiftId: shift.shift_id,
8879
- shiftName: shift.shift_name || `Shift ${shift.shift_id}`,
8880
- startTime: stripSeconds(shift.start_time),
8881
- endTime: stripSeconds(shift.end_time),
8882
- breaks: Array.isArray(shift.breaks) ? shift.breaks.map((b) => ({
8883
- startTime: b.start || b.startTime || "00:00",
8884
- endTime: b.end || b.endTime || "00:00",
8885
- duration: calculateBreakDuration2(
8886
- b.start || b.startTime || "00:00",
8887
- b.end || b.endTime || "00:00"
8888
- ),
8889
- remarks: b.remarks || b.name || ""
8890
- })) : [],
8891
- timezone: shift.timezone
8892
- }));
8893
- const day = mapped.find((s) => s.shiftId === 0);
8894
- const night = mapped.find((s) => s.shiftId === 1);
8895
- return {
8896
- shifts: mapped,
8897
- timezone: mapped[0]?.timezone,
8898
- dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallback?.dayShift,
8899
- nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallback?.nightShift,
8900
- transitionPeriodMinutes: fallback?.transitionPeriodMinutes || 0
8901
- };
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
+ });
8902
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
+ );
8903
9130
  const fetchAllConfigs = async () => {
8904
9131
  try {
8905
- setIsLoading(true);
8906
- setError(null);
8907
- console.log(`[useMultiLineShiftConfigs] Fetching shift configs for ${validLineIds.length} lines`);
8908
- 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);
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);
8909
9146
  if (fetchError) {
8910
9147
  console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
8911
9148
  throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
@@ -8917,33 +9154,23 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
8917
9154
  }
8918
9155
  lineShiftsMap.get(row.line_id).push(row);
8919
9156
  });
8920
- const configMap = /* @__PURE__ */ new Map();
8921
9157
  lineShiftsMap.forEach((shifts, lineId) => {
8922
- const config = buildShiftConfigFromRows(shifts, fallbackConfig);
8923
- configMap.set(lineId, config);
9158
+ shiftConfigStore.setFromOperatingHoursRows(lineId, shifts, fallbackConfig);
8924
9159
  });
8925
- validLineIds.forEach((lineId) => {
8926
- if (!configMap.has(lineId) && fallbackConfig) {
9160
+ missingLineIds.forEach((lineId) => {
9161
+ if (!lineShiftsMap.has(lineId) && fallbackConfig) {
8927
9162
  console.warn(`[useMultiLineShiftConfigs] No config found for line ${lineId}, using fallback`);
8928
- configMap.set(lineId, fallbackConfig);
9163
+ shiftConfigStore.set(lineId, fallbackConfig);
8929
9164
  }
8930
9165
  });
8931
- console.log(`[useMultiLineShiftConfigs] Built configs for ${configMap.size} lines`);
9166
+ console.log(`[useMultiLineShiftConfigs] Stored configs for ${lineShiftsMap.size} lines`);
8932
9167
  if (mounted) {
8933
- setShiftConfigMap(configMap);
8934
9168
  setIsLoading(false);
8935
9169
  }
8936
9170
  } catch (err) {
8937
9171
  console.error("[useMultiLineShiftConfigs] Error:", err);
8938
9172
  if (mounted) {
8939
9173
  setError(err instanceof Error ? err.message : "Failed to fetch shift configs");
8940
- if (fallbackConfig) {
8941
- const fallbackMap = /* @__PURE__ */ new Map();
8942
- validLineIds.forEach((lineId) => {
8943
- fallbackMap.set(lineId, fallbackConfig);
8944
- });
8945
- setShiftConfigMap(fallbackMap);
8946
- }
8947
9174
  setIsLoading(false);
8948
9175
  }
8949
9176
  }
@@ -8951,6 +9178,8 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
8951
9178
  fetchAllConfigs();
8952
9179
  return () => {
8953
9180
  mounted = false;
9181
+ unsubscribeStore();
9182
+ unsubscribeRealtimeList.forEach((unsub) => unsub());
8954
9183
  };
8955
9184
  }, [lineIdsKey, supabase]);
8956
9185
  return {
@@ -9293,8 +9522,10 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
9293
9522
  const operationalDateForSubscription = currentShiftDetails.date;
9294
9523
  const targetLineIds = [currentLineIdToUse];
9295
9524
  if (targetLineIds.length === 0) return;
9296
- const wsMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
9297
- const lineMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
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}`;
9298
9529
  const createSubscription = (table, filter2, channelNameBase, callback) => {
9299
9530
  const channelName = `${channelNameBase}-${Date.now()}`.replace(/[^a-zA-Z0-9_-]/g, "");
9300
9531
  const channel = supabase.channel(channelName).on(
@@ -9771,12 +10002,12 @@ var useRealtimeLineMetrics = ({
9771
10002
  const companyId = entityConfig.companyId;
9772
10003
  const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
9773
10004
  const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
9774
- let enabledWorkspaceIds = [];
9775
- for (const lineId2 of targetLineIds) {
9776
- const workspaces = await workspaceService.getWorkspaces(lineId2);
9777
- const enabledIds = workspaces.filter((ws) => ws.enable === true).map((ws) => ws.id);
9778
- enabledWorkspaceIds.push(...enabledIds);
9779
- }
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
+ );
9780
10011
  const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
9781
10012
  line_id,
9782
10013
  workspace_id,
@@ -9836,8 +10067,8 @@ var useRealtimeLineMetrics = ({
9836
10067
  const companyId = entityConfig.companyId;
9837
10068
  const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
9838
10069
  const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
9839
- const workspaces = await workspaceService.getWorkspaces(lineIdRef.current);
9840
- 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);
9841
10072
  const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
9842
10073
  workspace_id,
9843
10074
  workspace_name,
@@ -9957,20 +10188,23 @@ var useRealtimeLineMetrics = ({
9957
10188
  const companyId = entityConfig.companyId;
9958
10189
  const metricsTablePrefix = getMetricsTablePrefix();
9959
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}`;
9960
10195
  const lineMetricsChannel = supabase.channel(`line-metrics-${timestamp}`).on(
9961
10196
  "postgres_changes",
9962
10197
  {
9963
10198
  event: "*",
9964
10199
  schema: "public",
9965
10200
  table: "line_metrics",
9966
- filter: `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`
10201
+ filter: filter2
9967
10202
  },
9968
10203
  async (payload) => {
9969
10204
  const payloadData = payload.new;
9970
10205
  if (process.env.NODE_ENV === "development") {
9971
10206
  console.log("Line metrics update received:", payloadData);
9972
10207
  }
9973
- const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
9974
10208
  if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
9975
10209
  queueUpdate();
9976
10210
  }
@@ -9986,14 +10220,13 @@ var useRealtimeLineMetrics = ({
9986
10220
  event: "*",
9987
10221
  schema: "public",
9988
10222
  table: metricsTable,
9989
- filter: `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`
10223
+ filter: filter2
9990
10224
  },
9991
10225
  async (payload) => {
9992
10226
  const payloadData = payload.new;
9993
10227
  if (process.env.NODE_ENV === "development") {
9994
10228
  console.log(`${metricsTablePrefix} update received:`, payloadData);
9995
10229
  }
9996
- const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
9997
10230
  if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
9998
10231
  queueUpdate();
9999
10232
  }
@@ -10004,7 +10237,7 @@ var useRealtimeLineMetrics = ({
10004
10237
  }
10005
10238
  });
10006
10239
  channelsRef.current = [lineMetricsChannel, metricsChannel];
10007
- }, [supabase, queueUpdate, urlDate, shiftId, entityConfig, dateTimeConfig.defaultTimezone]);
10240
+ }, [supabase, queueUpdate, urlDate, shiftId, entityConfig, timezone, dateTimeConfig.defaultTimezone]);
10008
10241
  const prevShiftIdRef = useRef(void 0);
10009
10242
  useEffect(() => {
10010
10243
  if (!lineId) return;
@@ -10783,34 +11016,16 @@ var useFactoryOverviewMetrics = (date, shiftId) => {
10783
11016
  if (lineIds.length === 0) {
10784
11017
  throw new Error("No lines configured in entityConfig");
10785
11018
  }
10786
- const { data: { session } } = await supabase.auth.getSession();
10787
- if (!session?.access_token) {
10788
- throw new Error("No authentication token available");
10789
- }
10790
- const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
10791
- if (!apiUrl) {
10792
- throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
10793
- }
10794
11019
  const params = new URLSearchParams({
10795
11020
  line_ids: lineIds.join(","),
10796
11021
  date: queryDate,
10797
11022
  shift_id: queryShiftId.toString(),
10798
11023
  company_id: entityConfig.companyId || ""
10799
11024
  });
10800
- const response = await fetch(
10801
- `${apiUrl}/api/dashboard/factory-overview?${params.toString()}`,
10802
- {
10803
- headers: {
10804
- "Authorization": `Bearer ${session.access_token}`,
10805
- "Content-Type": "application/json"
10806
- }
10807
- }
11025
+ const data = await fetchBackendJson(
11026
+ supabase,
11027
+ `/api/dashboard/factory-overview?${params.toString()}`
10808
11028
  );
10809
- if (!response.ok) {
10810
- const errorText = await response.text();
10811
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
10812
- }
10813
- const data = await response.json();
10814
11029
  setMetrics(data);
10815
11030
  } catch (err) {
10816
11031
  console.error("[useFactoryOverviewMetrics] Error fetching factory overview:", err);
@@ -10839,6 +11054,46 @@ var isInitialized = false;
10839
11054
  var isInitializing = false;
10840
11055
  var initializedWithLineIds = [];
10841
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
+ };
10842
11097
  function getCurrentLineIds() {
10843
11098
  try {
10844
11099
  const config = _getDashboardConfigInstance();
@@ -10878,15 +11133,20 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
10878
11133
  console.log("\u{1F504} Target line IDs for workspace filtering:", targetLineIds);
10879
11134
  runtimeWorkspaceDisplayNames = {};
10880
11135
  if (targetLineIds.length > 0) {
10881
- for (const lineId of targetLineIds) {
10882
- console.log(`\u{1F504} Fetching workspaces for line: ${lineId}`);
10883
- 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 }) => {
10884
11144
  runtimeWorkspaceDisplayNames[lineId] = {};
10885
11145
  lineDisplayNamesMap.forEach((displayName, workspaceId) => {
10886
11146
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
10887
11147
  });
10888
11148
  console.log(`\u2705 Stored ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
10889
- }
11149
+ });
10890
11150
  } else {
10891
11151
  console.warn("\u26A0\uFE0F No line IDs found, fetching all workspaces (less efficient)");
10892
11152
  const allWorkspacesMap = await workspaceService.getWorkspaceDisplayNames();
@@ -10899,6 +11159,7 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
10899
11159
  initializedWithLineIds = targetLineIds;
10900
11160
  console.log("\u2705 Workspace display names initialized from Supabase:", runtimeWorkspaceDisplayNames);
10901
11161
  console.log("\u2705 Initialized with line IDs:", initializedWithLineIds);
11162
+ notifyWorkspaceDisplayNamesListeners(explicitLineId);
10902
11163
  } catch (error) {
10903
11164
  console.error("\u274C Failed to initialize workspace display names from Supabase:", error);
10904
11165
  } finally {
@@ -10921,6 +11182,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
10921
11182
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
10922
11183
  });
10923
11184
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
11185
+ notifyWorkspaceDisplayNamesListeners(lineId);
10924
11186
  } catch (error) {
10925
11187
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
10926
11188
  }
@@ -10939,6 +11201,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
10939
11201
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
10940
11202
  });
10941
11203
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
11204
+ notifyWorkspaceDisplayNamesListeners(lineId);
10942
11205
  } catch (error) {
10943
11206
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
10944
11207
  }
@@ -10978,6 +11241,7 @@ var getWorkspaceDisplayName = (workspaceId, lineId) => {
10978
11241
  runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
10979
11242
  });
10980
11243
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
11244
+ notifyWorkspaceDisplayNamesListeners(lineId);
10981
11245
  }).catch((error) => {
10982
11246
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
10983
11247
  });
@@ -11018,6 +11282,7 @@ var getShortWorkspaceDisplayName = (workspaceId, lineId) => {
11018
11282
  runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
11019
11283
  });
11020
11284
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
11285
+ notifyWorkspaceDisplayNamesListeners(lineId);
11021
11286
  }).catch((error) => {
11022
11287
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
11023
11288
  });
@@ -11059,6 +11324,9 @@ var getAllWorkspaceDisplayNamesAsync = async (companyId, lineId) => {
11059
11324
  while (isInitializing) {
11060
11325
  await new Promise((resolve) => setTimeout(resolve, 100));
11061
11326
  }
11327
+ if (lineId && isInitialized && !runtimeWorkspaceDisplayNames[lineId]) {
11328
+ await preInitializeWorkspaceDisplayNames(lineId);
11329
+ }
11062
11330
  if (lineId && runtimeWorkspaceDisplayNames[lineId]) {
11063
11331
  return { ...runtimeWorkspaceDisplayNames[lineId] };
11064
11332
  }
@@ -11091,6 +11359,7 @@ var refreshWorkspaceDisplayNames = async (companyId) => {
11091
11359
  runtimeWorkspaceDisplayNames = {};
11092
11360
  isInitialized = false;
11093
11361
  await initializeWorkspaceDisplayNames();
11362
+ notifyWorkspaceDisplayNamesListeners();
11094
11363
  };
11095
11364
  var clearWorkspaceDisplayNamesCache = () => {
11096
11365
  workspaceService.clearWorkspaceDisplayNamesCache();
@@ -11099,6 +11368,7 @@ var clearWorkspaceDisplayNamesCache = () => {
11099
11368
  isInitializing = false;
11100
11369
  initializedWithLineIds = [];
11101
11370
  initializationPromise = null;
11371
+ notifyWorkspaceDisplayNamesListeners();
11102
11372
  };
11103
11373
 
11104
11374
  // src/lib/hooks/useWorkspaceDisplayNames.ts
@@ -11121,6 +11391,23 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
11121
11391
  useEffect(() => {
11122
11392
  fetchDisplayNames();
11123
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]);
11124
11411
  return {
11125
11412
  displayNames,
11126
11413
  loading,
@@ -11129,7 +11416,7 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
11129
11416
  };
11130
11417
  };
11131
11418
  var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
11132
- const [displayName, setDisplayName] = useState(workspaceId);
11419
+ const [displayName, setDisplayName] = useState(() => getWorkspaceDisplayName(workspaceId, lineId));
11133
11420
  const [loading, setLoading] = useState(true);
11134
11421
  const [error, setError] = useState(null);
11135
11422
  const fetchDisplayName = useCallback(async () => {
@@ -11148,6 +11435,18 @@ var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
11148
11435
  useEffect(() => {
11149
11436
  fetchDisplayName();
11150
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]);
11151
11450
  return {
11152
11451
  displayName,
11153
11452
  loading,
@@ -11178,6 +11477,18 @@ var useWorkspaceDisplayNamesMap = (workspaceIds, lineId, companyId) => {
11178
11477
  useEffect(() => {
11179
11478
  fetchDisplayNames();
11180
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]);
11181
11492
  return {
11182
11493
  displayNames,
11183
11494
  loading,
@@ -11424,13 +11735,19 @@ var useAllWorkspaceMetrics = (options) => {
11424
11735
  setError(null);
11425
11736
  try {
11426
11737
  const lineWorkspaceMap = /* @__PURE__ */ new Map();
11427
- let allEnabledWorkspaceIds = [];
11428
- for (const lineId of configuredLineIds) {
11429
- const lineWorkspaces = await workspaceService.getWorkspaces(lineId);
11430
- const enabledIds = lineWorkspaces.filter((ws) => ws.enable === true).map((ws) => ws.id);
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 }) => {
11431
11747
  lineWorkspaceMap.set(lineId, enabledIds);
11432
- allEnabledWorkspaceIds.push(...enabledIds);
11433
- }
11748
+ enabledIds.forEach((id3) => allEnabledWorkspaceIdSet.add(id3));
11749
+ });
11750
+ const allEnabledWorkspaceIds = Array.from(allEnabledWorkspaceIdSet);
11434
11751
  if (allEnabledWorkspaceIds.length === 0) {
11435
11752
  setWorkspaces([]);
11436
11753
  setInitialized(true);
@@ -11551,43 +11868,61 @@ var useAllWorkspaceMetrics = (options) => {
11551
11868
  });
11552
11869
  }
11553
11870
  const setupSubscription = () => {
11554
- const channel2 = supabase.channel(`all-workspace-metrics-${Date.now()}`).on(
11555
- "postgres_changes",
11871
+ if (!metricsTable || configuredLineIds.length === 0) return [];
11872
+ const groupsToSubscribe = hasSpecificDateShift || shiftGroups.length === 0 ? [
11556
11873
  {
11557
- event: "*",
11558
- schema,
11559
- table: metricsTable
11560
- },
11561
- async (payload) => {
11562
- const data = payload.new || payload.old;
11563
- const dataKey = `${data?.date}-${data?.shift_id}`;
11564
- if (!validDateShiftCombos.has(dataKey)) {
11565
- return;
11566
- }
11567
- if (fetchTimeoutRef.current) {
11568
- clearTimeout(fetchTimeoutRef.current);
11569
- }
11570
- fetchTimeoutRef.current = setTimeout(async () => {
11571
- if (!isFetchingRef.current) {
11572
- await fetchWorkspaceMetrics();
11573
- }
11574
- fetchTimeoutRef.current = null;
11575
- }, 300);
11874
+ date: fallbackQueryDate,
11875
+ shiftId: fallbackQueryShiftId,
11876
+ lineIds: configuredLineIds
11576
11877
  }
11577
- ).subscribe();
11578
- return channel2;
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;
11903
+ }
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
+ });
11579
11916
  };
11580
- const channel = setupSubscription();
11917
+ const channels = setupSubscription();
11581
11918
  return () => {
11582
11919
  if (fetchTimeoutRef.current) {
11583
11920
  clearTimeout(fetchTimeoutRef.current);
11584
11921
  fetchTimeoutRef.current = null;
11585
11922
  }
11586
- if (channel) {
11587
- supabase.removeChannel(channel);
11588
- }
11923
+ channels.forEach((channel) => supabase.removeChannel(channel));
11589
11924
  };
11590
- }, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId]);
11925
+ }, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId, configuredLineIds]);
11591
11926
  useEffect(() => {
11592
11927
  setInitialized(false);
11593
11928
  }, [fallbackQueryDate, fallbackQueryShiftId, shiftGroups]);
@@ -13025,6 +13360,85 @@ function useLineSupervisor(lineId) {
13025
13360
  error
13026
13361
  };
13027
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
+ };
13028
13442
  function useIdleTimeReasons({
13029
13443
  workspaceId,
13030
13444
  lineId,
@@ -13430,6 +13844,102 @@ var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig, li
13430
13844
  return fullName;
13431
13845
  };
13432
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
+
13433
13943
  // ../../node_modules/clsx/dist/clsx.mjs
13434
13944
  function r(e) {
13435
13945
  var t, f, n = "";
@@ -28466,6 +28976,7 @@ if (typeof document !== "undefined") {
28466
28976
  }
28467
28977
  }
28468
28978
  var BASE_HLS_CONFIG = {
28979
+ // Keep buffer small to reduce wasted downloads on slow links
28469
28980
  maxBufferLength: 3,
28470
28981
  maxMaxBufferLength: 8,
28471
28982
  maxBufferSize: 50 * 1e3 * 1e3,
@@ -28473,10 +28984,10 @@ var BASE_HLS_CONFIG = {
28473
28984
  manifestLoadingTimeOut: 15e3,
28474
28985
  manifestLoadingMaxRetry: 3,
28475
28986
  manifestLoadingRetryDelay: 500,
28476
- levelLoadingTimeOut: 6e4,
28987
+ levelLoadingTimeOut: 2e4,
28477
28988
  levelLoadingMaxRetry: 5,
28478
28989
  levelLoadingRetryDelay: 500,
28479
- fragLoadingTimeOut: 6e4,
28990
+ fragLoadingTimeOut: 2e4,
28480
28991
  fragLoadingMaxRetry: 5,
28481
28992
  fragLoadingRetryDelay: 500,
28482
28993
  startPosition: -1,
@@ -28489,7 +29000,10 @@ var BASE_HLS_CONFIG = {
28489
29000
  abrBandWidthFactor: 0.95,
28490
29001
  abrBandWidthUpFactor: 0.7,
28491
29002
  abrMaxWithRealBitrate: false,
28492
- abrEwmaDefaultEstimate: 5e7
29003
+ // Favor a conservative first rendition on constrained networks
29004
+ abrEwmaDefaultEstimate: 1e6,
29005
+ startLevel: 0,
29006
+ capLevelToPlayerSize: true
28493
29007
  };
28494
29008
  var HlsVideoPlayer = forwardRef(({
28495
29009
  src,
@@ -29573,7 +30087,7 @@ var getSupabaseClient2 = () => {
29573
30087
  }
29574
30088
  return createClient(url, key);
29575
30089
  };
29576
- var getAuthToken3 = async () => {
30090
+ var getAuthToken4 = async () => {
29577
30091
  try {
29578
30092
  const supabase = getSupabaseClient2();
29579
30093
  const { data: { session } } = await supabase.auth.getSession();
@@ -29596,7 +30110,7 @@ function useWorkspaceCrop(workspaceId) {
29596
30110
  setIsLoading(true);
29597
30111
  setError(null);
29598
30112
  try {
29599
- const token = await getAuthToken3();
30113
+ const token = await getAuthToken4();
29600
30114
  if (!token) {
29601
30115
  throw new Error("Authentication required");
29602
30116
  }
@@ -30645,7 +31159,7 @@ var FileManagerFilters = ({
30645
31159
  const ROOT_CAUSE_OPTIONS = [
30646
31160
  "Operator Absent",
30647
31161
  "Operator Idle",
30648
- "Machine Maintenance",
31162
+ "Machine Downtime",
30649
31163
  "No Material"
30650
31164
  ];
30651
31165
  const getIdleTimeRootCause = useCallback((clipId) => {
@@ -30685,7 +31199,7 @@ var FileManagerFilters = ({
30685
31199
  method: "POST",
30686
31200
  headers: {
30687
31201
  "Content-Type": "application/json",
30688
- "Authorization": `Bearer ${await getAuthToken4()}`
31202
+ "Authorization": `Bearer ${await getAuthToken5()}`
30689
31203
  },
30690
31204
  body: JSON.stringify({
30691
31205
  action: "clip-metadata",
@@ -30718,7 +31232,7 @@ var FileManagerFilters = ({
30718
31232
  });
30719
31233
  }
30720
31234
  }, [workspaceId, date, shift]);
30721
- const getAuthToken4 = async () => {
31235
+ const getAuthToken5 = async () => {
30722
31236
  try {
30723
31237
  const { createClient: createClient5 } = await import('@supabase/supabase-js');
30724
31238
  const supabase = createClient5(
@@ -30744,7 +31258,7 @@ var FileManagerFilters = ({
30744
31258
  method: "POST",
30745
31259
  headers: {
30746
31260
  "Content-Type": "application/json",
30747
- "Authorization": `Bearer ${await getAuthToken4()}`
31261
+ "Authorization": `Bearer ${await getAuthToken5()}`
30748
31262
  },
30749
31263
  body: JSON.stringify({
30750
31264
  action: "percentile-clips",
@@ -32388,7 +32902,7 @@ var BottlenecksContent = ({
32388
32902
  fetchClipCounts();
32389
32903
  }
32390
32904
  }, [workspaceId, effectiveDateString, effectiveShiftId, s3ClipsService, fetchClipCounts, isEffectiveShiftReady]);
32391
- const getAuthToken4 = useCallback(async () => {
32905
+ const getAuthToken5 = useCallback(async () => {
32392
32906
  try {
32393
32907
  const { createClient: createClient5 } = await import('@supabase/supabase-js');
32394
32908
  const supabase = createClient5(
@@ -32407,7 +32921,7 @@ var BottlenecksContent = ({
32407
32921
  const fetchTriageClips = async () => {
32408
32922
  setIsLoadingTriageClips(true);
32409
32923
  try {
32410
- const token = await getAuthToken4();
32924
+ const token = await getAuthToken5();
32411
32925
  if (!token) {
32412
32926
  console.error("[BottlenecksContent] No auth token available");
32413
32927
  return;
@@ -32455,7 +32969,7 @@ var BottlenecksContent = ({
32455
32969
  }
32456
32970
  };
32457
32971
  fetchTriageClips();
32458
- }, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken4, isEffectiveShiftReady]);
32972
+ }, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken5, isEffectiveShiftReady]);
32459
32973
  useEffect(() => {
32460
32974
  if (!triageMode || triageClips.length === 0 || !session?.access_token) {
32461
32975
  return;
@@ -32786,7 +33300,20 @@ var BottlenecksContent = ({
32786
33300
  updateActiveFilter(categoryId);
32787
33301
  }
32788
33302
  try {
32789
- 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;
32790
33317
  let metadataArray = categoryMetadataRef.current;
32791
33318
  const clipExistsInMetadata = metadataArray.some((clip) => clip.clipId === clipId);
32792
33319
  if (metadataArray.length === 0 || !clipExistsInMetadata) {
@@ -32803,16 +33330,7 @@ var BottlenecksContent = ({
32803
33330
  }
32804
33331
  setCurrentMetadataIndex(clickedClipIndex);
32805
33332
  currentMetadataIndexRef.current = clickedClipIndex;
32806
- const video = await s3ClipsService.getClipById(clipId);
32807
- if (video) {
32808
- setPendingVideo(video);
32809
- setCurrentClipId(clipId);
32810
- setAllVideos([video]);
32811
- setCurrentIndex(0);
32812
- console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
32813
- } else {
32814
- throw new Error(`Failed to load video data for clip ${clipId}`);
32815
- }
33333
+ console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
32816
33334
  } catch (error2) {
32817
33335
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
32818
33336
  if (isMountedRef.current) {
@@ -35699,7 +36217,7 @@ var STATIC_COLORS = {
35699
36217
  // red-600 - Critical/Urgent
35700
36218
  "No Material": "#f59e0b",
35701
36219
  // amber-500 - Warning/Supply Chain
35702
- "Machine Maintenance": "#3b82f6",
36220
+ "Machine Downtime": "#3b82f6",
35703
36221
  // blue-500 - Scheduled/Technical
35704
36222
  "Operator Idle": "#8b5cf6"
35705
36223
  // violet-500 - Low Priority/Behavioral
@@ -38084,7 +38602,7 @@ var WorkspaceWhatsAppShareButton = ({
38084
38602
  }
38085
38603
  );
38086
38604
  };
38087
- var WorkspacePdfGenerator = ({ workspace, className }) => {
38605
+ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
38088
38606
  const [isGenerating, setIsGenerating] = useState(false);
38089
38607
  const entityConfig = useEntityConfig();
38090
38608
  const generatePDF = async () => {
@@ -38159,8 +38677,10 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
38159
38677
  doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
38160
38678
  };
38161
38679
  const perfOverviewStartY = 93;
38680
+ const hasIdleTimeReason = idleTimeReasons && idleTimeReasons.length > 0;
38681
+ const perfOverviewHeight = hasIdleTimeReason ? 80 : 70;
38162
38682
  doc.setFillColor(245, 245, 245);
38163
- doc.roundedRect(15, perfOverviewStartY, 180, 60, 3, 3, "F");
38683
+ doc.roundedRect(15, perfOverviewStartY, 180, perfOverviewHeight, 3, 3, "F");
38164
38684
  doc.setFontSize(18);
38165
38685
  doc.setFont("helvetica", "bold");
38166
38686
  doc.setTextColor(40, 40, 40);
@@ -38184,32 +38704,68 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
38184
38704
  doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 2);
38185
38705
  doc.setFont("helvetica", "bold");
38186
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;
38187
38724
  doc.setDrawColor(180, 180, 180);
38188
38725
  doc.setLineWidth(0.8);
38189
- doc.line(20, 163, 190, 163);
38190
- const hourlyPerfStartY = 168;
38726
+ doc.line(20, separatorBeforeHourlyY, 190, separatorBeforeHourlyY);
38727
+ const hourlyPerfStartY = hasIdleTimeReason ? 188 : 178;
38191
38728
  const hourlyData = workspace.hourly_action_counts || [];
38192
38729
  const hourlyTarget = workspace.pph_threshold;
38193
- const tableStartY = 199;
38194
- 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;
38195
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
+ }
38196
38747
  const backgroundHeight = tableStartY - hourlyPerfStartY + hourlyData.length * rowHeight + bottomPadding;
38197
38748
  doc.setFillColor(245, 245, 245);
38198
38749
  doc.roundedRect(15, hourlyPerfStartY, 180, backgroundHeight, 3, 3, "F");
38199
- doc.setFontSize(18);
38750
+ doc.setFontSize(titleFontSize);
38200
38751
  doc.setFont("helvetica", "bold");
38201
38752
  doc.setTextColor(40, 40, 40);
38202
- doc.text("Hourly Performance", 20, 178);
38753
+ const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
38754
+ doc.text("Hourly Performance", 20, hourlyTitleY);
38203
38755
  doc.setTextColor(0, 0, 0);
38204
- doc.setFontSize(11);
38756
+ doc.setFontSize(headerFontSize);
38205
38757
  doc.setFont("helvetica", "bold");
38206
- doc.text("Time Range", 25, 188);
38207
- doc.text("Output", 95, 188);
38208
- doc.text("Target", 135, 188);
38209
- 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);
38210
38764
  doc.setLineWidth(0.3);
38211
38765
  doc.setDrawColor(200, 200, 200);
38212
- doc.line(20, 191, 190, 191);
38766
+ const separatorY = headerY + 3;
38767
+ doc.line(20, separatorY, 190, separatorY);
38768
+ doc.setFontSize(contentFontSize);
38213
38769
  doc.setFont("helvetica", "normal");
38214
38770
  let yPos = tableStartY;
38215
38771
  const workspaceDate = new Date(workspace.date);
@@ -45661,23 +46217,6 @@ function HomeView({
45661
46217
  loading: displayNamesLoading,
45662
46218
  error: displayNamesError
45663
46219
  } = useWorkspaceDisplayNames(displayNameLineId, void 0);
45664
- useCallback(() => {
45665
- console.log("Refetching KPIs after line metrics update");
45666
- }, []);
45667
- const {
45668
- kpis,
45669
- isLoading: kpisLoading,
45670
- error: kpisError,
45671
- refetch: refetchKPIs
45672
- } = useLineKPIs({
45673
- lineId: selectedLineId
45674
- });
45675
- const onLineMetricsUpdate = useCallback(() => {
45676
- const timer = setTimeout(() => {
45677
- refetchKPIs();
45678
- }, 1e3);
45679
- return () => clearTimeout(timer);
45680
- }, [refetchKPIs]);
45681
46220
  const {
45682
46221
  workspaceMetrics,
45683
46222
  lineMetrics,
@@ -45686,10 +46225,22 @@ function HomeView({
45686
46225
  refetch: refetchMetrics
45687
46226
  } = useDashboardMetrics({
45688
46227
  lineId: selectedLineId,
45689
- onLineMetricsUpdate,
45690
46228
  userAccessibleLineIds: allLineIds
45691
46229
  // Pass user's accessible lines for supervisor filtering
45692
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]);
45693
46244
  const {
45694
46245
  activeBreaks: allActiveBreaks,
45695
46246
  isLoading: breaksLoading,
@@ -46008,12 +46559,10 @@ function HomeView({
46008
46559
  useEffect(() => {
46009
46560
  if (metricsError) {
46010
46561
  setErrorMessage(metricsError.message);
46011
- } else if (kpisError) {
46012
- setErrorMessage(kpisError.message);
46013
46562
  } else {
46014
46563
  setErrorMessage(null);
46015
46564
  }
46016
- }, [metricsError, kpisError]);
46565
+ }, [metricsError]);
46017
46566
  const handleLineChange = useCallback((value) => {
46018
46567
  setIsChangingFilter(true);
46019
46568
  setSelectedLineId(value);
@@ -46029,14 +46578,14 @@ function HomeView({
46029
46578
  }
46030
46579
  }, [LINE_FILTER_STORAGE_KEY]);
46031
46580
  useEffect(() => {
46032
- if (!metricsLoading && !kpisLoading && isChangingFilter) {
46581
+ if (!metricsLoading && isChangingFilter) {
46033
46582
  if (workspaceMetrics.length > 0 || selectedLineId === factoryViewId) {
46034
46583
  setIsChangingFilter(false);
46035
46584
  }
46036
46585
  }
46037
- }, [metricsLoading, kpisLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
46586
+ }, [metricsLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
46038
46587
  useEffect(() => {
46039
- if (!metricsLoading && !kpisLoading && !hasInitialDataLoaded) {
46588
+ if (!metricsLoading && !hasInitialDataLoaded) {
46040
46589
  setHasInitialDataLoaded(true);
46041
46590
  trackCoreEvent("Home View Loaded", {
46042
46591
  default_line_id: defaultLineId,
@@ -46044,7 +46593,7 @@ function HomeView({
46044
46593
  is_supervisor: isSupervisor
46045
46594
  });
46046
46595
  }
46047
- }, [metricsLoading, kpisLoading, hasInitialDataLoaded, defaultLineId, factoryViewId, isSupervisor]);
46596
+ }, [metricsLoading, hasInitialDataLoaded, defaultLineId, factoryViewId, isSupervisor]);
46048
46597
  const lineTitle = useMemo(() => {
46049
46598
  return factoryName;
46050
46599
  }, [factoryName]);
@@ -46058,7 +46607,7 @@ function HomeView({
46058
46607
  ] });
46059
46608
  }, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
46060
46609
  const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
46061
- const isDataLoading = metricsLoading || kpisLoading;
46610
+ const isDataLoading = metricsLoading;
46062
46611
  if (isInitialLoading) {
46063
46612
  return /* @__PURE__ */ jsx(LoadingPageCmp, { message: "Loading Dashboard..." });
46064
46613
  }
@@ -46202,7 +46751,7 @@ function withWorkspaceDisplayNames(Component3, options = {}) {
46202
46751
  const lineIdsKey = useMemo(() => {
46203
46752
  if (!props.lineIds) return "";
46204
46753
  if (Array.isArray(props.lineIds)) {
46205
- return props.lineIds.sort().join(",");
46754
+ return props.lineIds.slice().sort().join(",");
46206
46755
  }
46207
46756
  const values = Object.values(props.lineIds).filter(Boolean);
46208
46757
  return values.sort().join(",");
@@ -47325,9 +47874,15 @@ var KPIDetailView = ({
47325
47874
  };
47326
47875
  var KPIDetailViewWithDisplayNames = withSelectedLineDisplayNames(KPIDetailView);
47327
47876
  var KPIDetailView_default = KPIDetailViewWithDisplayNames;
47328
- var LineCard = ({ line, onClick, supervisorEnabled = false }) => {
47329
- const { kpis, isLoading, error } = useLineKPIs({ lineId: line.id });
47330
- const { supervisorName } = useLineSupervisor(line.id);
47877
+ var LineCard = ({
47878
+ line,
47879
+ kpis,
47880
+ isLoading,
47881
+ error,
47882
+ onClick,
47883
+ supervisorEnabled = false,
47884
+ supervisorName
47885
+ }) => {
47331
47886
  const isOnTrack = React24__default.useMemo(() => {
47332
47887
  if (!kpis) return null;
47333
47888
  return kpis.efficiency.value > 90;
@@ -47436,6 +47991,7 @@ var KPIsOverviewView = ({
47436
47991
  const [error, setError] = useState(null);
47437
47992
  const supabase = useSupabase();
47438
47993
  const dashboardConfig = useDashboardConfig();
47994
+ const entityConfig = useEntityConfig();
47439
47995
  const navigation = useNavigation(navigate);
47440
47996
  const dateTimeConfig = useDateTimeConfig();
47441
47997
  const representativeLineId = lineIds?.[0] || lines[0]?.id;
@@ -47443,6 +47999,27 @@ var KPIsOverviewView = ({
47443
47999
  const supervisorEnabled = dashboardConfig?.supervisorConfig?.enabled || false;
47444
48000
  const dbTimezone = useAppTimezone();
47445
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
+ });
47446
48023
  useEffect(() => {
47447
48024
  trackCorePageView("KPIs Overview");
47448
48025
  }, []);
@@ -47660,8 +48237,12 @@ var KPIsOverviewView = ({
47660
48237
  LineCard,
47661
48238
  {
47662
48239
  line,
48240
+ kpis: metricsError ? null : kpisByLineId.get(line.id) ?? (metricsLoading ? null : defaultKPIs),
48241
+ isLoading: metricsLoading,
48242
+ error: metricsError,
47663
48243
  onClick: (kpis) => handleLineClick(line, kpis),
47664
- supervisorEnabled
48244
+ supervisorEnabled,
48245
+ supervisorName: supervisorNamesByLineId.get(line.id) || null
47665
48246
  },
47666
48247
  line.id
47667
48248
  )) }) })
@@ -48485,43 +49066,6 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
48485
49066
  const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
48486
49067
  return Number(hoursDiff.toFixed(1));
48487
49068
  };
48488
- var calculateBreakDuration = (startTime, endTime) => {
48489
- const [startHour, startMinute] = startTime.split(":").map(Number);
48490
- const [endHour, endMinute] = endTime.split(":").map(Number);
48491
- let startMinutes = startHour * 60 + startMinute;
48492
- let endMinutes = endHour * 60 + endMinute;
48493
- if (endMinutes < startMinutes) {
48494
- endMinutes += 24 * 60;
48495
- }
48496
- return endMinutes - startMinutes;
48497
- };
48498
- var parseBreaksFromDB = (dbBreaks) => {
48499
- if (!dbBreaks) return [];
48500
- if (Array.isArray(dbBreaks)) {
48501
- return dbBreaks.map((breakItem) => ({
48502
- startTime: breakItem.start || breakItem.startTime || "00:00",
48503
- endTime: breakItem.end || breakItem.endTime || "00:00",
48504
- duration: calculateBreakDuration(
48505
- breakItem.start || breakItem.startTime || "00:00",
48506
- breakItem.end || breakItem.endTime || "00:00"
48507
- ),
48508
- remarks: breakItem.remarks || breakItem.name || ""
48509
- }));
48510
- } else if (dbBreaks.breaks && Array.isArray(dbBreaks.breaks)) {
48511
- return dbBreaks.breaks.map((breakItem) => ({
48512
- startTime: breakItem.start || breakItem.startTime || "00:00",
48513
- endTime: breakItem.end || breakItem.endTime || "00:00",
48514
- duration: calculateBreakDuration(
48515
- breakItem.start || breakItem.startTime || "00:00",
48516
- breakItem.end || breakItem.endTime || "00:00"
48517
- ),
48518
- remarks: breakItem.remarks || breakItem.name || ""
48519
- }));
48520
- } else {
48521
- console.warn("Unexpected breaks format:", dbBreaks);
48522
- return [];
48523
- }
48524
- };
48525
49069
  var getStoredLineState = (lineId) => {
48526
49070
  try {
48527
49071
  return JSON.parse(localStorage.getItem(`line_${lineId}_open`) || "false");
@@ -48813,16 +49357,9 @@ var ShiftsView = ({
48813
49357
  () => lineIds.map((id3) => ({
48814
49358
  id: id3,
48815
49359
  name: lineNames[id3] || `Line ${id3.substring(0, 4)}`,
48816
- dayShift: {
48817
- startTime: "08:00",
48818
- endTime: "16:00",
48819
- breaks: []
48820
- },
48821
- nightShift: {
48822
- startTime: "20:00",
48823
- endTime: "04:00",
48824
- breaks: []
48825
- },
49360
+ timezone: "Asia/Kolkata",
49361
+ shifts: [],
49362
+ // Will be populated from DB
48826
49363
  isOpen: true,
48827
49364
  isSaving: false,
48828
49365
  saveSuccess: false
@@ -48860,58 +49397,37 @@ var ShiftsView = ({
48860
49397
  setLoading(false);
48861
49398
  return;
48862
49399
  }
48863
- const { data: dayShiftOperatingHours, error: dayShiftError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 0).in("line_id", enabledLineIds);
48864
- if (dayShiftError) {
48865
- console.error("Error fetching day shift operating hours:", dayShiftError);
48866
- showToast("error", "Error loading day shift data");
48867
- setError("Failed to load day shift data");
48868
- return;
48869
- }
48870
- const { data: nightShiftOperatingHours, error: nightShiftError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 1).in("line_id", enabledLineIds);
48871
- if (nightShiftError) {
48872
- console.error("Error fetching night shift operating hours:", nightShiftError);
48873
- showToast("error", "Error loading night shift data");
48874
- setError("Failed to load night shift data");
49400
+ const { data: allShifts, error: shiftsError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").in("line_id", enabledLineIds);
49401
+ if (shiftsError) {
49402
+ console.error("Error fetching shifts:", shiftsError);
49403
+ showToast("error", "Error loading shift data");
49404
+ setError("Failed to load shift data");
48875
49405
  return;
48876
49406
  }
48877
- const dayShiftHoursMap = (dayShiftOperatingHours || []).reduce((map, item) => {
48878
- map[item.line_id] = {
48879
- startTime: item.start_time,
48880
- endTime: item.end_time,
48881
- breaks: parseBreaksFromDB(item.breaks)
48882
- };
48883
- return map;
49407
+ const shiftsByLine = (allShifts || []).reduce((acc, row) => {
49408
+ if (!acc[row.line_id]) acc[row.line_id] = [];
49409
+ acc[row.line_id].push(row);
49410
+ return acc;
48884
49411
  }, {});
48885
- const nightShiftHoursMap = (nightShiftOperatingHours || []).reduce((map, item) => {
48886
- map[item.line_id] = {
48887
- startTime: item.start_time,
48888
- endTime: item.end_time,
48889
- breaks: parseBreaksFromDB(item.breaks)
48890
- };
48891
- return map;
49412
+ const { data: linesData } = await supabase.from("lines").select("id, line_name").in("id", enabledLineIds);
49413
+ const lineNameMap = (linesData || []).reduce((acc, line) => {
49414
+ if (line.line_name) acc[line.id] = line.line_name;
49415
+ return acc;
48892
49416
  }, {});
48893
49417
  setLineConfigs((prev) => {
48894
49418
  const enabledConfigs = prev.filter((config) => enabledLineIds.includes(config.id));
48895
49419
  return enabledConfigs.map((config) => {
48896
- const typedConfig = config;
48897
- const lineId = typedConfig.id;
48898
- const newConfig = { ...typedConfig };
48899
- if (dayShiftHoursMap[lineId]) {
48900
- newConfig.dayShift = {
48901
- ...newConfig.dayShift,
48902
- ...dayShiftHoursMap[lineId]
48903
- };
48904
- }
48905
- if (nightShiftHoursMap[lineId]) {
48906
- newConfig.nightShift = {
48907
- ...newConfig.nightShift,
48908
- ...nightShiftHoursMap[lineId]
48909
- };
48910
- }
48911
- if (newConfig.isOpen === void 0) {
48912
- newConfig.isOpen = getStoredLineState(lineId);
48913
- }
48914
- return newConfig;
49420
+ const rows = shiftsByLine[config.id] || [];
49421
+ const builtConfig = buildShiftConfigFromOperatingHoursRows(rows);
49422
+ const lineName = lineNameMap[config.id] || lineNames[config.id] || `Line ${config.id.substring(0, 4)}`;
49423
+ const sortedShifts = [...builtConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
49424
+ return {
49425
+ ...config,
49426
+ name: lineName,
49427
+ shifts: sortedShifts,
49428
+ timezone: builtConfig.timezone || config.timezone,
49429
+ isOpen: config.isOpen ?? getStoredLineState(config.id)
49430
+ };
48915
49431
  });
48916
49432
  });
48917
49433
  setLoading(false);
@@ -48934,183 +49450,79 @@ var ShiftsView = ({
48934
49450
  );
48935
49451
  });
48936
49452
  }, []);
48937
- const updateDayShiftStartTime = useCallback((lineId, value) => {
48938
- setLineConfigs((prev) => prev.map((config) => {
48939
- const typedConfig = config;
48940
- if (typedConfig.id === lineId) {
48941
- const updatedDayShift = { ...typedConfig.dayShift, startTime: value };
48942
- return {
48943
- ...typedConfig,
48944
- dayShift: updatedDayShift
48945
- };
48946
- }
48947
- return typedConfig;
48948
- }));
48949
- }, []);
48950
- const updateDayShiftEndTime = useCallback((lineId, value) => {
48951
- setLineConfigs((prev) => prev.map((config) => {
48952
- const typedConfig = config;
48953
- if (typedConfig.id === lineId) {
48954
- const updatedDayShift = { ...typedConfig.dayShift, endTime: value };
48955
- return {
48956
- ...typedConfig,
48957
- dayShift: updatedDayShift
48958
- };
48959
- }
48960
- return typedConfig;
48961
- }));
48962
- }, []);
48963
- const updateNightShiftStartTime = useCallback((lineId, value) => {
49453
+ const updateShiftTime = useCallback((lineId, shiftIndex, field, value) => {
48964
49454
  setLineConfigs((prev) => prev.map((config) => {
48965
- const typedConfig = config;
48966
- if (typedConfig.id === lineId) {
48967
- const updatedNightShift = { ...typedConfig.nightShift, startTime: value };
48968
- return {
48969
- ...typedConfig,
48970
- nightShift: updatedNightShift
48971
- };
48972
- }
48973
- return typedConfig;
48974
- }));
48975
- }, []);
48976
- const updateNightShiftEndTime = useCallback((lineId, value) => {
48977
- setLineConfigs((prev) => prev.map((config) => {
48978
- const typedConfig = config;
48979
- if (typedConfig.id === lineId) {
48980
- const updatedNightShift = { ...typedConfig.nightShift, endTime: value };
48981
- return {
48982
- ...typedConfig,
48983
- nightShift: updatedNightShift
48984
- };
48985
- }
48986
- return typedConfig;
48987
- }));
48988
- }, []);
48989
- const addDayShiftBreak = useCallback((lineId) => {
48990
- setLineConfigs((prev) => prev.map((config) => {
48991
- const typedConfig = config;
48992
- if (typedConfig.id === lineId) {
48993
- const dayShift = { ...typedConfig.dayShift };
48994
- const newBreak = {
48995
- startTime: dayShift.startTime,
48996
- endTime: dayShift.startTime,
48997
- duration: 0,
48998
- remarks: ""
48999
- };
49000
- return {
49001
- ...typedConfig,
49002
- dayShift: {
49003
- ...dayShift,
49004
- breaks: [...dayShift.breaks, newBreak]
49005
- }
49006
- };
49007
- }
49008
- return typedConfig;
49009
- }));
49010
- }, []);
49011
- const addNightShiftBreak = useCallback((lineId) => {
49012
- setLineConfigs((prev) => prev.map((config) => {
49013
- const typedConfig = config;
49014
- if (typedConfig.id === lineId) {
49015
- const nightShift = { ...typedConfig.nightShift };
49016
- const newBreak = {
49017
- startTime: nightShift.startTime,
49018
- endTime: nightShift.startTime,
49019
- duration: 0
49020
- };
49021
- return {
49022
- ...typedConfig,
49023
- nightShift: {
49024
- ...nightShift,
49025
- breaks: [...nightShift.breaks, newBreak]
49026
- }
49027
- };
49028
- }
49029
- return typedConfig;
49030
- }));
49031
- }, []);
49032
- const updateDayShiftBreak = useCallback((lineId, index, field, value) => {
49033
- setLineConfigs((prev) => prev.map((config) => {
49034
- const typedConfig = config;
49035
- if (typedConfig.id === lineId) {
49036
- const dayShift = { ...typedConfig.dayShift };
49037
- const newBreaks = [...dayShift.breaks];
49038
- newBreaks[index] = { ...newBreaks[index], [field]: value };
49039
- if (field === "startTime" || field === "endTime") {
49040
- const startParts = newBreaks[index].startTime.split(":").map(Number);
49041
- const endParts = newBreaks[index].endTime.split(":").map(Number);
49042
- let startMinutes = startParts[0] * 60 + startParts[1];
49043
- let endMinutes = endParts[0] * 60 + endParts[1];
49044
- if (endMinutes < startMinutes) {
49045
- endMinutes += 24 * 60;
49046
- }
49047
- newBreaks[index].duration = endMinutes - startMinutes;
49455
+ if (config.id === lineId && config.shifts) {
49456
+ const newShifts = [...config.shifts];
49457
+ if (newShifts[shiftIndex]) {
49458
+ newShifts[shiftIndex] = { ...newShifts[shiftIndex], [field]: value };
49048
49459
  }
49049
- return {
49050
- ...typedConfig,
49051
- dayShift: {
49052
- ...dayShift,
49053
- breaks: newBreaks
49054
- }
49055
- };
49460
+ return { ...config, shifts: newShifts };
49056
49461
  }
49057
- return typedConfig;
49462
+ return config;
49058
49463
  }));
49059
49464
  }, []);
49060
- const updateNightShiftBreak = useCallback((lineId, index, field, value) => {
49465
+ const addShiftBreak = useCallback((lineId, shiftIndex) => {
49061
49466
  setLineConfigs((prev) => prev.map((config) => {
49062
- const typedConfig = config;
49063
- if (typedConfig.id === lineId) {
49064
- const nightShift = { ...typedConfig.nightShift };
49065
- const newBreaks = [...nightShift.breaks];
49066
- newBreaks[index] = { ...newBreaks[index], [field]: value };
49067
- if (field === "startTime" || field === "endTime") {
49068
- const startParts = newBreaks[index].startTime.split(":").map(Number);
49069
- const endParts = newBreaks[index].endTime.split(":").map(Number);
49070
- let startMinutes = startParts[0] * 60 + startParts[1];
49071
- let endMinutes = endParts[0] * 60 + endParts[1];
49072
- if (endMinutes < startMinutes) {
49073
- endMinutes += 24 * 60;
49074
- }
49075
- newBreaks[index].duration = endMinutes - startMinutes;
49467
+ if (config.id === lineId && config.shifts) {
49468
+ const newShifts = [...config.shifts];
49469
+ if (newShifts[shiftIndex]) {
49470
+ const shift = newShifts[shiftIndex];
49471
+ const newBreak = {
49472
+ startTime: shift.startTime,
49473
+ endTime: shift.startTime,
49474
+ duration: 0,
49475
+ remarks: ""
49476
+ };
49477
+ newShifts[shiftIndex] = {
49478
+ ...shift,
49479
+ breaks: [...shift.breaks || [], newBreak]
49480
+ };
49076
49481
  }
49077
- return {
49078
- ...typedConfig,
49079
- nightShift: {
49080
- ...nightShift,
49081
- breaks: newBreaks
49082
- }
49083
- };
49482
+ return { ...config, shifts: newShifts };
49084
49483
  }
49085
- return typedConfig;
49484
+ return config;
49086
49485
  }));
49087
49486
  }, []);
49088
- const removeDayShiftBreak = useCallback((lineId, index) => {
49487
+ const updateShiftBreak = useCallback((lineId, shiftIndex, breakIndex, field, value) => {
49089
49488
  setLineConfigs((prev) => prev.map((config) => {
49090
- if (config.id === lineId) {
49091
- const dayShift = { ...config.dayShift };
49092
- return {
49093
- ...config,
49094
- dayShift: {
49095
- ...dayShift,
49096
- breaks: dayShift.breaks.filter((_, i) => i !== index)
49489
+ if (config.id === lineId && config.shifts) {
49490
+ const newShifts = [...config.shifts];
49491
+ if (newShifts[shiftIndex]) {
49492
+ const shift = newShifts[shiftIndex];
49493
+ const newBreaks = [...shift.breaks || []];
49494
+ if (newBreaks[breakIndex]) {
49495
+ newBreaks[breakIndex] = { ...newBreaks[breakIndex], [field]: value };
49496
+ if (field === "startTime" || field === "endTime") {
49497
+ const startParts = newBreaks[breakIndex].startTime.split(":").map(Number);
49498
+ const endParts = newBreaks[breakIndex].endTime.split(":").map(Number);
49499
+ let startMinutes = startParts[0] * 60 + startParts[1];
49500
+ let endMinutes = endParts[0] * 60 + endParts[1];
49501
+ if (endMinutes < startMinutes) {
49502
+ endMinutes += 24 * 60;
49503
+ }
49504
+ newBreaks[breakIndex].duration = endMinutes - startMinutes;
49505
+ }
49097
49506
  }
49098
- };
49507
+ newShifts[shiftIndex] = { ...shift, breaks: newBreaks };
49508
+ }
49509
+ return { ...config, shifts: newShifts };
49099
49510
  }
49100
49511
  return config;
49101
49512
  }));
49102
49513
  }, []);
49103
- const removeNightShiftBreak = useCallback((lineId, index) => {
49514
+ const removeShiftBreak = useCallback((lineId, shiftIndex, breakIndex) => {
49104
49515
  setLineConfigs((prev) => prev.map((config) => {
49105
- if (config.id === lineId) {
49106
- const nightShift = { ...config.nightShift };
49107
- return {
49108
- ...config,
49109
- nightShift: {
49110
- ...nightShift,
49111
- breaks: nightShift.breaks.filter((_, i) => i !== index)
49112
- }
49113
- };
49516
+ if (config.id === lineId && config.shifts) {
49517
+ const newShifts = [...config.shifts];
49518
+ if (newShifts[shiftIndex]) {
49519
+ const shift = newShifts[shiftIndex];
49520
+ newShifts[shiftIndex] = {
49521
+ ...shift,
49522
+ breaks: (shift.breaks || []).filter((_, i) => i !== breakIndex)
49523
+ };
49524
+ }
49525
+ return { ...config, shifts: newShifts };
49114
49526
  }
49115
49527
  return config;
49116
49528
  }));
@@ -49124,29 +49536,26 @@ var ShiftsView = ({
49124
49536
  if (!lineConfig) {
49125
49537
  throw new Error("Line configuration not found");
49126
49538
  }
49127
- const dayShiftData = {
49128
- line_id: lineId,
49129
- shift_id: 0,
49130
- shift_name: "Day Shift",
49131
- start_time: lineConfig.dayShift.startTime,
49132
- end_time: lineConfig.dayShift.endTime,
49133
- breaks: formatBreaks(lineConfig.dayShift.breaks)
49134
- };
49135
- const nightShiftData = {
49136
- line_id: lineId,
49137
- shift_id: 1,
49138
- shift_name: "Night Shift",
49139
- start_time: lineConfig.nightShift.startTime,
49140
- end_time: lineConfig.nightShift.endTime,
49141
- breaks: formatBreaks(lineConfig.nightShift.breaks)
49142
- };
49143
- const dayResult = await supabase.from("line_operating_hours").upsert(dayShiftData).select();
49144
- if (dayResult.error) {
49145
- throw new Error(`Failed to save day shift: ${dayResult.error.message}`);
49539
+ const allSavedRows = [];
49540
+ for (const shift of lineConfig.shifts || []) {
49541
+ const shiftData = {
49542
+ line_id: lineId,
49543
+ shift_id: shift.shiftId,
49544
+ shift_name: shift.shiftName,
49545
+ start_time: shift.startTime,
49546
+ end_time: shift.endTime,
49547
+ breaks: formatBreaks(shift.breaks || [])
49548
+ };
49549
+ const { data, error: error2 } = await supabase.from("line_operating_hours").upsert(shiftData).select();
49550
+ if (error2) {
49551
+ throw new Error(`Failed to save shift ${shift.shiftName}: ${error2.message}`);
49552
+ }
49553
+ if (data) {
49554
+ allSavedRows.push(...data);
49555
+ }
49146
49556
  }
49147
- const nightResult = await supabase.from("line_operating_hours").upsert(nightShiftData).select();
49148
- if (nightResult.error) {
49149
- throw new Error(`Failed to save night shift: ${nightResult.error.message}`);
49557
+ if (allSavedRows.length > 0) {
49558
+ shiftConfigStore.setFromOperatingHoursRows(lineId, allSavedRows, shiftConfigStore.get(lineId));
49150
49559
  }
49151
49560
  setLineConfigs((prev) => prev.map(
49152
49561
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
@@ -49225,48 +49634,34 @@ var ShiftsView = ({
49225
49634
  )
49226
49635
  ] })
49227
49636
  ] }) }),
49228
- /* @__PURE__ */ jsxs("div", { id: `shift-panel-${config.id}`, className: "p-3 sm:p-4 md:p-6 border-t border-gray-200 w-full", children: [
49229
- /* @__PURE__ */ jsx(
49230
- ShiftPanel,
49231
- {
49232
- title: "Day Shift",
49233
- icon: /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 text-gray-600", 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" }) }),
49234
- startTime: config.dayShift.startTime,
49235
- endTime: config.dayShift.endTime,
49236
- breaks: config.dayShift.breaks,
49237
- onStartTimeChange: (value) => updateDayShiftStartTime(config.id, value),
49238
- onEndTimeChange: (value) => updateDayShiftEndTime(config.id, value),
49239
- onBreakUpdate: (index, field, value) => updateDayShiftBreak(config.id, index, field, value),
49240
- onBreakRemove: (index) => removeDayShiftBreak(config.id, index),
49241
- onBreakAdd: () => addDayShiftBreak(config.id),
49242
- shiftHours: calculateShiftHours(
49243
- config.dayShift.startTime,
49244
- config.dayShift.endTime,
49245
- config.dayShift.breaks
49246
- )
49247
- }
49248
- ),
49249
- /* @__PURE__ */ jsx(
49250
- ShiftPanel,
49251
- {
49252
- title: "Night Shift",
49253
- icon: /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 text-gray-600", 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" }) }),
49254
- startTime: config.nightShift.startTime,
49255
- endTime: config.nightShift.endTime,
49256
- breaks: config.nightShift.breaks,
49257
- onStartTimeChange: (value) => updateNightShiftStartTime(config.id, value),
49258
- onEndTimeChange: (value) => updateNightShiftEndTime(config.id, value),
49259
- onBreakUpdate: (index, field, value) => updateNightShiftBreak(config.id, index, field, value),
49260
- onBreakRemove: (index) => removeNightShiftBreak(config.id, index),
49261
- onBreakAdd: () => addNightShiftBreak(config.id),
49262
- shiftHours: calculateShiftHours(
49263
- config.nightShift.startTime,
49264
- config.nightShift.endTime,
49265
- config.nightShift.breaks
49266
- )
49267
- }
49268
- )
49269
- ] })
49637
+ /* @__PURE__ */ jsx("div", { id: `shift-panel-${config.id}`, className: "p-3 sm:p-4 md:p-6 border-t border-gray-200 w-full", children: config.shifts && config.shifts.length > 0 ? config.shifts.map((shift, shiftIndex) => /* @__PURE__ */ jsx(
49638
+ ShiftPanel,
49639
+ {
49640
+ title: shift.shiftName,
49641
+ icon: (
49642
+ // Icon based on shift name (case-insensitive)
49643
+ shift.shiftName.toLowerCase().includes("day") ? /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 text-gray-600", 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" }) }) : shift.shiftName.toLowerCase().includes("night") ? /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 text-gray-600", 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" }) }) : /* @__PURE__ */ jsx(Clock, { className: "w-5 h-5 text-gray-600" })
49644
+ ),
49645
+ startTime: shift.startTime,
49646
+ endTime: shift.endTime,
49647
+ breaks: shift.breaks || [],
49648
+ onStartTimeChange: (value) => updateShiftTime(config.id, shiftIndex, "startTime", value),
49649
+ onEndTimeChange: (value) => updateShiftTime(config.id, shiftIndex, "endTime", value),
49650
+ onBreakUpdate: (breakIndex, field, value) => updateShiftBreak(config.id, shiftIndex, breakIndex, field, value),
49651
+ onBreakRemove: (breakIndex) => removeShiftBreak(config.id, shiftIndex, breakIndex),
49652
+ onBreakAdd: () => addShiftBreak(config.id, shiftIndex),
49653
+ shiftHours: calculateShiftHours(
49654
+ shift.startTime,
49655
+ shift.endTime,
49656
+ shift.breaks || []
49657
+ )
49658
+ },
49659
+ shift.shiftId
49660
+ )) : /* @__PURE__ */ jsxs("div", { className: "text-center text-gray-500 py-8", children: [
49661
+ /* @__PURE__ */ jsx(Clock, { className: "w-12 h-12 mx-auto mb-3 text-gray-400" }),
49662
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: "No shifts configured for this line" }),
49663
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 mt-1", children: "Shifts will appear here once configured in the database" })
49664
+ ] }) })
49270
49665
  ] }, config.id)) })
49271
49666
  ] })
49272
49667
  ] });
@@ -50117,6 +50512,7 @@ var TargetsViewUI = ({
50117
50512
  onUpdateSelectedSKU,
50118
50513
  skuRequired = false
50119
50514
  }) => {
50515
+ const { displayNames: workspaceDisplayNames } = useWorkspaceDisplayNames();
50120
50516
  if (isLoading) {
50121
50517
  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..." }) }) });
50122
50518
  }
@@ -50264,7 +50660,7 @@ var TargetsViewUI = ({
50264
50660
  ] })
50265
50661
  ] }) }),
50266
50662
  /* @__PURE__ */ jsx("div", { className: "divide-y divide-gray-100", children: line.workspaces.map((workspace) => {
50267
- const formattedName = formatWorkspaceName(workspace.name, lineId);
50663
+ const formattedName = workspaceDisplayNames[`${lineId}_${workspace.name}`] || formatWorkspaceName(workspace.name, lineId);
50268
50664
  return /* @__PURE__ */ jsxs(
50269
50665
  "div",
50270
50666
  {
@@ -50988,8 +51384,17 @@ var TargetsView = ({
50988
51384
  };
50989
51385
  const handleUpdateWorkspaceDisplayName = useCallback(async (workspaceId, displayName) => {
50990
51386
  try {
50991
- await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
50992
- await forceRefreshWorkspaceDisplayNames();
51387
+ const updated = await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
51388
+ if (updated?.line_id && updated?.workspace_id) {
51389
+ upsertWorkspaceDisplayNameInCache({
51390
+ lineId: updated.line_id,
51391
+ workspaceId: updated.workspace_id,
51392
+ displayName: updated?.display_name || displayName,
51393
+ enabled: updated?.enable
51394
+ });
51395
+ } else {
51396
+ await forceRefreshWorkspaceDisplayNames();
51397
+ }
50993
51398
  toast.success("Workspace name updated successfully");
50994
51399
  } catch (error) {
50995
51400
  console.error("Error updating workspace display name:", error);
@@ -51668,7 +52073,13 @@ var WorkspaceDetailView = ({
51668
52073
  }
51669
52074
  )
51670
52075
  ] }),
51671
- activeTab === "overview" && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsx(WorkspacePdfGenerator, { workspace }) }),
52076
+ activeTab === "overview" && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsx(
52077
+ WorkspacePdfGenerator,
52078
+ {
52079
+ workspace,
52080
+ idleTimeReasons: idleTimeChartData
52081
+ }
52082
+ ) }),
51672
52083
  activeTab === "monthly_history" && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: /* @__PURE__ */ jsx(
51673
52084
  WorkspaceMonthlyPdfGenerator,
51674
52085
  {
@@ -52723,6 +53134,7 @@ var WorkspaceHealthView = ({
52723
53134
  const [selectedWorkspace, setSelectedWorkspace] = useState(null);
52724
53135
  const [selectedDate, setSelectedDate] = useState(void 0);
52725
53136
  const [selectedShiftId, setSelectedShiftId] = useState(void 0);
53137
+ const [showDatePicker, setShowDatePicker] = useState(false);
52726
53138
  const effectiveTimezone = timezone || "UTC";
52727
53139
  const currentShiftDetails = getCurrentShift(effectiveTimezone, shiftConfig);
52728
53140
  const operationalDate = currentShiftDetails.date;
@@ -52741,8 +53153,9 @@ var WorkspaceHealthView = ({
52741
53153
  loading,
52742
53154
  error,
52743
53155
  refetch,
52744
- shiftConfig: loadedShiftConfig
53156
+ shiftConfig: loadedShiftConfig,
52745
53157
  // Get shift config from the hook to pass to modal
53158
+ shiftConfigMap
52746
53159
  } = useWorkspaceHealth({
52747
53160
  lineId: effectiveLineIdForFetch,
52748
53161
  // undefined in factory view = fetch all lines
@@ -52754,6 +53167,13 @@ var WorkspaceHealthView = ({
52754
53167
  date: selectedDate,
52755
53168
  shiftId: selectedShiftId
52756
53169
  });
53170
+ const modalShiftConfig = useMemo(() => {
53171
+ if (!selectedWorkspace) return void 0;
53172
+ if (isFactoryView) {
53173
+ return shiftConfigMap.get(selectedWorkspace.line_id);
53174
+ }
53175
+ return loadedShiftConfig || void 0;
53176
+ }, [isFactoryView, loadedShiftConfig, selectedWorkspace, shiftConfigMap]);
52757
53177
  const handleWorkspaceClick = useCallback(
52758
53178
  (workspace) => {
52759
53179
  const url = `/workspace/${workspace.workspace_id}`;
@@ -52771,28 +53191,6 @@ var WorkspaceHealthView = ({
52771
53191
  const handleCloseDetails = useCallback(() => {
52772
53192
  setSelectedWorkspace(null);
52773
53193
  }, []);
52774
- const handleExport = useCallback(() => {
52775
- const csv = [
52776
- ["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
52777
- ...workspaces.map((w) => [
52778
- w.workspace_display_name || "",
52779
- w.line_name || "",
52780
- w.company_name || "",
52781
- w.status,
52782
- w.last_heartbeat,
52783
- w.consecutive_misses?.toString() || "0"
52784
- ])
52785
- ].map((row) => row.join(",")).join("\n");
52786
- const blob = new Blob([csv], { type: "text/csv" });
52787
- const url = window.URL.createObjectURL(blob);
52788
- const a = document.createElement("a");
52789
- a.href = url;
52790
- a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
52791
- document.body.appendChild(a);
52792
- a.click();
52793
- document.body.removeChild(a);
52794
- window.URL.revokeObjectURL(url);
52795
- }, [workspaces]);
52796
53194
  const getStatusIcon = (status) => {
52797
53195
  switch (status) {
52798
53196
  case "healthy":
@@ -52830,104 +53228,42 @@ var WorkspaceHealthView = ({
52830
53228
  }
52831
53229
  return /* @__PURE__ */ jsxs(Fragment, { children: [
52832
53230
  /* @__PURE__ */ jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
52833
- /* @__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: [
52834
- /* @__PURE__ */ jsxs("div", { className: "sm:hidden", children: [
52835
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
52836
- /* @__PURE__ */ jsx(
52837
- BackButtonMinimal,
52838
- {
52839
- onClick: () => router.push("/"),
52840
- text: "Back",
52841
- size: "sm",
52842
- "aria-label": "Navigate back to dashboard"
52843
- }
52844
- ),
52845
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
52846
- /* @__PURE__ */ jsx(
52847
- "button",
52848
- {
52849
- onClick: () => {
52850
- refetch();
52851
- },
52852
- className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52853
- "aria-label": "Refresh",
52854
- children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" })
52855
- }
52856
- ),
52857
- /* @__PURE__ */ jsx(
52858
- "button",
52859
- {
52860
- onClick: handleExport,
52861
- className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52862
- "aria-label": "Export CSV",
52863
- children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" })
52864
- }
52865
- )
52866
- ] })
52867
- ] }),
52868
- /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
52869
- /* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900", children: "System Health" }),
52870
- /* @__PURE__ */ jsxs("div", { className: "relative flex h-2 w-2", children: [
52871
- /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
52872
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-emerald-500" })
52873
- ] })
52874
- ] }) })
53231
+ /* @__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: [
53232
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx(
53233
+ BackButtonMinimal,
53234
+ {
53235
+ onClick: () => router.push("/"),
53236
+ text: "Back",
53237
+ size: "default",
53238
+ "aria-label": "Navigate back to dashboard"
53239
+ }
53240
+ ) }),
53241
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
53242
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "System Health" }),
53243
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-1 text-center", children: "Monitor workspace connectivity and operational status" })
52875
53244
  ] }),
52876
- /* @__PURE__ */ jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
52877
- /* @__PURE__ */ jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsx(
52878
- BackButtonMinimal,
53245
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
53246
+ /* @__PURE__ */ jsx(
53247
+ "button",
52879
53248
  {
52880
- onClick: () => router.push("/"),
52881
- text: "Back",
52882
- size: "default",
52883
- "aria-label": "Navigate back to dashboard"
53249
+ onClick: () => setShowDatePicker(!showDatePicker),
53250
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
53251
+ "aria-label": "Select date",
53252
+ children: /* @__PURE__ */ jsx(Calendar, { className: "h-5 w-5" })
52884
53253
  }
52885
- ) }),
52886
- /* @__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: [
52887
- /* @__PURE__ */ jsx("h1", { className: "text-lg md:text-xl lg:text-2xl xl:text-3xl font-semibold text-gray-900 truncate", children: "System Health" }),
52888
- /* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
52889
- /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
52890
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
52891
- ] })
52892
- ] }) }),
52893
- /* @__PURE__ */ jsxs("div", { className: "absolute right-0 flex gap-2", children: [
52894
- /* @__PURE__ */ jsx(
52895
- "button",
52896
- {
52897
- onClick: () => {
52898
- refetch();
52899
- },
52900
- className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52901
- "aria-label": "Refresh",
52902
- children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-5 w-5" })
52903
- }
52904
- ),
52905
- /* @__PURE__ */ jsx(
52906
- "button",
52907
- {
52908
- onClick: handleExport,
52909
- className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52910
- "aria-label": "Export CSV",
52911
- children: /* @__PURE__ */ jsx(Download, { className: "h-5 w-5" })
52912
- }
52913
- )
52914
- ] }),
52915
- /* @__PURE__ */ jsx("div", { className: "w-full h-8" })
52916
- ] }) }),
52917
- /* @__PURE__ */ jsx("div", { className: "mt-1 sm:mt-2", children: /* @__PURE__ */ jsx(
52918
- HealthDateShiftSelector,
52919
- {
52920
- selectedDate: selectedDate || operationalDate,
52921
- selectedShiftId: selectedShiftId ?? currentShiftDetails.shiftId,
52922
- shiftConfig,
52923
- timezone: effectiveTimezone,
52924
- onDateChange: setSelectedDate,
52925
- onShiftChange: setSelectedShiftId,
52926
- isCurrentShift: isViewingCurrentShift,
52927
- onReturnToLive: handleReturnToLive
52928
- }
52929
- ) })
52930
- ] }),
53254
+ ),
53255
+ /* @__PURE__ */ jsx(
53256
+ "button",
53257
+ {
53258
+ onClick: () => refetch(),
53259
+ disabled: loading,
53260
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
53261
+ "aria-label": "Refresh",
53262
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: `h-5 w-5 ${loading ? "animate-spin" : ""}` })
53263
+ }
53264
+ )
53265
+ ] })
53266
+ ] }) }) }),
52931
53267
  /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
52932
53268
  summary && /* @__PURE__ */ jsxs(
52933
53269
  motion.div,
@@ -53003,13 +53339,79 @@ var WorkspaceHealthView = ({
53003
53339
  )
53004
53340
  ] })
53005
53341
  ] }),
53342
+ showDatePicker && /* @__PURE__ */ jsxs(Fragment, { children: [
53343
+ /* @__PURE__ */ jsx(
53344
+ "div",
53345
+ {
53346
+ className: "fixed inset-0 bg-black/20 z-40",
53347
+ onClick: () => setShowDatePicker(false)
53348
+ }
53349
+ ),
53350
+ /* @__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: [
53351
+ /* @__PURE__ */ jsxs("div", { children: [
53352
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Date" }),
53353
+ /* @__PURE__ */ jsx(
53354
+ "input",
53355
+ {
53356
+ type: "date",
53357
+ value: selectedDate || operationalDate,
53358
+ max: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
53359
+ onChange: (e) => {
53360
+ setSelectedDate(e.target.value);
53361
+ },
53362
+ 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"
53363
+ }
53364
+ )
53365
+ ] }),
53366
+ /* @__PURE__ */ jsxs("div", { children: [
53367
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Shift" }),
53368
+ /* @__PURE__ */ jsx(
53369
+ "select",
53370
+ {
53371
+ value: selectedShiftId ?? currentShiftDetails.shiftId,
53372
+ onChange: (e) => setSelectedShiftId(Number(e.target.value)),
53373
+ 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",
53374
+ children: (shiftConfig?.shifts || []).map((shift) => /* @__PURE__ */ jsxs("option", { value: shift.shiftId, children: [
53375
+ shift.shiftName,
53376
+ " (",
53377
+ shift.startTime,
53378
+ " - ",
53379
+ shift.endTime,
53380
+ ")"
53381
+ ] }, shift.shiftId))
53382
+ }
53383
+ )
53384
+ ] }),
53385
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-2", children: [
53386
+ !isViewingCurrentShift && /* @__PURE__ */ jsx(
53387
+ "button",
53388
+ {
53389
+ onClick: () => {
53390
+ handleReturnToLive();
53391
+ setShowDatePicker(false);
53392
+ },
53393
+ className: "flex-1 px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors",
53394
+ children: "Return to Live"
53395
+ }
53396
+ ),
53397
+ /* @__PURE__ */ jsx(
53398
+ "button",
53399
+ {
53400
+ onClick: () => setShowDatePicker(false),
53401
+ className: "flex-1 px-3 py-2 text-sm text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors",
53402
+ children: "Done"
53403
+ }
53404
+ )
53405
+ ] })
53406
+ ] }) })
53407
+ ] }),
53006
53408
  /* @__PURE__ */ jsx(
53007
53409
  WorkspaceUptimeDetailModal_default,
53008
53410
  {
53009
53411
  workspace: selectedWorkspace,
53010
53412
  isOpen: Boolean(selectedWorkspace),
53011
53413
  onClose: handleCloseDetails,
53012
- shiftConfig: loadedShiftConfig,
53414
+ shiftConfig: modalShiftConfig,
53013
53415
  date: selectedDate,
53014
53416
  shiftId: selectedShiftId
53015
53417
  }
@@ -54152,7 +54554,7 @@ function DailyBarChart({
54152
54554
  axisLine: false,
54153
54555
  tick: (props) => {
54154
54556
  const { x, y, payload } = props;
54155
- if (payload.value === 0) return null;
54557
+ if (payload.value === 0) return /* @__PURE__ */ jsx("g", {});
54156
54558
  const hours = Math.round(payload.value / (1e3 * 60 * 60) * 10) / 10;
54157
54559
  return /* @__PURE__ */ jsxs("text", { x, y, dy: 4, textAnchor: "end", className: "text-xs fill-gray-400", children: [
54158
54560
  hours,
@@ -54507,7 +54909,7 @@ var UserManagementTable = ({
54507
54909
  }
54508
54910
  ),
54509
54911
  /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Assignments" }),
54510
- showUsageStats && /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Weekly usage" }),
54912
+ 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" }),
54511
54913
  /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Actions" })
54512
54914
  ] }) }),
54513
54915
  /* @__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: [
@@ -54519,12 +54921,25 @@ var UserManagementTable = ({
54519
54921
  const canChangeRole = permissions.canChangeRole(user);
54520
54922
  const canRemove = permissions.canRemoveUser(user);
54521
54923
  const hasActions = canChangeRole || canRemove;
54924
+ const canShowUsageModal = showUsageStats && (user.role_level === "plant_head" || user.role_level === "supervisor");
54925
+ const handleRowClick = (e) => {
54926
+ const target = e.target;
54927
+ if (target.closest("button") || target.closest("select") || target.closest("input") || target.closest('[role="button"]') || target.closest("[data-interactive]")) {
54928
+ return;
54929
+ }
54930
+ if (canShowUsageModal) {
54931
+ setUsageDetailUserId(user.user_id);
54932
+ setShowUsageDetailModal(true);
54933
+ }
54934
+ };
54522
54935
  return /* @__PURE__ */ jsxs(
54523
54936
  "tr",
54524
54937
  {
54938
+ onClick: handleRowClick,
54525
54939
  className: cn(
54526
- "hover:bg-gray-50 transition-colors",
54527
- isDeactivated && "opacity-60"
54940
+ "transition-all duration-150",
54941
+ isDeactivated && "opacity-60",
54942
+ canShowUsageModal ? "cursor-pointer hover:bg-blue-50/50 hover:shadow-sm" : "hover:bg-gray-50"
54528
54943
  ),
54529
54944
  children: [
54530
54945
  /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
@@ -54590,10 +55005,7 @@ var UserManagementTable = ({
54590
55005
  setShowUsageDetailModal(true);
54591
55006
  },
54592
55007
  className: "group flex items-center gap-3 hover:bg-gray-50 -mx-2 px-2 py-1.5 rounded-lg transition-all duration-200",
54593
- children: isUsageLoading ? /* @__PURE__ */ jsx("div", { className: "animate-pulse bg-gray-200 h-5 w-20 rounded" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
54594
- /* @__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) }),
54595
- /* @__PURE__ */ jsx(ArrowRight, { className: "w-4 h-4 text-gray-400 group-hover:text-blue-600 group-hover:translate-x-0.5 transition-all" })
54596
- ] })
55008
+ 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) })
54597
55009
  }
54598
55010
  ) : /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-400", children: "-" }) }),
54599
55011
  /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right", children: hasActions && /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsx(
@@ -54641,25 +55053,50 @@ var UserManagementTable = ({
54641
55053
  children: /* @__PURE__ */ jsxs("div", { className: "py-1", children: [
54642
55054
  (() => {
54643
55055
  const user = users.find((u) => u.user_id === openActionMenuId);
54644
- const isCurrentUser = user?.user_id === currentUserId;
54645
- const canChangeRole = user && permissions.canChangeRole(user);
54646
- return canChangeRole && /* @__PURE__ */ jsxs(
55056
+ const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
55057
+ return canShowUsage && /* @__PURE__ */ jsxs(
54647
55058
  "button",
54648
55059
  {
54649
55060
  onClick: () => {
54650
55061
  if (user) {
54651
- handleChangeRole(user);
55062
+ setUsageDetailUserId(user.user_id);
55063
+ setShowUsageDetailModal(true);
55064
+ handleCloseActionMenu();
54652
55065
  }
54653
55066
  },
54654
- 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",
54655
- disabled: isCurrentUser,
55067
+ className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
54656
55068
  children: [
54657
- /* @__PURE__ */ jsx(UserCog, { className: "w-4 h-4" }),
54658
- "Change Role"
55069
+ /* @__PURE__ */ jsx(BarChart3, { className: "w-4 h-4" }),
55070
+ "View Detailed Usage"
54659
55071
  ]
54660
55072
  }
54661
55073
  );
54662
55074
  })(),
55075
+ (() => {
55076
+ const user = users.find((u) => u.user_id === openActionMenuId);
55077
+ const isCurrentUser = user?.user_id === currentUserId;
55078
+ const canChangeRole = user && permissions.canChangeRole(user);
55079
+ const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
55080
+ return canChangeRole && /* @__PURE__ */ jsxs(Fragment, { children: [
55081
+ canShowUsage && /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200 my-1" }),
55082
+ /* @__PURE__ */ jsxs(
55083
+ "button",
55084
+ {
55085
+ onClick: () => {
55086
+ if (user) {
55087
+ handleChangeRole(user);
55088
+ }
55089
+ },
55090
+ 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",
55091
+ disabled: isCurrentUser,
55092
+ children: [
55093
+ /* @__PURE__ */ jsx(UserCog, { className: "w-4 h-4" }),
55094
+ "Change Role"
55095
+ ]
55096
+ }
55097
+ )
55098
+ ] });
55099
+ })(),
54663
55100
  (() => {
54664
55101
  const user = users.find((u) => u.user_id === openActionMenuId);
54665
55102
  const isCurrentUser = user?.user_id === currentUserId;
@@ -55201,7 +55638,7 @@ var TeamManagementView = ({
55201
55638
  }, {});
55202
55639
  }, [usageData, usageDateRange.daysElapsed]);
55203
55640
  const pageTitle = user?.role_level === "optifye" ? "Platform Users" : "Team Management";
55204
- const pageDescription = user?.role_level === "optifye" ? "Manage users across all companies" : user?.role_level === "owner" ? "Manage your team members" : "Manage supervisors in your factory";
55641
+ 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";
55205
55642
  const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "plant_head"].includes(user.role_level) : false;
55206
55643
  const loadData = useCallback(async () => {
55207
55644
  if (!supabase) {
@@ -57046,4 +57483,4 @@ function shuffleArray(array) {
57046
57483
  return shuffled;
57047
57484
  }
57048
57485
 
57049
- 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, areAllLinesOnSameShift, 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, 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, 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 };
57486
+ 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 };