@optifye/dashboard-core 6.10.0 → 6.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1847,22 +1847,112 @@ var qualityService = {
1847
1847
  }
1848
1848
  };
1849
1849
 
1850
- // src/lib/services/workspaceService.ts
1850
+ // src/lib/services/backendClient.ts
1851
+ var ACCESS_TOKEN_REFRESH_BUFFER_MS = 6e4;
1852
+ var cachedAccessToken = null;
1853
+ var cachedAccessTokenExpiresAtMs = null;
1854
+ var cachedUserId = null;
1855
+ var inFlightRequests = /* @__PURE__ */ new Map();
1856
+ var authListenerSubscription = null;
1857
+ var authListenerSupabase = null;
1858
+ var clearBackendClientCaches = () => {
1859
+ cachedAccessToken = null;
1860
+ cachedAccessTokenExpiresAtMs = null;
1861
+ cachedUserId = null;
1862
+ inFlightRequests.clear();
1863
+ };
1864
+ var initBackendClientAuthListener = (supabase) => {
1865
+ if (authListenerSupabase === supabase && authListenerSubscription) return;
1866
+ if (authListenerSubscription) {
1867
+ authListenerSubscription.unsubscribe();
1868
+ authListenerSubscription = null;
1869
+ }
1870
+ authListenerSupabase = supabase;
1871
+ const { data } = supabase.auth.onAuthStateChange(() => {
1872
+ clearBackendClientCaches();
1873
+ });
1874
+ authListenerSubscription = data.subscription;
1875
+ };
1851
1876
  var getBackendUrl = () => {
1852
1877
  const url = process.env.NEXT_PUBLIC_BACKEND_URL;
1853
1878
  if (!url) {
1854
1879
  throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
1855
1880
  }
1856
- return url;
1881
+ return url.replace(/\/$/, "");
1857
1882
  };
1858
- var getAuthToken = async () => {
1859
- const supabase = _getSupabaseInstance();
1860
- if (!supabase) throw new Error("Supabase client not initialized");
1861
- const { data: { session } } = await supabase.auth.getSession();
1883
+ var getAuthToken = async (supabase) => {
1884
+ const now2 = Date.now();
1885
+ if (cachedAccessToken && cachedAccessTokenExpiresAtMs && now2 < cachedAccessTokenExpiresAtMs - ACCESS_TOKEN_REFRESH_BUFFER_MS) {
1886
+ return cachedAccessToken;
1887
+ }
1888
+ const {
1889
+ data: { session }
1890
+ } = await supabase.auth.getSession();
1862
1891
  if (!session?.access_token) {
1892
+ clearBackendClientCaches();
1863
1893
  throw new Error("No authentication token available. Please log in.");
1864
1894
  }
1865
- return session.access_token;
1895
+ cachedAccessToken = session.access_token;
1896
+ cachedUserId = session.user?.id || null;
1897
+ const expiresAtSeconds = session?.expires_at;
1898
+ cachedAccessTokenExpiresAtMs = typeof expiresAtSeconds === "number" ? expiresAtSeconds * 1e3 : now2 + 5 * 60 * 1e3;
1899
+ return cachedAccessToken;
1900
+ };
1901
+ var defaultDedupeKey = (method, url, body) => {
1902
+ const bodyKey = body === void 0 ? "" : typeof body === "string" ? body : JSON.stringify(body);
1903
+ return `${cachedUserId || "anon"}::${method.toUpperCase()}::${url}::${bodyKey}`;
1904
+ };
1905
+ var fetchBackendJson = async (supabase, endpoint, options = {}) => {
1906
+ const baseUrl = getBackendUrl();
1907
+ const url = endpoint.startsWith("http") ? endpoint : `${baseUrl}${endpoint.startsWith("/") ? "" : "/"}${endpoint}`;
1908
+ const method = (options.method || "GET").toString();
1909
+ const bodyForKey = options.body;
1910
+ const dedupeKey = options.dedupeKey || defaultDedupeKey(method, url, bodyForKey);
1911
+ const existing = inFlightRequests.get(dedupeKey);
1912
+ if (existing) {
1913
+ return existing;
1914
+ }
1915
+ const requestPromise = (async () => {
1916
+ const headers = new Headers(options.headers || {});
1917
+ if (!options.skipAuth) {
1918
+ const token = await getAuthToken(supabase);
1919
+ headers.set("Authorization", `Bearer ${token}`);
1920
+ }
1921
+ if (!headers.has("Content-Type") && options.body !== void 0) {
1922
+ headers.set("Content-Type", "application/json");
1923
+ }
1924
+ const response = await fetch(url, {
1925
+ ...options,
1926
+ headers
1927
+ });
1928
+ if (!response.ok) {
1929
+ const errorText = await response.text();
1930
+ throw new Error(`Backend API error (${response.status}): ${errorText}`);
1931
+ }
1932
+ if (response.status === 204) return void 0;
1933
+ const text = await response.text();
1934
+ return text ? JSON.parse(text) : void 0;
1935
+ })();
1936
+ inFlightRequests.set(dedupeKey, requestPromise);
1937
+ try {
1938
+ return await requestPromise;
1939
+ } finally {
1940
+ inFlightRequests.delete(dedupeKey);
1941
+ }
1942
+ };
1943
+
1944
+ // src/lib/services/workspaceService.ts
1945
+ var getBackendUrl2 = () => {
1946
+ const url = process.env.NEXT_PUBLIC_BACKEND_URL;
1947
+ if (!url) {
1948
+ throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
1949
+ }
1950
+ return url;
1951
+ };
1952
+ var getAuthToken2 = async () => {
1953
+ const supabase = _getSupabaseInstance();
1954
+ if (!supabase) throw new Error("Supabase client not initialized");
1955
+ return getAuthToken(supabase);
1866
1956
  };
1867
1957
  var workspaceService = {
1868
1958
  // Cache for workspace display names to avoid repeated API calls
@@ -1870,26 +1960,56 @@ var workspaceService = {
1870
1960
  _cacheTimestamp: 0,
1871
1961
  _cacheExpiryMs: 5 * 60 * 1e3,
1872
1962
  // 5 minutes cache
1873
- async getWorkspaces(lineId) {
1874
- try {
1875
- const token = await getAuthToken();
1876
- const apiUrl = getBackendUrl();
1877
- const response = await fetch(`${apiUrl}/api/workspaces?line_id=${lineId}`, {
1878
- headers: {
1879
- "Authorization": `Bearer ${token}`,
1880
- "Content-Type": "application/json"
1963
+ // Cache for workspace lists to avoid repeated API calls (line configuration changes infrequently)
1964
+ _workspacesCache: /* @__PURE__ */ new Map(),
1965
+ _workspacesInFlight: /* @__PURE__ */ new Map(),
1966
+ _workspacesCacheExpiryMs: 10 * 60 * 1e3,
1967
+ // 10 minutes cache
1968
+ async getWorkspaces(lineId, options) {
1969
+ const enabledOnly = options?.enabledOnly ?? false;
1970
+ const force = options?.force ?? false;
1971
+ const cacheKey = `${lineId}::enabledOnly=${enabledOnly}`;
1972
+ const now2 = Date.now();
1973
+ const cached = this._workspacesCache.get(cacheKey);
1974
+ if (!force && cached && now2 - cached.timestamp < this._workspacesCacheExpiryMs) {
1975
+ return cached.workspaces;
1976
+ }
1977
+ const inFlight = this._workspacesInFlight.get(cacheKey);
1978
+ if (!force && inFlight) {
1979
+ return inFlight;
1980
+ }
1981
+ const fetchPromise = (async () => {
1982
+ try {
1983
+ const token = await getAuthToken2();
1984
+ const apiUrl = getBackendUrl2();
1985
+ const params = new URLSearchParams({ line_id: lineId });
1986
+ if (enabledOnly) params.set("enabled_only", "true");
1987
+ const response = await fetch(`${apiUrl}/api/workspaces?${params.toString()}`, {
1988
+ headers: {
1989
+ "Authorization": `Bearer ${token}`,
1990
+ "Content-Type": "application/json"
1991
+ }
1992
+ });
1993
+ if (!response.ok) {
1994
+ const errorText = await response.text();
1995
+ throw new Error(`Backend API error (${response.status}): ${errorText}`);
1881
1996
  }
1882
- });
1883
- if (!response.ok) {
1884
- const errorText = await response.text();
1885
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
1997
+ const data = await response.json();
1998
+ const workspaces = data.workspaces || [];
1999
+ this._workspacesCache.set(cacheKey, { workspaces, timestamp: Date.now() });
2000
+ return workspaces;
2001
+ } catch (error) {
2002
+ console.error("Error fetching workspaces:", error);
2003
+ throw error;
2004
+ } finally {
2005
+ this._workspacesInFlight.delete(cacheKey);
1886
2006
  }
1887
- const data = await response.json();
1888
- return data.workspaces || [];
1889
- } catch (error) {
1890
- console.error("Error fetching workspaces:", error);
1891
- throw error;
1892
- }
2007
+ })();
2008
+ this._workspacesInFlight.set(cacheKey, fetchPromise);
2009
+ return fetchPromise;
2010
+ },
2011
+ async getEnabledWorkspaces(lineId, options) {
2012
+ return this.getWorkspaces(lineId, { enabledOnly: true, force: options?.force });
1893
2013
  },
1894
2014
  /**
1895
2015
  * Fetches workspace display names from the database
@@ -1897,8 +2017,8 @@ var workspaceService = {
1897
2017
  */
1898
2018
  async getWorkspaceDisplayNames(companyId, lineId) {
1899
2019
  try {
1900
- const token = await getAuthToken();
1901
- const apiUrl = getBackendUrl();
2020
+ const token = await getAuthToken2();
2021
+ const apiUrl = getBackendUrl2();
1902
2022
  const params = new URLSearchParams();
1903
2023
  if (companyId) params.append("company_id", companyId);
1904
2024
  if (lineId) params.append("line_id", lineId);
@@ -1957,16 +2077,39 @@ var workspaceService = {
1957
2077
  this._workspaceDisplayNamesCache.clear();
1958
2078
  this._cacheTimestamp = 0;
1959
2079
  },
2080
+ clearWorkspacesCache() {
2081
+ this._workspacesCache.clear();
2082
+ this._workspacesInFlight.clear();
2083
+ },
2084
+ invalidateWorkspacesCacheForLine(lineId) {
2085
+ const prefix = `${lineId}::`;
2086
+ Array.from(this._workspacesCache.keys()).forEach((key) => {
2087
+ if (key.startsWith(prefix)) this._workspacesCache.delete(key);
2088
+ });
2089
+ Array.from(this._workspacesInFlight.keys()).forEach((key) => {
2090
+ if (key.startsWith(prefix)) this._workspacesInFlight.delete(key);
2091
+ });
2092
+ },
2093
+ _patchCachedWorkspacesForLine(lineId, workspaceRowId, patch) {
2094
+ const prefix = `${lineId}::`;
2095
+ Array.from(this._workspacesCache.entries()).forEach(([key, entry]) => {
2096
+ if (!key.startsWith(prefix)) return;
2097
+ const nextWorkspaces = (entry.workspaces || []).map(
2098
+ (ws) => ws?.id === workspaceRowId ? { ...ws, ...patch } : ws
2099
+ );
2100
+ this._workspacesCache.set(key, { workspaces: nextWorkspaces, timestamp: Date.now() });
2101
+ });
2102
+ },
1960
2103
  /**
1961
2104
  * Updates the display name for a workspace
1962
2105
  * @param workspaceId - The workspace UUID
1963
2106
  * @param displayName - The new display name
1964
- * @returns Promise<void>
2107
+ * @returns Updated workspace record from backend
1965
2108
  */
1966
2109
  async updateWorkspaceDisplayName(workspaceId, displayName) {
1967
2110
  try {
1968
- const token = await getAuthToken();
1969
- const apiUrl = getBackendUrl();
2111
+ const token = await getAuthToken2();
2112
+ const apiUrl = getBackendUrl2();
1970
2113
  const response = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/display-name`, {
1971
2114
  method: "PATCH",
1972
2115
  headers: {
@@ -1979,7 +2122,15 @@ var workspaceService = {
1979
2122
  const errorText = await response.text();
1980
2123
  throw new Error(`Backend API error (${response.status}): ${errorText}`);
1981
2124
  }
2125
+ const data = await response.json();
1982
2126
  this.clearWorkspaceDisplayNamesCache();
2127
+ const updatedWorkspace = data?.workspace || null;
2128
+ if (updatedWorkspace?.line_id && updatedWorkspace?.id) {
2129
+ this._patchCachedWorkspacesForLine(updatedWorkspace.line_id, updatedWorkspace.id, {
2130
+ display_name: updatedWorkspace.display_name
2131
+ });
2132
+ }
2133
+ return updatedWorkspace;
1983
2134
  } catch (error) {
1984
2135
  console.error(`Error updating workspace display name for ${workspaceId}:`, error);
1985
2136
  throw error;
@@ -1987,8 +2138,8 @@ var workspaceService = {
1987
2138
  },
1988
2139
  async updateWorkspaceAction(updates) {
1989
2140
  try {
1990
- const token = await getAuthToken();
1991
- const apiUrl = getBackendUrl();
2141
+ const token = await getAuthToken2();
2142
+ const apiUrl = getBackendUrl2();
1992
2143
  const response = await fetch(`${apiUrl}/api/workspaces/actions/update`, {
1993
2144
  method: "POST",
1994
2145
  headers: {
@@ -2001,6 +2152,7 @@ var workspaceService = {
2001
2152
  const errorText = await response.text();
2002
2153
  throw new Error(`Backend API error (${response.status}): ${errorText}`);
2003
2154
  }
2155
+ this.clearWorkspacesCache();
2004
2156
  } catch (error) {
2005
2157
  console.error("Error updating workspace actions:", error);
2006
2158
  throw error;
@@ -2008,8 +2160,8 @@ var workspaceService = {
2008
2160
  },
2009
2161
  async updateActionThresholds(thresholds) {
2010
2162
  try {
2011
- const token = await getAuthToken();
2012
- const apiUrl = getBackendUrl();
2163
+ const token = await getAuthToken2();
2164
+ const apiUrl = getBackendUrl2();
2013
2165
  const response = await fetch(`${apiUrl}/api/workspaces/action-thresholds/update`, {
2014
2166
  method: "POST",
2015
2167
  headers: {
@@ -2029,8 +2181,8 @@ var workspaceService = {
2029
2181
  },
2030
2182
  async getActionThresholds(lineId, date, shiftId = 0) {
2031
2183
  try {
2032
- const token = await getAuthToken();
2033
- const apiUrl = getBackendUrl();
2184
+ const token = await getAuthToken2();
2185
+ const apiUrl = getBackendUrl2();
2034
2186
  const response = await fetch(
2035
2187
  `${apiUrl}/api/workspaces/action-thresholds?line_id=${lineId}&date=${date}&shift_id=${shiftId}`,
2036
2188
  {
@@ -2085,8 +2237,8 @@ var workspaceService = {
2085
2237
  const totalPPH = outputWorkspaces.reduce((sum, ws) => sum + (ws.action_pph_threshold || 0), 0);
2086
2238
  const operationalDate = getOperationalDate(defaultTimezone || "UTC");
2087
2239
  try {
2088
- const token = await getAuthToken();
2089
- const apiUrl = getBackendUrl();
2240
+ const token = await getAuthToken2();
2241
+ const apiUrl = getBackendUrl2();
2090
2242
  const response = await fetch(
2091
2243
  `${apiUrl}/api/workspaces/line-thresholds?line_id=${lineId}`,
2092
2244
  {
@@ -2117,8 +2269,8 @@ var workspaceService = {
2117
2269
  // Returns ShiftConfiguration array, which is the logical representation.
2118
2270
  async getShiftConfigurations(lineId) {
2119
2271
  try {
2120
- const token = await getAuthToken();
2121
- const apiUrl = getBackendUrl();
2272
+ const token = await getAuthToken2();
2273
+ const apiUrl = getBackendUrl2();
2122
2274
  const response = await fetch(
2123
2275
  `${apiUrl}/api/workspaces/shift-configurations?line_id=${lineId}`,
2124
2276
  {
@@ -2148,8 +2300,8 @@ var workspaceService = {
2148
2300
  },
2149
2301
  async updateShiftConfigurations(shiftConfig) {
2150
2302
  try {
2151
- const token = await getAuthToken();
2152
- const apiUrl = getBackendUrl();
2303
+ const token = await getAuthToken2();
2304
+ const apiUrl = getBackendUrl2();
2153
2305
  const response = await fetch(
2154
2306
  `${apiUrl}/api/workspaces/shift-configurations`,
2155
2307
  {
@@ -2186,8 +2338,8 @@ var workspaceService = {
2186
2338
  */
2187
2339
  async fetchBulkTargets(params) {
2188
2340
  try {
2189
- const token = await getAuthToken();
2190
- const apiUrl = getBackendUrl();
2341
+ const token = await getAuthToken2();
2342
+ const apiUrl = getBackendUrl2();
2191
2343
  const {
2192
2344
  companyId,
2193
2345
  lineIds,
@@ -2373,7 +2525,17 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2373
2525
  }
2374
2526
  }
2375
2527
  const dataWithUptime = processedData.map((workspace) => {
2376
- const uptimeDetails = uptimeMap.get(workspace.workspace_id);
2528
+ const mapKey = this.getUptimeMapKey(workspace.line_id, workspace.workspace_id);
2529
+ const uptimeDetails = uptimeMap.get(mapKey);
2530
+ console.log(`[getWorkspaceHealthStatus] Lookup:`, {
2531
+ mapKey,
2532
+ workspaceLineId: workspace.line_id,
2533
+ workspaceId: workspace.workspace_id,
2534
+ workspaceName: workspace.workspace_display_name,
2535
+ found: !!uptimeDetails,
2536
+ uptime: uptimeDetails?.percentage,
2537
+ downtime: uptimeDetails ? Math.max(0, uptimeDetails.expectedMinutes - uptimeDetails.actualMinutes) : null
2538
+ });
2377
2539
  if (uptimeDetails) {
2378
2540
  return {
2379
2541
  ...workspace,
@@ -2734,6 +2896,13 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2734
2896
  clearCache() {
2735
2897
  this.cache.clear();
2736
2898
  }
2899
+ /**
2900
+ * Generate a composite key for the uptime map that includes line_id
2901
+ * This prevents data collision when multiple lines have workspaces with the same workspace_id
2902
+ */
2903
+ getUptimeMapKey(lineId, workspaceId) {
2904
+ return lineId ? `${lineId}::${workspaceId}` : workspaceId;
2905
+ }
2737
2906
  async calculateWorkspaceUptime(companyId, passedShiftConfig, timezone, lineShiftConfigs, overrideDate, overrideShiftId) {
2738
2907
  const supabase = _getSupabaseInstance();
2739
2908
  if (!supabase) throw new Error("Supabase client not initialized");
@@ -2763,7 +2932,7 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2763
2932
  const queryShiftId = overrideShiftId ?? currentShiftId;
2764
2933
  const tableName = `performance_metrics_${companyId.replace(/-/g, "_")}`;
2765
2934
  try {
2766
- const { data: queryData, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array").eq("date", queryDate).eq("shift_id", queryShiftId);
2935
+ const { data: queryData, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array, line_id").eq("date", queryDate).eq("shift_id", queryShiftId);
2767
2936
  if (error) {
2768
2937
  console.error("Error fetching performance metrics:", error);
2769
2938
  return /* @__PURE__ */ new Map();
@@ -2809,7 +2978,8 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2809
2978
  }
2810
2979
  const completedWindow = uptimeMinutes + downtimeMinutes;
2811
2980
  const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
2812
- uptimeMap.set(record.workspace_id, {
2981
+ const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
2982
+ uptimeMap.set(mapKey, {
2813
2983
  expectedMinutes: completedMinutes,
2814
2984
  actualMinutes: uptimeMinutes,
2815
2985
  percentage,
@@ -2851,6 +3021,17 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2851
3021
  uniqueQueries.get(key).lineConfigs.push({ lineId, shiftStartDate, completedMinutes });
2852
3022
  });
2853
3023
  console.log(`[calculateWorkspaceUptimeMultiLine] Querying ${uniqueQueries.size} unique date/shift combinations for ${lineShiftConfigs.size} lines`);
3024
+ uniqueQueries.forEach((queryConfig, key) => {
3025
+ console.log(`[calculateWorkspaceUptimeMultiLine] Query batch ${key}:`, {
3026
+ date: queryConfig.date,
3027
+ shiftId: queryConfig.shiftId,
3028
+ lineConfigs: queryConfig.lineConfigs.map((lc) => ({
3029
+ lineId: lc.lineId,
3030
+ completedMinutes: lc.completedMinutes,
3031
+ shiftStartDate: lc.shiftStartDate.toISOString()
3032
+ }))
3033
+ });
3034
+ });
2854
3035
  const queryPromises = Array.from(uniqueQueries.entries()).map(async ([key, { date, shiftId, lineConfigs }]) => {
2855
3036
  try {
2856
3037
  const { data, error } = await supabase.from(tableName).select("workspace_id, workspace_display_name, output_hourly, output_array, line_id").eq("date", date).eq("shift_id", shiftId);
@@ -2867,8 +3048,14 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2867
3048
  const results = await Promise.all(queryPromises);
2868
3049
  for (const { records, lineConfigs } of results) {
2869
3050
  for (const record of records) {
2870
- const lineConfig = lineConfigs.find((lc) => {
2871
- return record.line_id && lineShiftConfigs.has(record.line_id);
3051
+ const lineConfig = lineConfigs.find((lc) => lc.lineId === record.line_id);
3052
+ console.log(`[calculateWorkspaceUptimeMultiLine] Record match:`, {
3053
+ recordLineId: record.line_id,
3054
+ recordWorkspaceId: record.workspace_id,
3055
+ recordWorkspaceDisplayName: record.workspace_display_name,
3056
+ availableLineIds: lineConfigs.map((lc) => lc.lineId),
3057
+ matchFound: !!lineConfig,
3058
+ matchedLineId: lineConfig?.lineId || "FALLBACK to " + lineConfigs[0]?.lineId
2872
3059
  });
2873
3060
  const effectiveLineConfig = lineConfig || lineConfigs[0];
2874
3061
  if (!effectiveLineConfig) continue;
@@ -2911,12 +3098,22 @@ var WorkspaceHealthService = class _WorkspaceHealthService {
2911
3098
  }
2912
3099
  const completedWindow = uptimeMinutes + downtimeMinutes;
2913
3100
  const percentage = completedWindow > 0 ? Number((uptimeMinutes / completedWindow * 100).toFixed(1)) : 100;
2914
- uptimeMap.set(record.workspace_id, {
3101
+ const mapKey = this.getUptimeMapKey(record.line_id, record.workspace_id);
3102
+ uptimeMap.set(mapKey, {
2915
3103
  expectedMinutes: completedMinutes,
2916
3104
  actualMinutes: uptimeMinutes,
2917
3105
  percentage,
2918
3106
  lastCalculated: (/* @__PURE__ */ new Date()).toISOString()
2919
3107
  });
3108
+ console.log(`[calculateWorkspaceUptimeMultiLine] Storing uptime:`, {
3109
+ mapKey,
3110
+ lineId: record.line_id,
3111
+ workspaceId: record.workspace_id,
3112
+ workspaceDisplayName: record.workspace_display_name,
3113
+ expectedMinutes: completedMinutes,
3114
+ actualMinutes: uptimeMinutes,
3115
+ percentage
3116
+ });
2920
3117
  }
2921
3118
  }
2922
3119
  console.log(`[calculateWorkspaceUptimeMultiLine] Calculated uptime for ${uptimeMap.size} workspaces`);
@@ -4564,7 +4761,7 @@ var getSupabaseClient = () => {
4564
4761
  }
4565
4762
  return supabaseJs.createClient(url, key);
4566
4763
  };
4567
- var getAuthToken2 = async () => {
4764
+ var getAuthToken3 = async () => {
4568
4765
  try {
4569
4766
  const supabase = getSupabaseClient();
4570
4767
  const { data: { session } } = await supabase.auth.getSession();
@@ -4598,7 +4795,7 @@ var S3ClipsSupabaseService = class {
4598
4795
  * Fetch with authentication and error handling
4599
4796
  */
4600
4797
  async fetchWithAuth(endpoint, body) {
4601
- const token = await getAuthToken2();
4798
+ const token = await getAuthToken3();
4602
4799
  if (!token) {
4603
4800
  throw new Error("Authentication required");
4604
4801
  }
@@ -6308,7 +6505,7 @@ var IDLE_TIME_REASON_COLORS = {
6308
6505
  bg: "bg-amber-50",
6309
6506
  border: "border-amber-200"
6310
6507
  },
6311
- "Machine Maintenance": {
6508
+ "Machine Downtime": {
6312
6509
  hex: "#3b82f6",
6313
6510
  // blue-500 - Scheduled/Technical
6314
6511
  text: "text-blue-600",
@@ -6744,6 +6941,7 @@ var SupabaseProvider = ({ client, children }) => {
6744
6941
  _setSupabaseInstance(client);
6745
6942
  React24.useEffect(() => {
6746
6943
  _setSupabaseInstance(client);
6944
+ initBackendClientAuthListener(client);
6747
6945
  }, [client]);
6748
6946
  const contextValue = React24.useMemo(() => ({ supabase: client }), [client]);
6749
6947
  return /* @__PURE__ */ jsxRuntime.jsx(SupabaseContext.Provider, { value: contextValue, children });
@@ -7658,33 +7856,10 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
7658
7856
  console.log("[useWorkspaceDetailedMetrics] Using shift ID:", queryShiftId, "from input shift:", shiftId);
7659
7857
  console.log("[useWorkspaceDetailedMetrics] Using date:", queryDate, "from input date:", date);
7660
7858
  console.log(`[useWorkspaceDetailedMetrics] Fetching from backend API for workspace: ${workspaceId}, date: ${queryDate}, shift: ${queryShiftId}`);
7661
- const { data: { session } } = await supabase.auth.getSession();
7662
- if (!session?.access_token) {
7663
- throw new Error("No authentication token available");
7664
- }
7665
- const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
7666
- const response = await fetch(
7667
- `${apiUrl}/api/dashboard/workspace/${workspaceId}/metrics?date=${queryDate}&shift_id=${queryShiftId}&company_id=${companyId}`,
7668
- {
7669
- headers: {
7670
- "Authorization": `Bearer ${session.access_token}`,
7671
- "Content-Type": "application/json"
7672
- }
7673
- }
7859
+ const backendData = await fetchBackendJson(
7860
+ supabase,
7861
+ `/api/dashboard/workspace/${workspaceId}/metrics?date=${queryDate}&shift_id=${queryShiftId}&company_id=${companyId}`
7674
7862
  );
7675
- if (!response.ok) {
7676
- const errorText = await response.text();
7677
- console.error("[useWorkspaceDetailedMetrics] Backend API error response:", errorText);
7678
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
7679
- }
7680
- const responseText = await response.text();
7681
- let backendData;
7682
- try {
7683
- backendData = JSON.parse(responseText);
7684
- } catch (parseError) {
7685
- console.error("[useWorkspaceDetailedMetrics] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
7686
- throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
7687
- }
7688
7863
  const data = backendData.metrics;
7689
7864
  if (data && options?.shiftConfig) {
7690
7865
  const dynamicShiftName = getShiftNameById(
@@ -7698,20 +7873,10 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
7698
7873
  }
7699
7874
  if (!data && !date && shiftId === void 0) {
7700
7875
  console.log("[useWorkspaceDetailedMetrics] No data found for current date/shift, attempting to find most recent data...");
7701
- const fallbackResponse = await fetch(
7702
- `${apiUrl}/api/dashboard/workspace/${workspaceId}/metrics?company_id=${companyId}&latest=true`,
7703
- {
7704
- headers: {
7705
- "Authorization": `Bearer ${session.access_token}`,
7706
- "Content-Type": "application/json"
7707
- }
7708
- }
7876
+ const fallbackData = await fetchBackendJson(
7877
+ supabase,
7878
+ `/api/dashboard/workspace/${workspaceId}/metrics?company_id=${companyId}&latest=true`
7709
7879
  );
7710
- if (!fallbackResponse.ok) {
7711
- const errorText = await fallbackResponse.text();
7712
- throw new Error(`Backend API error (${fallbackResponse.status}): ${errorText}`);
7713
- }
7714
- const fallbackData = await fallbackResponse.json();
7715
7880
  const recentData = fallbackData.metrics;
7716
7881
  if (recentData) {
7717
7882
  console.log(`[useWorkspaceDetailedMetrics] Found fallback data from date: ${recentData.date}, shift: ${recentData.shift_id}`);
@@ -8273,15 +8438,171 @@ var useWorkspaceDetailedMetrics = (workspaceId, date, shiftId, options) => {
8273
8438
  // Force refresh without cache
8274
8439
  };
8275
8440
  };
8441
+
8442
+ // src/lib/stores/shiftConfigStore.ts
8443
+ var shiftConfigsByLineId = /* @__PURE__ */ new Map();
8444
+ var listeners = /* @__PURE__ */ new Set();
8445
+ var inFlightFetchesByLineId = /* @__PURE__ */ new Map();
8446
+ var subscriptionsByLineId = /* @__PURE__ */ new Map();
8447
+ var calculateBreakDuration = (startTime, endTime) => {
8448
+ const [sh, sm] = startTime.split(":").map(Number);
8449
+ const [eh, em] = endTime.split(":").map(Number);
8450
+ let startMinutes = sh * 60 + sm;
8451
+ let endMinutes = eh * 60 + em;
8452
+ if (endMinutes < startMinutes) {
8453
+ endMinutes += 24 * 60;
8454
+ }
8455
+ return endMinutes - startMinutes;
8456
+ };
8457
+ var stripSeconds = (timeStr) => {
8458
+ if (!timeStr) return timeStr;
8459
+ return timeStr.substring(0, 5);
8460
+ };
8461
+ var buildShiftConfigFromOperatingHoursRows = (rows, fallback) => {
8462
+ const mapped = (rows || []).map((row) => ({
8463
+ shiftId: row.shift_id,
8464
+ shiftName: row.shift_name || `Shift ${row.shift_id}`,
8465
+ startTime: stripSeconds(row.start_time),
8466
+ endTime: stripSeconds(row.end_time),
8467
+ breaks: (() => {
8468
+ const raw = Array.isArray(row.breaks) ? row.breaks : Array.isArray(row.breaks?.breaks) ? row.breaks.breaks : [];
8469
+ return raw.map((b) => ({
8470
+ startTime: stripSeconds(b.start || b.startTime || "00:00"),
8471
+ endTime: stripSeconds(b.end || b.endTime || "00:00"),
8472
+ duration: calculateBreakDuration(
8473
+ stripSeconds(b.start || b.startTime || "00:00"),
8474
+ stripSeconds(b.end || b.endTime || "00:00")
8475
+ ),
8476
+ remarks: b.remarks || b.name || ""
8477
+ }));
8478
+ })(),
8479
+ timezone: row.timezone || void 0
8480
+ }));
8481
+ const day = mapped.find((s) => s.shiftId === 0);
8482
+ const night = mapped.find((s) => s.shiftId === 1);
8483
+ return {
8484
+ shifts: mapped,
8485
+ timezone: mapped[0]?.timezone || fallback?.timezone,
8486
+ dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallback?.dayShift,
8487
+ nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallback?.nightShift,
8488
+ transitionPeriodMinutes: fallback?.transitionPeriodMinutes || 0
8489
+ };
8490
+ };
8491
+ var shiftConfigStore = {
8492
+ get(lineId) {
8493
+ return shiftConfigsByLineId.get(lineId);
8494
+ },
8495
+ set(lineId, config) {
8496
+ shiftConfigsByLineId.set(lineId, config);
8497
+ listeners.forEach((listener) => listener(lineId));
8498
+ },
8499
+ setFromOperatingHoursRows(lineId, rows, fallback) {
8500
+ const config = buildShiftConfigFromOperatingHoursRows(rows, fallback);
8501
+ this.set(lineId, config);
8502
+ return config;
8503
+ },
8504
+ getMany(lineIds) {
8505
+ const map = /* @__PURE__ */ new Map();
8506
+ lineIds.forEach((lineId) => {
8507
+ const config = shiftConfigsByLineId.get(lineId);
8508
+ if (config) map.set(lineId, config);
8509
+ });
8510
+ return map;
8511
+ },
8512
+ subscribe(listener) {
8513
+ listeners.add(listener);
8514
+ return () => listeners.delete(listener);
8515
+ },
8516
+ clear() {
8517
+ shiftConfigsByLineId.clear();
8518
+ listeners.forEach((listener) => listener("*"));
8519
+ }
8520
+ };
8521
+ var fetchAndStoreShiftConfig = async (supabase, lineId, fallback) => {
8522
+ const existing = inFlightFetchesByLineId.get(lineId);
8523
+ if (existing) return existing;
8524
+ const promise = (async () => {
8525
+ try {
8526
+ const { data, error } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").eq("line_id", lineId);
8527
+ if (error) {
8528
+ throw new Error(`Failed to fetch shift config: ${error.message}`);
8529
+ }
8530
+ if (!data || data.length === 0) {
8531
+ if (fallback) {
8532
+ shiftConfigStore.set(lineId, fallback);
8533
+ return fallback;
8534
+ }
8535
+ return null;
8536
+ }
8537
+ return shiftConfigStore.setFromOperatingHoursRows(lineId, data, fallback);
8538
+ } finally {
8539
+ inFlightFetchesByLineId.delete(lineId);
8540
+ }
8541
+ })();
8542
+ inFlightFetchesByLineId.set(lineId, promise);
8543
+ return promise;
8544
+ };
8545
+ var ensureShiftConfigSubscription = (supabase, lineId, fallback) => {
8546
+ const existing = subscriptionsByLineId.get(lineId);
8547
+ if (existing && existing.supabase === supabase) {
8548
+ existing.refCount += 1;
8549
+ return () => {
8550
+ const current = subscriptionsByLineId.get(lineId);
8551
+ if (!current) return;
8552
+ current.refCount -= 1;
8553
+ if (current.refCount <= 0) {
8554
+ current.channel.unsubscribe();
8555
+ subscriptionsByLineId.delete(lineId);
8556
+ }
8557
+ };
8558
+ }
8559
+ if (existing) {
8560
+ existing.channel.unsubscribe();
8561
+ subscriptionsByLineId.delete(lineId);
8562
+ }
8563
+ const channel = supabase.channel(`shift_config_${lineId}`).on(
8564
+ "postgres_changes",
8565
+ {
8566
+ event: "*",
8567
+ schema: "public",
8568
+ table: "line_operating_hours",
8569
+ filter: `line_id=eq.${lineId}`
8570
+ },
8571
+ () => {
8572
+ fetchAndStoreShiftConfig(supabase, lineId, fallback).catch((err) => {
8573
+ console.error("[shiftConfigStore] Failed to refresh shift config", { lineId, err });
8574
+ });
8575
+ }
8576
+ ).subscribe();
8577
+ subscriptionsByLineId.set(lineId, { supabase, channel, refCount: 1 });
8578
+ return () => {
8579
+ const current = subscriptionsByLineId.get(lineId);
8580
+ if (!current) return;
8581
+ current.refCount -= 1;
8582
+ if (current.refCount <= 0) {
8583
+ current.channel.unsubscribe();
8584
+ subscriptionsByLineId.delete(lineId);
8585
+ }
8586
+ };
8587
+ };
8588
+
8589
+ // src/lib/hooks/useLineShiftConfig.ts
8276
8590
  var useLineShiftConfig = (lineId, fallbackConfig) => {
8277
- const [shiftConfig, setShiftConfig] = React24.useState(fallbackConfig || null);
8278
- const [isLoading, setIsLoading] = React24.useState(true);
8591
+ const [shiftConfig, setShiftConfig] = React24.useState(() => {
8592
+ if (!lineId || lineId === "factory" || lineId === "all") return fallbackConfig || null;
8593
+ return shiftConfigStore.get(lineId) || fallbackConfig || null;
8594
+ });
8595
+ const [isLoading, setIsLoading] = React24.useState(() => {
8596
+ if (!lineId || lineId === "factory" || lineId === "all") return false;
8597
+ return !shiftConfigStore.get(lineId);
8598
+ });
8279
8599
  const [error, setError] = React24.useState(null);
8280
8600
  const supabase = useSupabase();
8281
8601
  React24.useEffect(() => {
8282
8602
  if (!lineId || lineId === "factory" || lineId === "all") {
8283
8603
  setShiftConfig(fallbackConfig || null);
8284
8604
  setIsLoading(false);
8605
+ setError(null);
8285
8606
  return;
8286
8607
  }
8287
8608
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -8289,98 +8610,49 @@ var useLineShiftConfig = (lineId, fallbackConfig) => {
8289
8610
  console.warn(`[useShiftConfig] Invalid line ID format: ${lineId}, using fallback`);
8290
8611
  setShiftConfig(fallbackConfig || null);
8291
8612
  setIsLoading(false);
8613
+ setError(null);
8292
8614
  return;
8293
8615
  }
8294
8616
  let mounted = true;
8295
- const calculateBreakDuration2 = (startTime, endTime) => {
8296
- const [sh, sm] = startTime.split(":").map(Number);
8297
- const [eh, em] = endTime.split(":").map(Number);
8298
- let startMinutes = sh * 60 + sm;
8299
- let endMinutes = eh * 60 + em;
8300
- if (endMinutes < startMinutes) {
8301
- endMinutes += 24 * 60;
8302
- }
8303
- return endMinutes - startMinutes;
8617
+ const syncFromStore = () => {
8618
+ const stored = shiftConfigStore.get(lineId);
8619
+ setShiftConfig(stored || fallbackConfig || null);
8304
8620
  };
8305
- const fetchShiftConfig = async () => {
8621
+ syncFromStore();
8622
+ const unsubscribeStore = shiftConfigStore.subscribe((changedLineId) => {
8623
+ if (!mounted) return;
8624
+ if (changedLineId === "*" || changedLineId === lineId) {
8625
+ syncFromStore();
8626
+ }
8627
+ });
8628
+ const unsubscribeRealtime = ensureShiftConfigSubscription(supabase, lineId, fallbackConfig);
8629
+ const ensureLoaded = async () => {
8306
8630
  try {
8307
- setIsLoading(true);
8308
- setError(null);
8309
- console.log(`[useLineShiftConfig] \u{1F50D} Fetching shift config for line: ${lineId}`);
8310
- const { data: shiftsData, error: shiftsError } = await supabase.from("line_operating_hours").select("shift_id, shift_name, start_time, end_time, breaks, timezone").eq("line_id", lineId);
8311
- if (shiftsError) {
8312
- console.error(`[useLineShiftConfig] \u274C Error fetching shifts:`, shiftsError);
8313
- throw new Error(`Failed to fetch shift config: ${shiftsError.message}`);
8314
- }
8315
- if (!shiftsData || shiftsData.length === 0) {
8316
- console.warn(`[useLineShiftConfig] \u26A0\uFE0F No shift config found for line ${lineId}, using fallback`);
8317
- if (mounted) {
8318
- setShiftConfig(fallbackConfig || null);
8319
- setIsLoading(false);
8320
- }
8631
+ const existing = shiftConfigStore.get(lineId);
8632
+ if (existing) {
8633
+ if (mounted) setIsLoading(false);
8321
8634
  return;
8322
8635
  }
8323
- const stripSeconds = (timeStr) => {
8324
- if (!timeStr) return timeStr;
8325
- return timeStr.substring(0, 5);
8326
- };
8327
- const mapped = shiftsData.map((shift) => ({
8328
- shiftId: shift.shift_id,
8329
- shiftName: shift.shift_name || `Shift ${shift.shift_id}`,
8330
- startTime: stripSeconds(shift.start_time),
8331
- endTime: stripSeconds(shift.end_time),
8332
- breaks: Array.isArray(shift.breaks) ? shift.breaks.map((b) => ({
8333
- startTime: b.start || b.startTime || "00:00",
8334
- endTime: b.end || b.endTime || "00:00",
8335
- duration: calculateBreakDuration2(
8336
- b.start || b.startTime || "00:00",
8337
- b.end || b.endTime || "00:00"
8338
- ),
8339
- remarks: b.remarks || b.name || ""
8340
- })) : [],
8341
- timezone: shift.timezone
8342
- }));
8343
- const day = mapped.find((s) => s.shiftId === 0);
8344
- const night = mapped.find((s) => s.shiftId === 1);
8345
- const config = {
8346
- shifts: mapped,
8347
- timezone: mapped[0]?.timezone,
8348
- dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallbackConfig?.dayShift,
8349
- nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallbackConfig?.nightShift,
8350
- transitionPeriodMinutes: fallbackConfig?.transitionPeriodMinutes || 0
8351
- };
8352
- console.log(`[useLineShiftConfig] \u2705 Built config from DB:`, config);
8353
8636
  if (mounted) {
8354
- setShiftConfig(config);
8355
- setIsLoading(false);
8637
+ setIsLoading(true);
8638
+ setError(null);
8356
8639
  }
8640
+ await fetchAndStoreShiftConfig(supabase, lineId, fallbackConfig);
8357
8641
  } catch (err) {
8358
8642
  console.error("[useShiftConfig] Error fetching shift config:", err);
8359
8643
  if (mounted) {
8360
8644
  setError(err instanceof Error ? err.message : "Unknown error occurred");
8361
- setShiftConfig(fallbackConfig || null);
8362
- setIsLoading(false);
8645
+ setShiftConfig(shiftConfigStore.get(lineId) || fallbackConfig || null);
8363
8646
  }
8647
+ } finally {
8648
+ if (mounted) setIsLoading(false);
8364
8649
  }
8365
8650
  };
8366
- fetchShiftConfig();
8367
- const subscription = supabase.channel(`shift_config_${lineId}`).on(
8368
- "postgres_changes",
8369
- {
8370
- event: "*",
8371
- // Listen to all events (INSERT, UPDATE, DELETE)
8372
- schema: "public",
8373
- table: "line_operating_hours",
8374
- filter: `line_id=eq.${lineId}`
8375
- },
8376
- (payload) => {
8377
- console.log("[useShiftConfig] Real-time update received:", payload);
8378
- fetchShiftConfig();
8379
- }
8380
- ).subscribe();
8651
+ ensureLoaded();
8381
8652
  return () => {
8382
8653
  mounted = false;
8383
- subscription.unsubscribe();
8654
+ unsubscribeStore();
8655
+ unsubscribeRealtime();
8384
8656
  };
8385
8657
  }, [lineId, supabase]);
8386
8658
  return {
@@ -8471,8 +8743,8 @@ var useLineWorkspaceMetrics = (lineId, options) => {
8471
8743
  queryShiftId,
8472
8744
  metricsTable
8473
8745
  });
8474
- const workspaces2 = await workspaceService.getWorkspaces(lineId);
8475
- const enabledWorkspaceIds = workspaces2.filter((ws) => ws.enable === true).map((ws) => ws.id);
8746
+ const enabledWorkspaces = await workspaceService.getEnabledWorkspaces(lineId);
8747
+ const enabledWorkspaceIds = enabledWorkspaces.map((ws) => ws.id);
8476
8748
  if (enabledWorkspaceIds.length === 0) {
8477
8749
  setWorkspaces([]);
8478
8750
  setInitialized(true);
@@ -8570,14 +8842,6 @@ var useHistoricWorkspaceMetrics = (workspaceId, date, shiftId, options) => {
8570
8842
  isFetchingRef.current = true;
8571
8843
  setIsLoading(true);
8572
8844
  setError(null);
8573
- const { data: { session } } = await supabase.auth.getSession();
8574
- if (!session?.access_token) {
8575
- throw new Error("No authentication token available");
8576
- }
8577
- const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
8578
- if (!apiUrl) {
8579
- throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
8580
- }
8581
8845
  const params = new URLSearchParams({
8582
8846
  company_id: entityConfig.companyId || "",
8583
8847
  date
@@ -8585,20 +8849,10 @@ var useHistoricWorkspaceMetrics = (workspaceId, date, shiftId, options) => {
8585
8849
  if (shiftId !== void 0) {
8586
8850
  params.append("shift_id", shiftId.toString());
8587
8851
  }
8588
- const response = await fetch(
8589
- `${apiUrl}/api/dashboard/workspace/${workspaceId}/metrics?${params.toString()}`,
8590
- {
8591
- headers: {
8592
- "Authorization": `Bearer ${session.access_token}`,
8593
- "Content-Type": "application/json"
8594
- }
8595
- }
8852
+ const data = await fetchBackendJson(
8853
+ supabase,
8854
+ `/api/dashboard/workspace/${workspaceId}/metrics?${params.toString()}`
8596
8855
  );
8597
- if (!response.ok) {
8598
- const errorText = await response.text();
8599
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
8600
- }
8601
- const data = await response.json();
8602
8856
  const fetchedMetrics = data.metrics;
8603
8857
  if (!fetchedMetrics) {
8604
8858
  setMetrics(null);
@@ -8818,14 +9072,6 @@ var useLeaderboardMetrics = (date, shiftId, limit = 10, filter2 = "all") => {
8818
9072
  const currentShift = getCurrentShift(defaultTimezone, shiftConfig);
8819
9073
  const queryDate = date || currentShift.date;
8820
9074
  const queryShiftId = shiftId !== void 0 ? shiftId : currentShift.shiftId;
8821
- const { data: { session } } = await supabase.auth.getSession();
8822
- if (!session?.access_token) {
8823
- throw new Error("No authentication token available");
8824
- }
8825
- const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
8826
- if (!apiUrl) {
8827
- throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
8828
- }
8829
9075
  const params = new URLSearchParams({
8830
9076
  date: queryDate,
8831
9077
  shift_id: queryShiftId.toString(),
@@ -8833,20 +9079,10 @@ var useLeaderboardMetrics = (date, shiftId, limit = 10, filter2 = "all") => {
8833
9079
  limit: limit.toString(),
8834
9080
  filter: filter2
8835
9081
  });
8836
- const response = await fetch(
8837
- `${apiUrl}/api/dashboard/leaderboard?${params.toString()}`,
8838
- {
8839
- headers: {
8840
- "Authorization": `Bearer ${session.access_token}`,
8841
- "Content-Type": "application/json"
8842
- }
8843
- }
9082
+ const data = await fetchBackendJson(
9083
+ supabase,
9084
+ `/api/dashboard/leaderboard?${params.toString()}`
8844
9085
  );
8845
- if (!response.ok) {
8846
- const errorText = await response.text();
8847
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
8848
- }
8849
- const data = await response.json();
8850
9086
  setLeaderboard(data.leaderboard || []);
8851
9087
  } catch (err) {
8852
9088
  console.error("[useLeaderboardMetrics] Error fetching leaderboard:", err);
@@ -8867,27 +9103,201 @@ var useLeaderboardMetrics = (date, shiftId, limit = 10, filter2 = "all") => {
8867
9103
  refetch: fetchLeaderboard
8868
9104
  };
8869
9105
  };
9106
+ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
9107
+ const [shiftConfigMap, setShiftConfigMap] = React24.useState(/* @__PURE__ */ new Map());
9108
+ const [isLoading, setIsLoading] = React24.useState(true);
9109
+ const [error, setError] = React24.useState(null);
9110
+ const supabase = useSupabase();
9111
+ const lineIdsKey = React24.useMemo(() => lineIds.slice().sort().join(","), [lineIds]);
9112
+ React24.useEffect(() => {
9113
+ if (!lineIds || lineIds.length === 0) {
9114
+ setShiftConfigMap(/* @__PURE__ */ new Map());
9115
+ setIsLoading(false);
9116
+ setError(null);
9117
+ return;
9118
+ }
9119
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
9120
+ const validLineIds = lineIds.filter((id3) => uuidRegex.test(id3));
9121
+ if (validLineIds.length === 0) {
9122
+ console.warn("[useMultiLineShiftConfigs] No valid line IDs provided");
9123
+ setShiftConfigMap(/* @__PURE__ */ new Map());
9124
+ setIsLoading(false);
9125
+ setError(null);
9126
+ return;
9127
+ }
9128
+ let mounted = true;
9129
+ const syncFromStore = (changedLineId) => {
9130
+ setShiftConfigMap((prev) => {
9131
+ const next = new Map(prev);
9132
+ const updateLine = (lineId) => {
9133
+ const config = shiftConfigStore.get(lineId) || fallbackConfig;
9134
+ if (config) next.set(lineId, config);
9135
+ };
9136
+ if (!changedLineId || changedLineId === "*") {
9137
+ validLineIds.forEach(updateLine);
9138
+ } else if (validLineIds.includes(changedLineId)) {
9139
+ updateLine(changedLineId);
9140
+ }
9141
+ return next;
9142
+ });
9143
+ };
9144
+ const initialMap = /* @__PURE__ */ new Map();
9145
+ validLineIds.forEach((lineId) => {
9146
+ const config = shiftConfigStore.get(lineId) || fallbackConfig;
9147
+ if (config) initialMap.set(lineId, config);
9148
+ });
9149
+ setShiftConfigMap(initialMap);
9150
+ const unsubscribeStore = shiftConfigStore.subscribe((changedLineId) => {
9151
+ if (!mounted) return;
9152
+ if (changedLineId === "*" || validLineIds.includes(changedLineId)) {
9153
+ syncFromStore(changedLineId);
9154
+ }
9155
+ });
9156
+ const unsubscribeRealtimeList = validLineIds.map(
9157
+ (lineId) => ensureShiftConfigSubscription(supabase, lineId, fallbackConfig)
9158
+ );
9159
+ const fetchAllConfigs = async () => {
9160
+ try {
9161
+ const missingLineIds = validLineIds.filter((lineId) => !shiftConfigStore.get(lineId));
9162
+ if (missingLineIds.length === 0) {
9163
+ if (mounted) {
9164
+ setIsLoading(false);
9165
+ setError(null);
9166
+ }
9167
+ return;
9168
+ }
9169
+ if (mounted) {
9170
+ setIsLoading(true);
9171
+ setError(null);
9172
+ }
9173
+ console.log(`[useMultiLineShiftConfigs] Fetching shift configs for ${missingLineIds.length} lines`);
9174
+ const { data, error: fetchError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").in("line_id", missingLineIds);
9175
+ if (fetchError) {
9176
+ console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
9177
+ throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
9178
+ }
9179
+ const lineShiftsMap = /* @__PURE__ */ new Map();
9180
+ data?.forEach((row) => {
9181
+ if (!lineShiftsMap.has(row.line_id)) {
9182
+ lineShiftsMap.set(row.line_id, []);
9183
+ }
9184
+ lineShiftsMap.get(row.line_id).push(row);
9185
+ });
9186
+ lineShiftsMap.forEach((shifts, lineId) => {
9187
+ shiftConfigStore.setFromOperatingHoursRows(lineId, shifts, fallbackConfig);
9188
+ });
9189
+ missingLineIds.forEach((lineId) => {
9190
+ if (!lineShiftsMap.has(lineId) && fallbackConfig) {
9191
+ console.warn(`[useMultiLineShiftConfigs] No config found for line ${lineId}, using fallback`);
9192
+ shiftConfigStore.set(lineId, fallbackConfig);
9193
+ }
9194
+ });
9195
+ console.log(`[useMultiLineShiftConfigs] Stored configs for ${lineShiftsMap.size} lines`);
9196
+ if (mounted) {
9197
+ setIsLoading(false);
9198
+ }
9199
+ } catch (err) {
9200
+ console.error("[useMultiLineShiftConfigs] Error:", err);
9201
+ if (mounted) {
9202
+ setError(err instanceof Error ? err.message : "Failed to fetch shift configs");
9203
+ setIsLoading(false);
9204
+ }
9205
+ }
9206
+ };
9207
+ fetchAllConfigs();
9208
+ return () => {
9209
+ mounted = false;
9210
+ unsubscribeStore();
9211
+ unsubscribeRealtimeList.forEach((unsub) => unsub());
9212
+ };
9213
+ }, [lineIdsKey, supabase]);
9214
+ return {
9215
+ shiftConfigMap,
9216
+ isLoading,
9217
+ error
9218
+ };
9219
+ };
9220
+
9221
+ // src/lib/utils/shiftGrouping.ts
9222
+ var getCurrentShiftForLine = (lineId, shiftConfig, timezone, now2 = /* @__PURE__ */ new Date()) => {
9223
+ const currentShift = getCurrentShift(timezone, shiftConfig, now2);
9224
+ return {
9225
+ lineId,
9226
+ shiftId: currentShift.shiftId,
9227
+ date: currentShift.date,
9228
+ shiftName: currentShift.shiftName || `Shift ${currentShift.shiftId}`
9229
+ };
9230
+ };
9231
+ var groupLinesByShift = (shiftConfigMap, timezone, now2 = /* @__PURE__ */ new Date()) => {
9232
+ const lineShiftInfos = [];
9233
+ shiftConfigMap.forEach((shiftConfig, lineId) => {
9234
+ const info = getCurrentShiftForLine(lineId, shiftConfig, timezone, now2);
9235
+ lineShiftInfos.push(info);
9236
+ });
9237
+ const groupMap = /* @__PURE__ */ new Map();
9238
+ lineShiftInfos.forEach((info) => {
9239
+ const key = `${info.shiftId}-${info.date}`;
9240
+ if (!groupMap.has(key)) {
9241
+ groupMap.set(key, {
9242
+ shiftId: info.shiftId,
9243
+ date: info.date,
9244
+ shiftName: info.shiftName,
9245
+ lineIds: []
9246
+ });
9247
+ }
9248
+ groupMap.get(key).lineIds.push(info.lineId);
9249
+ });
9250
+ return Array.from(groupMap.values()).sort((a, b) => a.shiftId - b.shiftId);
9251
+ };
9252
+ var areAllLinesOnSameShift = (shiftConfigMap, timezone, now2 = /* @__PURE__ */ new Date()) => {
9253
+ if (shiftConfigMap.size <= 1) return true;
9254
+ const groups = groupLinesByShift(shiftConfigMap, timezone, now2);
9255
+ return groups.length === 1;
9256
+ };
9257
+ var getUniformShiftGroup = (shiftConfigMap, timezone, now2 = /* @__PURE__ */ new Date()) => {
9258
+ const groups = groupLinesByShift(shiftConfigMap, timezone, now2);
9259
+ return groups.length === 1 ? groups[0] : null;
9260
+ };
9261
+
9262
+ // src/lib/hooks/useDashboardMetrics.ts
8870
9263
  var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds }) => {
8871
9264
  const { supabaseUrl, supabaseKey } = useDashboardConfig();
8872
9265
  const entityConfig = useEntityConfig();
8873
9266
  const databaseConfig = useDatabaseConfig();
8874
9267
  const dateTimeConfig = useDateTimeConfig();
8875
9268
  const isFactoryView = lineId === (entityConfig.factoryViewId || "factory");
8876
- const firstLineId = React24.useMemo(() => {
8877
- const configuredLineIds = getConfiguredLineIds(entityConfig);
8878
- return configuredLineIds.length > 0 ? configuredLineIds[0] : void 0;
9269
+ const appTimezone = useAppTimezone();
9270
+ const defaultTimezone = appTimezone || dateTimeConfig?.defaultTimezone || "UTC";
9271
+ const configuredLineIds = React24.useMemo(() => {
9272
+ return getConfiguredLineIds(entityConfig);
8879
9273
  }, [entityConfig]);
8880
- const lineIdForShiftConfig = isFactoryView ? firstLineId : lineId;
8881
- const { shiftConfig, isLoading: shiftLoading, isFromDatabase } = useDynamicShiftConfig(lineIdForShiftConfig);
9274
+ const { shiftConfig: staticShiftConfig } = useDashboardConfig();
9275
+ const {
9276
+ shiftConfigMap: multiLineShiftConfigMap,
9277
+ isLoading: isMultiLineShiftConfigLoading
9278
+ } = useMultiLineShiftConfigs(
9279
+ isFactoryView ? configuredLineIds : [],
9280
+ staticShiftConfig
9281
+ );
9282
+ const {
9283
+ shiftConfig: singleLineShiftConfig,
9284
+ isLoading: isSingleLineShiftConfigLoading,
9285
+ isFromDatabase
9286
+ } = useDynamicShiftConfig(isFactoryView ? void 0 : lineId);
9287
+ const shiftLoading = isFactoryView ? isMultiLineShiftConfigLoading : isSingleLineShiftConfigLoading;
9288
+ const shiftGroups = React24.useMemo(() => {
9289
+ if (!isFactoryView) return [];
9290
+ if (isMultiLineShiftConfigLoading || multiLineShiftConfigMap.size === 0) return [];
9291
+ return groupLinesByShift(multiLineShiftConfigMap, defaultTimezone);
9292
+ }, [isFactoryView, isMultiLineShiftConfigLoading, multiLineShiftConfigMap, defaultTimezone]);
9293
+ const shiftConfig = isFactoryView ? null : singleLineShiftConfig;
8882
9294
  console.log(`[useDashboardMetrics] \u{1F3AF} Shift config for line ${lineId}:`, {
8883
9295
  isFactoryView,
8884
- lineIdForShiftConfig,
8885
- firstLineId,
8886
9296
  isFromDatabase,
8887
- shiftConfig: shiftConfig ? { shifts: shiftConfig.shifts?.length, timezone: shiftConfig.timezone } : null
9297
+ shiftConfig: shiftConfig ? { shifts: shiftConfig.shifts?.length, timezone: shiftConfig.timezone } : null,
9298
+ shiftGroupsCount: shiftGroups.length,
9299
+ shiftGroups: shiftGroups.map((g) => ({ shiftId: g.shiftId, date: g.date, lineCount: g.lineIds.length }))
8888
9300
  });
8889
- const appTimezone = useAppTimezone();
8890
- const defaultTimezone = appTimezone || dateTimeConfig?.defaultTimezone || "UTC";
8891
9301
  const configuredLineMetricsTable = databaseConfig?.tables?.lineMetrics ?? "line_metrics";
8892
9302
  const schema = databaseConfig?.schema ?? "public";
8893
9303
  const supabase = useSupabase();
@@ -8924,16 +9334,6 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
8924
9334
  setIsLoading(true);
8925
9335
  setError(null);
8926
9336
  try {
8927
- const currentShiftDetails = getCurrentShift(defaultTimezone, shiftConfig);
8928
- const operationalDate = getOperationalDate(defaultTimezone);
8929
- const targetLineIds = currentLineIdToUse === (entityConfig.factoryViewId || "factory") ? userAccessibleLineIds || getConfiguredLineIds(entityConfig) : [currentLineIdToUse];
8930
- if (targetLineIds.length === 0 && currentLineIdToUse === (entityConfig.factoryViewId || "factory")) {
8931
- throw new Error("Factory view selected, but no lines are configured in entityConfig.");
8932
- }
8933
- if (targetLineIds.length === 0) {
8934
- throw new Error("No target line IDs available for fetching metrics.");
8935
- }
8936
- const isFactoryView2 = currentLineIdToUse === (entityConfig.factoryViewId || "factory");
8937
9337
  const { data: { session } } = await supabase.auth.getSession();
8938
9338
  console.log("[useDashboardMetrics] Session check:", {
8939
9339
  hasSession: !!session,
@@ -8947,42 +9347,98 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
8947
9347
  if (!apiUrl) {
8948
9348
  throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
8949
9349
  }
8950
- const lineIdsParam = isFactoryView2 ? `line_ids=${targetLineIds.join(",")}` : `line_id=${targetLineIds[0]}`;
8951
- const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${operationalDate}&shift_id=${currentShiftDetails.shiftId}&company_id=${entityConfig.companyId}`;
8952
- console.log("[useDashboardMetrics] Calling backend API:", {
8953
- url,
8954
- apiUrl,
8955
- lineIdsParam,
8956
- operationalDate,
8957
- shiftId: currentShiftDetails.shiftId,
8958
- companyId: entityConfig.companyId
8959
- });
8960
- const response = await fetch(url, {
8961
- method: "GET",
8962
- headers: {
8963
- "Authorization": `Bearer ${session.access_token}`,
8964
- "Content-Type": "application/json"
8965
- }
8966
- });
8967
- console.log("[useDashboardMetrics] Response status:", response.status, response.statusText);
8968
- if (!response.ok) {
8969
- const errorText = await response.text();
8970
- console.error("[useDashboardMetrics] Backend API error response:", errorText);
8971
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
9350
+ const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
9351
+ const isFactory = currentLineIdToUse === factoryViewIdentifier;
9352
+ const targetLineIds = isFactory ? userAccessibleLineIds || configuredLineIds : [currentLineIdToUse];
9353
+ if (targetLineIds.length === 0) {
9354
+ throw new Error("No target line IDs available for fetching metrics.");
8972
9355
  }
8973
- const responseText = await response.text();
8974
- let backendData;
8975
- try {
8976
- backendData = JSON.parse(responseText);
8977
- } catch (parseError) {
8978
- console.error("[useDashboardMetrics] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
8979
- throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
9356
+ let allWorkspaceMetrics = [];
9357
+ let allLineMetrics = [];
9358
+ if (isFactory && shiftGroups.length > 0) {
9359
+ console.log(`[useDashboardMetrics] \u{1F3ED} Factory view: Fetching for ${shiftGroups.length} shift group(s)`);
9360
+ const metricsPromises = shiftGroups.map(async (group) => {
9361
+ const lineIdsParam = `line_ids=${group.lineIds.join(",")}`;
9362
+ const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${group.date}&shift_id=${group.shiftId}&company_id=${entityConfig.companyId}`;
9363
+ console.log(`[useDashboardMetrics] \u{1F4CA} Fetching for shift ${group.shiftId} (${group.shiftName}):`, {
9364
+ lineIds: group.lineIds,
9365
+ date: group.date
9366
+ });
9367
+ const response = await fetch(url, {
9368
+ method: "GET",
9369
+ headers: {
9370
+ "Authorization": `Bearer ${session.access_token}`,
9371
+ "Content-Type": "application/json"
9372
+ }
9373
+ });
9374
+ if (!response.ok) {
9375
+ const errorText = await response.text();
9376
+ console.error(`[useDashboardMetrics] Backend API error for shift ${group.shiftId}:`, errorText);
9377
+ throw new Error(`Backend API error (${response.status}): ${errorText}`);
9378
+ }
9379
+ const responseText = await response.text();
9380
+ try {
9381
+ return JSON.parse(responseText);
9382
+ } catch (parseError) {
9383
+ console.error("[useDashboardMetrics] Failed to parse response:", responseText.substring(0, 500));
9384
+ throw new Error(`Invalid JSON response from backend`);
9385
+ }
9386
+ });
9387
+ const results = await Promise.all(metricsPromises);
9388
+ results.forEach((result) => {
9389
+ if (result.workspace_metrics) {
9390
+ allWorkspaceMetrics.push(...result.workspace_metrics);
9391
+ }
9392
+ if (result.line_metrics) {
9393
+ allLineMetrics.push(...result.line_metrics);
9394
+ }
9395
+ });
9396
+ console.log(`[useDashboardMetrics] \u{1F4CA} Merged metrics from ${results.length} shift groups:`, {
9397
+ workspaceCount: allWorkspaceMetrics.length,
9398
+ lineMetricsCount: allLineMetrics.length
9399
+ });
9400
+ } else {
9401
+ const currentShiftDetails = shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : shiftGroups.length === 1 ? { date: shiftGroups[0].date, shiftId: shiftGroups[0].shiftId } : getCurrentShift(defaultTimezone, staticShiftConfig);
9402
+ const operationalDate = currentShiftDetails.date;
9403
+ const lineIdsParam = isFactory ? `line_ids=${targetLineIds.join(",")}` : `line_id=${targetLineIds[0]}`;
9404
+ const url = `${apiUrl}/api/dashboard/metrics?${lineIdsParam}&date=${operationalDate}&shift_id=${currentShiftDetails.shiftId}&company_id=${entityConfig.companyId}`;
9405
+ console.log("[useDashboardMetrics] Calling backend API:", {
9406
+ url,
9407
+ apiUrl,
9408
+ lineIdsParam,
9409
+ operationalDate,
9410
+ shiftId: currentShiftDetails.shiftId,
9411
+ companyId: entityConfig.companyId
9412
+ });
9413
+ const response = await fetch(url, {
9414
+ method: "GET",
9415
+ headers: {
9416
+ "Authorization": `Bearer ${session.access_token}`,
9417
+ "Content-Type": "application/json"
9418
+ }
9419
+ });
9420
+ console.log("[useDashboardMetrics] Response status:", response.status, response.statusText);
9421
+ if (!response.ok) {
9422
+ const errorText = await response.text();
9423
+ console.error("[useDashboardMetrics] Backend API error response:", errorText);
9424
+ throw new Error(`Backend API error (${response.status}): ${errorText}`);
9425
+ }
9426
+ const responseText = await response.text();
9427
+ let backendData;
9428
+ try {
9429
+ backendData = JSON.parse(responseText);
9430
+ } catch (parseError) {
9431
+ console.error("[useDashboardMetrics] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
9432
+ throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
9433
+ }
9434
+ console.log("[useDashboardMetrics] Backend response:", {
9435
+ workspaceCount: backendData.workspace_metrics?.length || 0,
9436
+ lineCount: backendData.line_metrics?.length || 0
9437
+ });
9438
+ allWorkspaceMetrics = backendData.workspace_metrics || [];
9439
+ allLineMetrics = backendData.line_metrics || [];
8980
9440
  }
8981
- console.log("[useDashboardMetrics] Backend response:", {
8982
- workspaceCount: backendData.workspace_metrics?.length || 0,
8983
- lineCount: backendData.line_metrics?.length || 0
8984
- });
8985
- const transformedWorkspaceData = (backendData.workspace_metrics || []).map((item) => ({
9441
+ const transformedWorkspaceData = allWorkspaceMetrics.map((item) => ({
8986
9442
  company_id: item.company_id || entityConfig.companyId,
8987
9443
  line_id: item.line_id,
8988
9444
  shift_id: item.shift_id,
@@ -9005,7 +9461,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
9005
9461
  });
9006
9462
  const newMetricsState = {
9007
9463
  workspaceMetrics: transformedWorkspaceData,
9008
- lineMetrics: backendData.line_metrics || []
9464
+ lineMetrics: allLineMetrics || []
9009
9465
  };
9010
9466
  console.log("[useDashboardMetrics] Setting metrics state:", {
9011
9467
  workspaceMetrics: newMetricsState.workspaceMetrics.length,
@@ -9026,9 +9482,12 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
9026
9482
  metrics2?.lineMetrics?.length || 0,
9027
9483
  companySpecificMetricsTable,
9028
9484
  entityConfig,
9029
- appTimezone,
9030
9485
  defaultTimezone,
9031
9486
  shiftConfig,
9487
+ shiftGroups,
9488
+ configuredLineIds,
9489
+ staticShiftConfig,
9490
+ userAccessibleLineIds,
9032
9491
  shiftLoading
9033
9492
  ]);
9034
9493
  const fetchAllMetricsRef = React24.useRef(fetchAllMetrics);
@@ -9052,32 +9511,70 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
9052
9511
  if (!currentLineIdToUse || !supabase || companySpecificMetricsTable.includes("unknown_company") || !entityConfig.companyId) {
9053
9512
  return;
9054
9513
  }
9055
- const currentShiftDetails = getCurrentShift(defaultTimezone, shiftConfig);
9056
- const operationalDateForSubscription = getOperationalDate(defaultTimezone);
9057
- const targetLineIds = currentLineIdToUse === (entityConfig.factoryViewId || "factory") ? userAccessibleLineIds || getConfiguredLineIds(entityConfig) : [currentLineIdToUse];
9058
- if (targetLineIds.length === 0) return;
9059
- const wsMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
9060
- const lineMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
9514
+ const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
9515
+ const isFactory = currentLineIdToUse === factoryViewIdentifier;
9061
9516
  const channels = [];
9062
- const createSubscription = (table, filter2, channelNameBase, callback) => {
9063
- const channelName = `${channelNameBase}-${Date.now()}`.replace(/[^a-zA-Z0-9_-]/g, "");
9064
- const channel = supabase.channel(channelName).on(
9065
- "postgres_changes",
9066
- { event: "*", schema, table, filter: filter2 },
9067
- (payload) => {
9068
- const payloadData = payload.new;
9069
- if (payloadData?.date === operationalDateForSubscription && payloadData?.shift_id === currentShiftDetails.shiftId) {
9070
- callback();
9517
+ if (isFactory && shiftGroups.length > 0) {
9518
+ console.log(`[useDashboardMetrics] \u{1F4E1} Setting up subscriptions for ${shiftGroups.length} shift group(s)`);
9519
+ shiftGroups.forEach((group, index) => {
9520
+ const baseFilterParts = `date=eq.${group.date},shift_id=eq.${group.shiftId}`;
9521
+ const lineIdFilterPart = `line_id=in.(${group.lineIds.map((id3) => `"${id3}"`).join(",")})`;
9522
+ const filter2 = `${baseFilterParts},${lineIdFilterPart}`;
9523
+ const wsChannelName = `dashboard-ws-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
9524
+ const wsChannel = supabase.channel(wsChannelName).on(
9525
+ "postgres_changes",
9526
+ { event: "*", schema, table: companySpecificMetricsTable, filter: filter2 },
9527
+ (payload) => {
9528
+ const payloadData = payload.new || payload.old;
9529
+ if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
9530
+ queueUpdate();
9531
+ }
9071
9532
  }
9072
- }
9073
- ).subscribe();
9074
- channels.push(channel);
9075
- };
9076
- createSubscription(companySpecificMetricsTable, wsMetricsFilter, "dashboard-ws-metrics", queueUpdate);
9077
- createSubscription(configuredLineMetricsTable, lineMetricsFilter, "dashboard-line-metrics", () => {
9078
- queueUpdate();
9079
- onLineMetricsUpdateRef.current?.();
9080
- });
9533
+ ).subscribe();
9534
+ channels.push(wsChannel);
9535
+ const lmChannelName = `dashboard-lm-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
9536
+ const lmChannel = supabase.channel(lmChannelName).on(
9537
+ "postgres_changes",
9538
+ { event: "*", schema, table: configuredLineMetricsTable, filter: filter2 },
9539
+ (payload) => {
9540
+ const payloadData = payload.new || payload.old;
9541
+ if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
9542
+ queueUpdate();
9543
+ onLineMetricsUpdateRef.current?.();
9544
+ }
9545
+ }
9546
+ ).subscribe();
9547
+ channels.push(lmChannel);
9548
+ });
9549
+ } else {
9550
+ const currentShiftDetails = shiftConfig ? getCurrentShift(defaultTimezone, shiftConfig) : getCurrentShift(defaultTimezone, staticShiftConfig);
9551
+ const operationalDateForSubscription = currentShiftDetails.date;
9552
+ const targetLineIds = [currentLineIdToUse];
9553
+ if (targetLineIds.length === 0) return;
9554
+ const baseFilterParts = `date=eq.${operationalDateForSubscription},shift_id=eq.${currentShiftDetails.shiftId}`;
9555
+ const lineIdFilterPart = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
9556
+ const wsMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
9557
+ const lineMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
9558
+ const createSubscription = (table, filter2, channelNameBase, callback) => {
9559
+ const channelName = `${channelNameBase}-${Date.now()}`.replace(/[^a-zA-Z0-9_-]/g, "");
9560
+ const channel = supabase.channel(channelName).on(
9561
+ "postgres_changes",
9562
+ { event: "*", schema, table, filter: filter2 },
9563
+ (payload) => {
9564
+ const payloadData = payload.new;
9565
+ if (payloadData?.date === operationalDateForSubscription && payloadData?.shift_id === currentShiftDetails.shiftId) {
9566
+ callback();
9567
+ }
9568
+ }
9569
+ ).subscribe();
9570
+ channels.push(channel);
9571
+ };
9572
+ createSubscription(companySpecificMetricsTable, wsMetricsFilter, "dashboard-ws-metrics", queueUpdate);
9573
+ createSubscription(configuredLineMetricsTable, lineMetricsFilter, "dashboard-line-metrics", () => {
9574
+ queueUpdate();
9575
+ onLineMetricsUpdateRef.current?.();
9576
+ });
9577
+ }
9081
9578
  return () => {
9082
9579
  channels.forEach((channel) => {
9083
9580
  supabase?.removeChannel(channel);
@@ -9091,9 +9588,10 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
9091
9588
  schema,
9092
9589
  entityConfig?.companyId,
9093
9590
  entityConfig?.factoryViewId,
9094
- appTimezone,
9095
9591
  defaultTimezone,
9096
9592
  shiftConfig,
9593
+ staticShiftConfig,
9594
+ shiftGroups,
9097
9595
  lineId,
9098
9596
  userAccessibleLineIds
9099
9597
  ]);
@@ -9111,20 +9609,37 @@ var useLineKPIs = ({ lineId, enabled }) => {
9111
9609
  const isFactoryView = lineId === (entityConfig.factoryViewId || "factory");
9112
9610
  const databaseConfig = useDatabaseConfig();
9113
9611
  const dateTimeConfig = useDateTimeConfig();
9114
- useShiftConfig();
9115
- const firstLineId = React24.useMemo(() => {
9116
- const configuredLineIds = getConfiguredLineIds(entityConfig);
9117
- return configuredLineIds.length > 0 ? configuredLineIds[0] : void 0;
9612
+ const staticShiftConfig = useShiftConfig();
9613
+ const appTimezone = useAppTimezone();
9614
+ const timezone = appTimezone || dateTimeConfig.defaultTimezone || "UTC";
9615
+ const configuredLineIds = React24.useMemo(() => {
9616
+ return getConfiguredLineIds(entityConfig);
9118
9617
  }, [entityConfig]);
9119
- const lineIdForShiftConfig = isFactoryView ? firstLineId : lineId;
9120
- const { shiftConfig: dynamicShiftConfig, isFromDatabase, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineIdForShiftConfig);
9121
- const shiftConfig = dynamicShiftConfig;
9618
+ const {
9619
+ shiftConfigMap: multiLineShiftConfigMap,
9620
+ isLoading: isMultiLineShiftConfigLoading
9621
+ } = useMultiLineShiftConfigs(
9622
+ isFactoryView ? configuredLineIds : [],
9623
+ staticShiftConfig
9624
+ );
9625
+ const {
9626
+ shiftConfig: singleLineShiftConfig,
9627
+ isFromDatabase,
9628
+ isLoading: isSingleLineShiftConfigLoading
9629
+ } = useDynamicShiftConfig(isFactoryView ? void 0 : lineId);
9630
+ const isShiftConfigLoading = isFactoryView ? isMultiLineShiftConfigLoading : isSingleLineShiftConfigLoading;
9631
+ const shiftGroups = React24.useMemo(() => {
9632
+ if (!isFactoryView) return [];
9633
+ if (isMultiLineShiftConfigLoading || multiLineShiftConfigMap.size === 0) return [];
9634
+ return groupLinesByShift(multiLineShiftConfigMap, timezone);
9635
+ }, [isFactoryView, isMultiLineShiftConfigLoading, multiLineShiftConfigMap, timezone]);
9636
+ const shiftConfig = isFactoryView ? null : singleLineShiftConfig;
9122
9637
  console.log(`[useLineKPIs] \u{1F3AF} Shift config for line ${lineId}:`, {
9123
9638
  isFactoryView,
9124
- lineIdForShiftConfig,
9125
- firstLineId,
9126
9639
  isFromDatabase,
9127
- shiftConfig
9640
+ shiftConfig,
9641
+ shiftGroupsCount: shiftGroups.length,
9642
+ shiftGroups: shiftGroups.map((g) => ({ shiftId: g.shiftId, date: g.date, lineCount: g.lineIds.length }))
9128
9643
  });
9129
9644
  const supabase = useSupabase();
9130
9645
  React24.useMemo(() => {
@@ -9138,8 +9653,6 @@ var useLineKPIs = ({ lineId, enabled }) => {
9138
9653
  const updateQueueRef = React24.useRef(false);
9139
9654
  const updateTimeoutRef = React24.useRef(null);
9140
9655
  const queueUpdateRef = React24.useRef(void 0);
9141
- const appTimezone = useAppTimezone();
9142
- const timezone = appTimezone || dateTimeConfig.defaultTimezone || "UTC";
9143
9656
  const schema = databaseConfig.schema ?? "public";
9144
9657
  const lineMetricsTable = databaseConfig.tables?.lineMetrics ?? "line_metrics";
9145
9658
  const companySpecificMetricsTable = React24.useMemo(
@@ -9158,8 +9671,6 @@ var useLineKPIs = ({ lineId, enabled }) => {
9158
9671
  setIsLoading(true);
9159
9672
  setError(null);
9160
9673
  try {
9161
- const currentShiftDetails = getCurrentShift(timezone, shiftConfig ?? void 0);
9162
- const operationalDate = currentShiftDetails.date;
9163
9674
  const { data: { session } } = await supabase.auth.getSession();
9164
9675
  if (!session?.access_token) {
9165
9676
  throw new Error("No authentication token available");
@@ -9170,33 +9681,68 @@ var useLineKPIs = ({ lineId, enabled }) => {
9170
9681
  }
9171
9682
  const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
9172
9683
  const isFactory = currentLineId === factoryViewIdentifier;
9173
- const lineParam = isFactory ? `line_ids=${getConfiguredLineIds(entityConfig).join(",")}` : `line_id=${currentLineId}`;
9174
- const response = await fetch(
9175
- `${apiUrl}/api/dashboard/line-kpis?${lineParam}&date=${operationalDate}&shift_id=${currentShiftDetails.shiftId}&company_id=${entityConfig.companyId}`,
9176
- {
9177
- headers: {
9178
- "Authorization": `Bearer ${session.access_token}`,
9179
- "Content-Type": "application/json"
9684
+ if (isFactory && shiftGroups.length > 0) {
9685
+ console.log(`[useLineKPIs] \u{1F3ED} Factory view: Fetching KPIs for ${shiftGroups.length} shift group(s)`);
9686
+ const kpiPromises = shiftGroups.map(async (group) => {
9687
+ const lineIdsParam = `line_ids=${group.lineIds.join(",")}`;
9688
+ const url = `${apiUrl}/api/dashboard/line-kpis?${lineIdsParam}&date=${group.date}&shift_id=${group.shiftId}&company_id=${entityConfig.companyId}`;
9689
+ console.log(`[useLineKPIs] \u{1F4CA} Fetching for shift ${group.shiftId} (${group.shiftName}):`, {
9690
+ lineIds: group.lineIds,
9691
+ date: group.date
9692
+ });
9693
+ const response = await fetch(url, {
9694
+ headers: {
9695
+ "Authorization": `Bearer ${session.access_token}`,
9696
+ "Content-Type": "application/json"
9697
+ }
9698
+ });
9699
+ if (!response.ok) {
9700
+ const errorText = await response.text();
9701
+ console.error(`[useLineKPIs] Backend API error for shift ${group.shiftId}:`, errorText);
9702
+ throw new Error(`Backend API error (${response.status}): ${errorText}`);
9180
9703
  }
9181
- }
9182
- );
9183
- if (!response.ok) {
9184
- const errorText = await response.text();
9185
- console.error("[useLineKPIs] Backend API error response:", errorText);
9186
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
9187
- }
9188
- const responseText = await response.text();
9189
- let backendData;
9190
- try {
9191
- backendData = JSON.parse(responseText);
9192
- } catch (parseError) {
9193
- console.error("[useLineKPIs] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
9194
- throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
9195
- }
9196
- if (backendData.kpis) {
9197
- setKPIs(backendData.kpis);
9704
+ const responseText = await response.text();
9705
+ try {
9706
+ return JSON.parse(responseText);
9707
+ } catch (parseError) {
9708
+ console.error("[useLineKPIs] Failed to parse response as JSON:", responseText.substring(0, 500));
9709
+ throw new Error(`Invalid JSON response from backend`);
9710
+ }
9711
+ });
9712
+ const results = await Promise.all(kpiPromises);
9713
+ const aggregatedKPIs = aggregateKPIResults(results);
9714
+ setKPIs(aggregatedKPIs);
9198
9715
  } else {
9199
- setKPIs(null);
9716
+ const currentShiftDetails = shiftConfig ? getCurrentShift(timezone, shiftConfig) : shiftGroups.length === 1 ? { date: shiftGroups[0].date, shiftId: shiftGroups[0].shiftId } : getCurrentShift(timezone, staticShiftConfig);
9717
+ const operationalDate = currentShiftDetails.date;
9718
+ const lineParam = isFactory ? `line_ids=${configuredLineIds.join(",")}` : `line_id=${currentLineId}`;
9719
+ const response = await fetch(
9720
+ `${apiUrl}/api/dashboard/line-kpis?${lineParam}&date=${operationalDate}&shift_id=${currentShiftDetails.shiftId}&company_id=${entityConfig.companyId}`,
9721
+ {
9722
+ headers: {
9723
+ "Authorization": `Bearer ${session.access_token}`,
9724
+ "Content-Type": "application/json"
9725
+ }
9726
+ }
9727
+ );
9728
+ if (!response.ok) {
9729
+ const errorText = await response.text();
9730
+ console.error("[useLineKPIs] Backend API error response:", errorText);
9731
+ throw new Error(`Backend API error (${response.status}): ${errorText}`);
9732
+ }
9733
+ const responseText = await response.text();
9734
+ let backendData;
9735
+ try {
9736
+ backendData = JSON.parse(responseText);
9737
+ } catch (parseError) {
9738
+ console.error("[useLineKPIs] Failed to parse response as JSON. Response text:", responseText.substring(0, 500));
9739
+ throw new Error(`Invalid JSON response from backend. Received: ${responseText.substring(0, 100)}...`);
9740
+ }
9741
+ if (backendData.kpis) {
9742
+ setKPIs(backendData.kpis);
9743
+ } else {
9744
+ setKPIs(null);
9745
+ }
9200
9746
  }
9201
9747
  } catch (err) {
9202
9748
  console.error("[useLineKPIs] Error fetching KPIs:", err);
@@ -9207,7 +9753,80 @@ var useLineKPIs = ({ lineId, enabled }) => {
9207
9753
  isFetchingRef.current = false;
9208
9754
  updateQueueRef.current = false;
9209
9755
  }
9210
- }, [timezone, shiftConfig, entityConfig, supabase, enabled, isShiftConfigLoading]);
9756
+ }, [timezone, shiftConfig, shiftGroups, configuredLineIds, staticShiftConfig, entityConfig, supabase, enabled, isShiftConfigLoading]);
9757
+ const aggregateKPIResults = (results) => {
9758
+ const validResults = results.filter((r2) => r2?.kpis);
9759
+ if (validResults.length === 0) return null;
9760
+ if (validResults.length === 1) return validResults[0].kpis;
9761
+ let totalEfficiency = 0;
9762
+ let totalEfficiencyWeight = 0;
9763
+ let totalOutputCurrent = 0;
9764
+ let totalOutputTarget = 0;
9765
+ let totalIdealOutput = 0;
9766
+ let totalUnderperformingCurrent = 0;
9767
+ let totalUnderperformingTotal = 0;
9768
+ let totalCycleTimeSum = 0;
9769
+ let totalCycleTimeCount = 0;
9770
+ let totalQualitySum = 0;
9771
+ let totalQualityCount = 0;
9772
+ validResults.forEach((result) => {
9773
+ const kpis2 = result.kpis;
9774
+ if (kpis2.efficiency?.value !== void 0) {
9775
+ const weight = kpis2.outputProgress?.current || 1;
9776
+ totalEfficiency += kpis2.efficiency.value * weight;
9777
+ totalEfficiencyWeight += weight;
9778
+ }
9779
+ if (kpis2.outputProgress) {
9780
+ totalOutputCurrent += kpis2.outputProgress.current || 0;
9781
+ totalOutputTarget += kpis2.outputProgress.target || 0;
9782
+ totalIdealOutput += kpis2.outputProgress.idealOutput || 0;
9783
+ }
9784
+ if (kpis2.underperformingWorkers) {
9785
+ totalUnderperformingCurrent += kpis2.underperformingWorkers.current || 0;
9786
+ totalUnderperformingTotal += kpis2.underperformingWorkers.total || 0;
9787
+ }
9788
+ if (kpis2.avgCycleTime?.value !== void 0) {
9789
+ totalCycleTimeSum += kpis2.avgCycleTime.value;
9790
+ totalCycleTimeCount++;
9791
+ }
9792
+ if (kpis2.qualityCompliance?.value !== void 0) {
9793
+ totalQualitySum += kpis2.qualityCompliance.value;
9794
+ totalQualityCount++;
9795
+ }
9796
+ });
9797
+ const aggregated = {
9798
+ efficiency: {
9799
+ value: totalEfficiencyWeight > 0 ? totalEfficiency / totalEfficiencyWeight : 0,
9800
+ change: 0
9801
+ // Change not meaningful when aggregating across shifts
9802
+ },
9803
+ outputProgress: {
9804
+ current: totalOutputCurrent,
9805
+ target: totalOutputTarget,
9806
+ change: 0,
9807
+ // Change not meaningful when aggregating across shifts
9808
+ idealOutput: totalIdealOutput
9809
+ },
9810
+ underperformingWorkers: {
9811
+ current: totalUnderperformingCurrent,
9812
+ total: totalUnderperformingTotal,
9813
+ change: 0
9814
+ // Change not meaningful when aggregating across shifts
9815
+ },
9816
+ avgCycleTime: {
9817
+ value: totalCycleTimeCount > 0 ? totalCycleTimeSum / totalCycleTimeCount : 0,
9818
+ change: 0
9819
+ // Change not meaningful when aggregating across shifts
9820
+ },
9821
+ qualityCompliance: {
9822
+ value: totalQualityCount > 0 ? totalQualitySum / totalQualityCount : 0,
9823
+ change: 0
9824
+ // Change not meaningful when aggregating across shifts
9825
+ }
9826
+ };
9827
+ console.log("[useLineKPIs] \u{1F4CA} Aggregated KPIs from", validResults.length, "shift groups:", aggregated);
9828
+ return aggregated;
9829
+ };
9211
9830
  const queueUpdate = React24.useCallback(() => {
9212
9831
  if (updateTimeoutRef.current) {
9213
9832
  clearTimeout(updateTimeoutRef.current);
@@ -9227,40 +9846,66 @@ var useLineKPIs = ({ lineId, enabled }) => {
9227
9846
  return;
9228
9847
  }
9229
9848
  fetchKPIs();
9230
- const currentShiftDetails = getCurrentShift(timezone, shiftConfig ?? void 0);
9231
- const operationalDate = currentShiftDetails.date;
9232
9849
  const factoryViewIdentifier = entityConfig.factoryViewId || "factory";
9233
- const targetLineIds = currentLineId === factoryViewIdentifier ? getConfiguredLineIds(entityConfig) : [currentLineId];
9234
- if (targetLineIds.length === 0) {
9235
- console.warn("[useLineKPIs] No target line IDs for subscription. LineId:", currentLineId);
9236
- return;
9237
- }
9238
- const baseFilterParts = `date=eq.${operationalDate},shift_id=eq.${currentShiftDetails.shiftId}`;
9239
- const lineIdFilterPart = `line_id=in.(${targetLineIds.join(",")})`;
9240
- const lineMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
9241
- const companyTableFilter = `${baseFilterParts},${lineIdFilterPart}`;
9850
+ const isFactory = currentLineId === factoryViewIdentifier;
9242
9851
  const activeChannels = [];
9243
- const lmChannelName = `kpi-lm-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
9244
- const lmChannel = supabase.channel(lmChannelName).on(
9245
- "postgres_changes",
9246
- { event: "*", schema, table: lineMetricsTable, filter: lineMetricsFilter },
9247
- (payload) => {
9248
- const payloadData = payload.new || payload.old;
9249
- if (payloadData?.date === operationalDate && payloadData?.shift_id === currentShiftDetails.shiftId) {
9250
- queueUpdateRef.current?.();
9852
+ if (isFactory && shiftGroups.length > 0) {
9853
+ console.log(`[useLineKPIs] \u{1F4E1} Setting up subscriptions for ${shiftGroups.length} shift group(s)`);
9854
+ shiftGroups.forEach((group, index) => {
9855
+ const baseFilterParts = `date=eq.${group.date},shift_id=eq.${group.shiftId}`;
9856
+ const lineIdFilterPart = `line_id=in.(${group.lineIds.join(",")})`;
9857
+ const filter2 = `${baseFilterParts},${lineIdFilterPart}`;
9858
+ const lmChannelName = `kpi-lm-factory-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
9859
+ const lmChannel = supabase.channel(lmChannelName).on(
9860
+ "postgres_changes",
9861
+ { event: "*", schema, table: lineMetricsTable, filter: filter2 },
9862
+ (payload) => {
9863
+ const payloadData = payload.new || payload.old;
9864
+ if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
9865
+ queueUpdateRef.current?.();
9866
+ }
9867
+ }
9868
+ ).subscribe((status, err) => {
9869
+ if (status === supabaseJs.REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === supabaseJs.REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
9870
+ console.error(`[useLineKPIs] Subscription to ${lineMetricsTable} (group ${index}) FAILED:`, status, err);
9871
+ }
9872
+ });
9873
+ activeChannels.push(lmChannel);
9874
+ if (companySpecificMetricsTable && !companySpecificMetricsTable.includes("unknown_company")) {
9875
+ const csChannelName = `kpi-cs-factory-g${index}-${group.date}-${group.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
9876
+ const csChannel = supabase.channel(csChannelName).on(
9877
+ "postgres_changes",
9878
+ { event: "*", schema, table: companySpecificMetricsTable, filter: filter2 },
9879
+ (payload) => {
9880
+ const payloadData = payload.new || payload.old;
9881
+ if (payloadData?.date === group.date && payloadData?.shift_id === group.shiftId) {
9882
+ queueUpdateRef.current?.();
9883
+ }
9884
+ }
9885
+ ).subscribe((status, err) => {
9886
+ if (status === supabaseJs.REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === supabaseJs.REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
9887
+ console.error(`[useLineKPIs] Subscription to ${companySpecificMetricsTable} (group ${index}) FAILED:`, status, err);
9888
+ }
9889
+ });
9890
+ activeChannels.push(csChannel);
9251
9891
  }
9892
+ });
9893
+ } else {
9894
+ const currentShiftDetails = shiftConfig ? getCurrentShift(timezone, shiftConfig) : getCurrentShift(timezone, staticShiftConfig);
9895
+ const operationalDate = currentShiftDetails.date;
9896
+ const targetLineIds = [currentLineId];
9897
+ if (targetLineIds.length === 0) {
9898
+ console.warn("[useLineKPIs] No target line IDs for subscription. LineId:", currentLineId);
9899
+ return;
9252
9900
  }
9253
- ).subscribe((status, err) => {
9254
- if (status === supabaseJs.REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === supabaseJs.REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
9255
- console.error(`[useLineKPIs] Subscription to ${lineMetricsTable} FAILED: ${status}`, err || "");
9256
- }
9257
- });
9258
- activeChannels.push(lmChannel);
9259
- if (companySpecificMetricsTable && !companySpecificMetricsTable.includes("unknown_company")) {
9260
- const csChannelName = `kpi-cs-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
9261
- const csChannel = supabase.channel(csChannelName).on(
9901
+ const baseFilterParts = `date=eq.${operationalDate},shift_id=eq.${currentShiftDetails.shiftId}`;
9902
+ const lineIdFilterPart = `line_id=in.(${targetLineIds.join(",")})`;
9903
+ const lineMetricsFilter = `${baseFilterParts},${lineIdFilterPart}`;
9904
+ const companyTableFilter = `${baseFilterParts},${lineIdFilterPart}`;
9905
+ const lmChannelName = `kpi-lm-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
9906
+ const lmChannel = supabase.channel(lmChannelName).on(
9262
9907
  "postgres_changes",
9263
- { event: "*", schema, table: companySpecificMetricsTable, filter: companyTableFilter },
9908
+ { event: "*", schema, table: lineMetricsTable, filter: lineMetricsFilter },
9264
9909
  (payload) => {
9265
9910
  const payloadData = payload.new || payload.old;
9266
9911
  if (payloadData?.date === operationalDate && payloadData?.shift_id === currentShiftDetails.shiftId) {
@@ -9269,10 +9914,28 @@ var useLineKPIs = ({ lineId, enabled }) => {
9269
9914
  }
9270
9915
  ).subscribe((status, err) => {
9271
9916
  if (status === supabaseJs.REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === supabaseJs.REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
9272
- console.error(`[useLineKPIs] Subscription to ${companySpecificMetricsTable} FAILED: ${status}`, err || "");
9917
+ console.error(`[useLineKPIs] Subscription to ${lineMetricsTable} FAILED: ${status}`, err || "");
9273
9918
  }
9274
9919
  });
9275
- activeChannels.push(csChannel);
9920
+ activeChannels.push(lmChannel);
9921
+ if (companySpecificMetricsTable && !companySpecificMetricsTable.includes("unknown_company")) {
9922
+ const csChannelName = `kpi-cs-${currentLineId}-${operationalDate}-${currentShiftDetails.shiftId}`.replace(/[^a-zA-Z0-9_-]/g, "");
9923
+ const csChannel = supabase.channel(csChannelName).on(
9924
+ "postgres_changes",
9925
+ { event: "*", schema, table: companySpecificMetricsTable, filter: companyTableFilter },
9926
+ (payload) => {
9927
+ const payloadData = payload.new || payload.old;
9928
+ if (payloadData?.date === operationalDate && payloadData?.shift_id === currentShiftDetails.shiftId) {
9929
+ queueUpdateRef.current?.();
9930
+ }
9931
+ }
9932
+ ).subscribe((status, err) => {
9933
+ if (status === supabaseJs.REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR || status === supabaseJs.REALTIME_SUBSCRIBE_STATES.TIMED_OUT) {
9934
+ console.error(`[useLineKPIs] Subscription to ${companySpecificMetricsTable} FAILED: ${status}`, err || "");
9935
+ }
9936
+ });
9937
+ activeChannels.push(csChannel);
9938
+ }
9276
9939
  }
9277
9940
  return () => {
9278
9941
  activeChannels.forEach((ch) => supabase.removeChannel(ch).catch((err) => console.error("[useLineKPIs] Error removing KPI channel:", err)));
@@ -9280,7 +9943,7 @@ var useLineKPIs = ({ lineId, enabled }) => {
9280
9943
  clearTimeout(updateTimeoutRef.current);
9281
9944
  }
9282
9945
  };
9283
- }, [lineId, supabase, entityConfig, schema, lineMetricsTable, companySpecificMetricsTable, timezone, shiftConfig, isShiftConfigLoading]);
9946
+ }, [lineId, supabase, entityConfig, schema, lineMetricsTable, companySpecificMetricsTable, timezone, shiftConfig, staticShiftConfig, shiftGroups, isShiftConfigLoading]);
9284
9947
  return {
9285
9948
  kpis,
9286
9949
  isLoading,
@@ -9368,12 +10031,12 @@ var useRealtimeLineMetrics = ({
9368
10031
  const companyId = entityConfig.companyId;
9369
10032
  const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
9370
10033
  const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
9371
- let enabledWorkspaceIds = [];
9372
- for (const lineId2 of targetLineIds) {
9373
- const workspaces = await workspaceService.getWorkspaces(lineId2);
9374
- const enabledIds = workspaces.filter((ws) => ws.enable === true).map((ws) => ws.id);
9375
- enabledWorkspaceIds.push(...enabledIds);
9376
- }
10034
+ const enabledWorkspaceLists = await Promise.all(
10035
+ targetLineIds.map((lineId2) => workspaceService.getEnabledWorkspaces(lineId2))
10036
+ );
10037
+ const enabledWorkspaceIds = Array.from(
10038
+ new Set(enabledWorkspaceLists.flatMap((workspaces) => workspaces.map((ws) => ws.id)))
10039
+ );
9377
10040
  const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
9378
10041
  line_id,
9379
10042
  workspace_id,
@@ -9433,8 +10096,8 @@ var useRealtimeLineMetrics = ({
9433
10096
  const companyId = entityConfig.companyId;
9434
10097
  const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
9435
10098
  const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
9436
- const workspaces = await workspaceService.getWorkspaces(lineIdRef.current);
9437
- const enabledWorkspaceIds = workspaces.filter((ws) => ws.enable === true).map((ws) => ws.id);
10099
+ const enabledWorkspaces = await workspaceService.getEnabledWorkspaces(lineIdRef.current);
10100
+ const enabledWorkspaceIds = enabledWorkspaces.map((ws) => ws.id);
9438
10101
  const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
9439
10102
  workspace_id,
9440
10103
  workspace_name,
@@ -9554,20 +10217,23 @@ var useRealtimeLineMetrics = ({
9554
10217
  const companyId = entityConfig.companyId;
9555
10218
  const metricsTablePrefix = getMetricsTablePrefix();
9556
10219
  const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
10220
+ const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
10221
+ const baseFilterParts = `date=eq.${currentDate},shift_id=eq.${shiftId}`;
10222
+ const lineIdFilterPart = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
10223
+ const filter2 = `${baseFilterParts},${lineIdFilterPart}`;
9557
10224
  const lineMetricsChannel = supabase.channel(`line-metrics-${timestamp}`).on(
9558
10225
  "postgres_changes",
9559
10226
  {
9560
10227
  event: "*",
9561
10228
  schema: "public",
9562
10229
  table: "line_metrics",
9563
- filter: `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`
10230
+ filter: filter2
9564
10231
  },
9565
10232
  async (payload) => {
9566
10233
  const payloadData = payload.new;
9567
10234
  if (process.env.NODE_ENV === "development") {
9568
10235
  console.log("Line metrics update received:", payloadData);
9569
10236
  }
9570
- const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
9571
10237
  if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
9572
10238
  queueUpdate();
9573
10239
  }
@@ -9583,14 +10249,13 @@ var useRealtimeLineMetrics = ({
9583
10249
  event: "*",
9584
10250
  schema: "public",
9585
10251
  table: metricsTable,
9586
- filter: `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`
10252
+ filter: filter2
9587
10253
  },
9588
10254
  async (payload) => {
9589
10255
  const payloadData = payload.new;
9590
10256
  if (process.env.NODE_ENV === "development") {
9591
10257
  console.log(`${metricsTablePrefix} update received:`, payloadData);
9592
10258
  }
9593
- const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
9594
10259
  if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
9595
10260
  queueUpdate();
9596
10261
  }
@@ -9601,7 +10266,7 @@ var useRealtimeLineMetrics = ({
9601
10266
  }
9602
10267
  });
9603
10268
  channelsRef.current = [lineMetricsChannel, metricsChannel];
9604
- }, [supabase, queueUpdate, urlDate, shiftId, entityConfig, dateTimeConfig.defaultTimezone]);
10269
+ }, [supabase, queueUpdate, urlDate, shiftId, entityConfig, timezone, dateTimeConfig.defaultTimezone]);
9605
10270
  const prevShiftIdRef = React24.useRef(void 0);
9606
10271
  React24.useEffect(() => {
9607
10272
  if (!lineId) return;
@@ -10380,34 +11045,16 @@ var useFactoryOverviewMetrics = (date, shiftId) => {
10380
11045
  if (lineIds.length === 0) {
10381
11046
  throw new Error("No lines configured in entityConfig");
10382
11047
  }
10383
- const { data: { session } } = await supabase.auth.getSession();
10384
- if (!session?.access_token) {
10385
- throw new Error("No authentication token available");
10386
- }
10387
- const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
10388
- if (!apiUrl) {
10389
- throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
10390
- }
10391
11048
  const params = new URLSearchParams({
10392
11049
  line_ids: lineIds.join(","),
10393
11050
  date: queryDate,
10394
11051
  shift_id: queryShiftId.toString(),
10395
11052
  company_id: entityConfig.companyId || ""
10396
11053
  });
10397
- const response = await fetch(
10398
- `${apiUrl}/api/dashboard/factory-overview?${params.toString()}`,
10399
- {
10400
- headers: {
10401
- "Authorization": `Bearer ${session.access_token}`,
10402
- "Content-Type": "application/json"
10403
- }
10404
- }
11054
+ const data = await fetchBackendJson(
11055
+ supabase,
11056
+ `/api/dashboard/factory-overview?${params.toString()}`
10405
11057
  );
10406
- if (!response.ok) {
10407
- const errorText = await response.text();
10408
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
10409
- }
10410
- const data = await response.json();
10411
11058
  setMetrics(data);
10412
11059
  } catch (err) {
10413
11060
  console.error("[useFactoryOverviewMetrics] Error fetching factory overview:", err);
@@ -10436,6 +11083,46 @@ var isInitialized = false;
10436
11083
  var isInitializing = false;
10437
11084
  var initializedWithLineIds = [];
10438
11085
  var initializationPromise = null;
11086
+ var workspaceDisplayNamesListeners = /* @__PURE__ */ new Set();
11087
+ var notifyWorkspaceDisplayNamesListeners = (changedLineId) => {
11088
+ workspaceDisplayNamesListeners.forEach((listener) => {
11089
+ try {
11090
+ listener(changedLineId);
11091
+ } catch (err) {
11092
+ console.error("[workspaceDisplayNames] Listener error", err);
11093
+ }
11094
+ });
11095
+ };
11096
+ var subscribeWorkspaceDisplayNames = (listener) => {
11097
+ workspaceDisplayNamesListeners.add(listener);
11098
+ return () => workspaceDisplayNamesListeners.delete(listener);
11099
+ };
11100
+ var getAllWorkspaceDisplayNamesSnapshot = (lineId) => {
11101
+ if (lineId && runtimeWorkspaceDisplayNames[lineId]) {
11102
+ return { ...runtimeWorkspaceDisplayNames[lineId] };
11103
+ }
11104
+ if (lineId) return {};
11105
+ const allNames = {};
11106
+ Object.entries(runtimeWorkspaceDisplayNames).forEach(([cachedLineId, lineNames]) => {
11107
+ Object.entries(lineNames).forEach(([workspaceId, displayName]) => {
11108
+ allNames[`${cachedLineId}_${workspaceId}`] = displayName;
11109
+ });
11110
+ });
11111
+ return allNames;
11112
+ };
11113
+ var upsertWorkspaceDisplayNameInCache = (params) => {
11114
+ const { lineId, workspaceId, displayName, enabled } = params;
11115
+ if (!lineId || !workspaceId) return;
11116
+ if (!runtimeWorkspaceDisplayNames[lineId]) {
11117
+ runtimeWorkspaceDisplayNames[lineId] = {};
11118
+ }
11119
+ if (enabled === false || !displayName) {
11120
+ delete runtimeWorkspaceDisplayNames[lineId][workspaceId];
11121
+ } else {
11122
+ runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
11123
+ }
11124
+ notifyWorkspaceDisplayNamesListeners(lineId);
11125
+ };
10439
11126
  function getCurrentLineIds() {
10440
11127
  try {
10441
11128
  const config = _getDashboardConfigInstance();
@@ -10475,15 +11162,20 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
10475
11162
  console.log("\u{1F504} Target line IDs for workspace filtering:", targetLineIds);
10476
11163
  runtimeWorkspaceDisplayNames = {};
10477
11164
  if (targetLineIds.length > 0) {
10478
- for (const lineId of targetLineIds) {
10479
- console.log(`\u{1F504} Fetching workspaces for line: ${lineId}`);
10480
- const lineDisplayNamesMap = await workspaceService.getWorkspaceDisplayNames(void 0, lineId);
11165
+ const results = await Promise.all(
11166
+ targetLineIds.map(async (lineId) => {
11167
+ console.log(`\u{1F504} Fetching workspaces for line: ${lineId}`);
11168
+ const lineDisplayNamesMap = await workspaceService.getWorkspaceDisplayNames(void 0, lineId);
11169
+ return { lineId, lineDisplayNamesMap };
11170
+ })
11171
+ );
11172
+ results.forEach(({ lineId, lineDisplayNamesMap }) => {
10481
11173
  runtimeWorkspaceDisplayNames[lineId] = {};
10482
11174
  lineDisplayNamesMap.forEach((displayName, workspaceId) => {
10483
11175
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
10484
11176
  });
10485
11177
  console.log(`\u2705 Stored ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
10486
- }
11178
+ });
10487
11179
  } else {
10488
11180
  console.warn("\u26A0\uFE0F No line IDs found, fetching all workspaces (less efficient)");
10489
11181
  const allWorkspacesMap = await workspaceService.getWorkspaceDisplayNames();
@@ -10496,6 +11188,7 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
10496
11188
  initializedWithLineIds = targetLineIds;
10497
11189
  console.log("\u2705 Workspace display names initialized from Supabase:", runtimeWorkspaceDisplayNames);
10498
11190
  console.log("\u2705 Initialized with line IDs:", initializedWithLineIds);
11191
+ notifyWorkspaceDisplayNamesListeners(explicitLineId);
10499
11192
  } catch (error) {
10500
11193
  console.error("\u274C Failed to initialize workspace display names from Supabase:", error);
10501
11194
  } finally {
@@ -10518,6 +11211,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
10518
11211
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
10519
11212
  });
10520
11213
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
11214
+ notifyWorkspaceDisplayNamesListeners(lineId);
10521
11215
  } catch (error) {
10522
11216
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
10523
11217
  }
@@ -10536,6 +11230,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
10536
11230
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
10537
11231
  });
10538
11232
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
11233
+ notifyWorkspaceDisplayNamesListeners(lineId);
10539
11234
  } catch (error) {
10540
11235
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
10541
11236
  }
@@ -10575,6 +11270,7 @@ var getWorkspaceDisplayName = (workspaceId, lineId) => {
10575
11270
  runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
10576
11271
  });
10577
11272
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
11273
+ notifyWorkspaceDisplayNamesListeners(lineId);
10578
11274
  }).catch((error) => {
10579
11275
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
10580
11276
  });
@@ -10615,6 +11311,7 @@ var getShortWorkspaceDisplayName = (workspaceId, lineId) => {
10615
11311
  runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
10616
11312
  });
10617
11313
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
11314
+ notifyWorkspaceDisplayNamesListeners(lineId);
10618
11315
  }).catch((error) => {
10619
11316
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
10620
11317
  });
@@ -10656,6 +11353,9 @@ var getAllWorkspaceDisplayNamesAsync = async (companyId, lineId) => {
10656
11353
  while (isInitializing) {
10657
11354
  await new Promise((resolve) => setTimeout(resolve, 100));
10658
11355
  }
11356
+ if (lineId && isInitialized && !runtimeWorkspaceDisplayNames[lineId]) {
11357
+ await preInitializeWorkspaceDisplayNames(lineId);
11358
+ }
10659
11359
  if (lineId && runtimeWorkspaceDisplayNames[lineId]) {
10660
11360
  return { ...runtimeWorkspaceDisplayNames[lineId] };
10661
11361
  }
@@ -10688,6 +11388,7 @@ var refreshWorkspaceDisplayNames = async (companyId) => {
10688
11388
  runtimeWorkspaceDisplayNames = {};
10689
11389
  isInitialized = false;
10690
11390
  await initializeWorkspaceDisplayNames();
11391
+ notifyWorkspaceDisplayNamesListeners();
10691
11392
  };
10692
11393
  var clearWorkspaceDisplayNamesCache = () => {
10693
11394
  workspaceService.clearWorkspaceDisplayNamesCache();
@@ -10696,6 +11397,7 @@ var clearWorkspaceDisplayNamesCache = () => {
10696
11397
  isInitializing = false;
10697
11398
  initializedWithLineIds = [];
10698
11399
  initializationPromise = null;
11400
+ notifyWorkspaceDisplayNamesListeners();
10699
11401
  };
10700
11402
 
10701
11403
  // src/lib/hooks/useWorkspaceDisplayNames.ts
@@ -10718,6 +11420,23 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
10718
11420
  React24.useEffect(() => {
10719
11421
  fetchDisplayNames();
10720
11422
  }, [fetchDisplayNames]);
11423
+ React24.useEffect(() => {
11424
+ const snapshot = getAllWorkspaceDisplayNamesSnapshot(lineId);
11425
+ if (Object.keys(snapshot).length > 0) {
11426
+ setDisplayNames(snapshot);
11427
+ setLoading(false);
11428
+ }
11429
+ const unsubscribe = subscribeWorkspaceDisplayNames((changedLineId) => {
11430
+ if (!lineId) {
11431
+ setDisplayNames(getAllWorkspaceDisplayNamesSnapshot());
11432
+ return;
11433
+ }
11434
+ if (!changedLineId || changedLineId === "*" || changedLineId === lineId) {
11435
+ setDisplayNames(getAllWorkspaceDisplayNamesSnapshot(lineId));
11436
+ }
11437
+ });
11438
+ return unsubscribe;
11439
+ }, [lineId]);
10721
11440
  return {
10722
11441
  displayNames,
10723
11442
  loading,
@@ -10726,7 +11445,7 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
10726
11445
  };
10727
11446
  };
10728
11447
  var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
10729
- const [displayName, setDisplayName] = React24.useState(workspaceId);
11448
+ const [displayName, setDisplayName] = React24.useState(() => getWorkspaceDisplayName(workspaceId, lineId));
10730
11449
  const [loading, setLoading] = React24.useState(true);
10731
11450
  const [error, setError] = React24.useState(null);
10732
11451
  const fetchDisplayName = React24.useCallback(async () => {
@@ -10745,6 +11464,18 @@ var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
10745
11464
  React24.useEffect(() => {
10746
11465
  fetchDisplayName();
10747
11466
  }, [fetchDisplayName]);
11467
+ React24.useEffect(() => {
11468
+ const unsubscribe = subscribeWorkspaceDisplayNames((changedLineId) => {
11469
+ if (!lineId) {
11470
+ setDisplayName(getWorkspaceDisplayName(workspaceId));
11471
+ return;
11472
+ }
11473
+ if (!changedLineId || changedLineId === "*" || changedLineId === lineId) {
11474
+ setDisplayName(getWorkspaceDisplayName(workspaceId, lineId));
11475
+ }
11476
+ });
11477
+ return unsubscribe;
11478
+ }, [workspaceId, lineId]);
10748
11479
  return {
10749
11480
  displayName,
10750
11481
  loading,
@@ -10775,6 +11506,18 @@ var useWorkspaceDisplayNamesMap = (workspaceIds, lineId, companyId) => {
10775
11506
  React24.useEffect(() => {
10776
11507
  fetchDisplayNames();
10777
11508
  }, [fetchDisplayNames]);
11509
+ React24.useEffect(() => {
11510
+ const unsubscribe = subscribeWorkspaceDisplayNames((changedLineId) => {
11511
+ if (lineId && changedLineId && changedLineId !== "*" && changedLineId !== lineId) return;
11512
+ const snapshot = getAllWorkspaceDisplayNamesSnapshot(lineId);
11513
+ const next = {};
11514
+ workspaceIds.forEach((id3) => {
11515
+ if (snapshot[id3]) next[id3] = snapshot[id3];
11516
+ });
11517
+ setDisplayNames(next);
11518
+ });
11519
+ return unsubscribe;
11520
+ }, [workspaceIds, lineId]);
10778
11521
  return {
10779
11522
  displayNames,
10780
11523
  loading,
@@ -10953,10 +11696,26 @@ var useAllWorkspaceMetrics = (options) => {
10953
11696
  const dateTimeConfig = useDateTimeConfig();
10954
11697
  const staticShiftConfig = useShiftConfig();
10955
11698
  const timezone = useAppTimezone();
10956
- const configuredLineIds = React24.useMemo(() => getConfiguredLineIds(entityConfig), [entityConfig]);
10957
- const representativeLineId = options?.allowedLineIds?.[0] || configuredLineIds[0];
10958
- const { shiftConfig: dynamicShiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(representativeLineId);
10959
- const shiftConfig = dynamicShiftConfig || staticShiftConfig;
11699
+ const configuredLineIds = React24.useMemo(() => {
11700
+ const allLineIds = getConfiguredLineIds(entityConfig);
11701
+ if (options?.allowedLineIds) {
11702
+ return allLineIds.filter((id3) => options.allowedLineIds.includes(id3));
11703
+ }
11704
+ return allLineIds;
11705
+ }, [entityConfig, options?.allowedLineIds]);
11706
+ const {
11707
+ shiftConfigMap: multiLineShiftConfigMap,
11708
+ isLoading: isShiftConfigLoading
11709
+ } = useMultiLineShiftConfigs(configuredLineIds, staticShiftConfig);
11710
+ const shiftGroups = React24.useMemo(() => {
11711
+ if (isShiftConfigLoading || multiLineShiftConfigMap.size === 0) return [];
11712
+ return groupLinesByShift(multiLineShiftConfigMap, timezone || dateTimeConfig.defaultTimezone || "Asia/Kolkata");
11713
+ }, [isShiftConfigLoading, multiLineShiftConfigMap, timezone, dateTimeConfig.defaultTimezone]);
11714
+ console.log(`[useAllWorkspaceMetrics] \u{1F4CA} Shift groups:`, shiftGroups.map((g) => ({
11715
+ shiftId: g.shiftId,
11716
+ date: g.date,
11717
+ lineIds: g.lineIds
11718
+ })));
10960
11719
  const supabase = useSupabase();
10961
11720
  const [workspaces, setWorkspaces] = React24.useState([]);
10962
11721
  const [loading, setLoading] = React24.useState(true);
@@ -10964,19 +11723,29 @@ var useAllWorkspaceMetrics = (options) => {
10964
11723
  const [initialized, setInitialized] = React24.useState(false);
10965
11724
  const fetchTimeoutRef = React24.useRef(null);
10966
11725
  const isFetchingRef = React24.useRef(false);
10967
- const queryShiftId = React24.useMemo(() => {
11726
+ const hasSpecificDateShift = options?.initialDate !== void 0 || options?.initialShiftId !== void 0;
11727
+ const fallbackQueryShiftId = React24.useMemo(() => {
10968
11728
  if (options?.initialShiftId !== void 0) {
10969
11729
  return options.initialShiftId;
10970
11730
  }
11731
+ if (shiftGroups.length > 0) {
11732
+ return shiftGroups[0].shiftId;
11733
+ }
10971
11734
  const currentShift = getCurrentShift(
10972
11735
  timezone || dateTimeConfig.defaultTimezone || "Asia/Kolkata",
10973
- shiftConfig
11736
+ staticShiftConfig
10974
11737
  );
10975
11738
  return currentShift.shiftId;
10976
- }, [options?.initialShiftId, timezone, dateTimeConfig.defaultTimezone, shiftConfig]);
10977
- const queryDate = React24.useMemo(() => {
10978
- return options?.initialDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
10979
- }, [options?.initialDate, timezone, dateTimeConfig.defaultTimezone]);
11739
+ }, [options?.initialShiftId, shiftGroups, timezone, dateTimeConfig.defaultTimezone, staticShiftConfig]);
11740
+ const fallbackQueryDate = React24.useMemo(() => {
11741
+ if (options?.initialDate) {
11742
+ return options.initialDate;
11743
+ }
11744
+ if (shiftGroups.length > 0) {
11745
+ return shiftGroups[0].date;
11746
+ }
11747
+ return getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
11748
+ }, [options?.initialDate, shiftGroups, timezone, dateTimeConfig.defaultTimezone]);
10980
11749
  const metricsTable = React24.useMemo(() => {
10981
11750
  const companyId = entityConfig.companyId;
10982
11751
  if (!companyId) return "";
@@ -10994,49 +11763,105 @@ var useAllWorkspaceMetrics = (options) => {
10994
11763
  }
10995
11764
  setError(null);
10996
11765
  try {
10997
- const allConfiguredLineIds = getConfiguredLineIds(entityConfig);
10998
- const configuredLineIds2 = options?.allowedLineIds ? allConfiguredLineIds.filter((id3) => options.allowedLineIds.includes(id3)) : allConfiguredLineIds;
10999
- let enabledWorkspaceIds = [];
11000
- for (const lineId of configuredLineIds2) {
11001
- const workspaces2 = await workspaceService.getWorkspaces(lineId);
11002
- const enabledIds = workspaces2.filter((ws) => ws.enable === true).map((ws) => ws.id);
11003
- enabledWorkspaceIds.push(...enabledIds);
11004
- }
11005
- if (enabledWorkspaceIds.length === 0) {
11766
+ const lineWorkspaceMap = /* @__PURE__ */ new Map();
11767
+ const allEnabledWorkspaceIdSet = /* @__PURE__ */ new Set();
11768
+ const perLineEnabledIds = await Promise.all(
11769
+ configuredLineIds.map(async (lineId) => {
11770
+ const enabledWorkspaces = await workspaceService.getEnabledWorkspaces(lineId);
11771
+ const enabledIds = enabledWorkspaces.map((ws) => ws.id);
11772
+ return { lineId, enabledIds };
11773
+ })
11774
+ );
11775
+ perLineEnabledIds.forEach(({ lineId, enabledIds }) => {
11776
+ lineWorkspaceMap.set(lineId, enabledIds);
11777
+ enabledIds.forEach((id3) => allEnabledWorkspaceIdSet.add(id3));
11778
+ });
11779
+ const allEnabledWorkspaceIds = Array.from(allEnabledWorkspaceIdSet);
11780
+ if (allEnabledWorkspaceIds.length === 0) {
11006
11781
  setWorkspaces([]);
11007
11782
  setInitialized(true);
11008
11783
  setLoading(false);
11009
11784
  return;
11010
11785
  }
11011
- const { data, error: fetchError } = await supabase.from(metricsTable).select(`
11012
- workspace_name,
11013
- total_output,
11014
- avg_pph,
11015
- efficiency,
11016
- workspace_id,
11017
- avg_cycle_time,
11018
- performance_score,
11019
- trend_score,
11020
- line_id,
11021
- total_day_output
11022
- `).eq("date", queryDate).eq("shift_id", queryShiftId).in("workspace_id", enabledWorkspaceIds).order("efficiency", { ascending: false });
11023
- if (fetchError) throw fetchError;
11024
- const transformedData = (data || []).map((item) => ({
11025
- company_id: entityConfig.companyId || "unknown",
11026
- line_id: item.line_id,
11027
- shift_id: queryShiftId,
11028
- date: queryDate,
11029
- workspace_uuid: item.workspace_id,
11030
- workspace_name: item.workspace_name,
11031
- action_count: item.total_output || 0,
11032
- pph: item.avg_pph || 0,
11033
- performance_score: item.performance_score || 0,
11034
- avg_cycle_time: item.avg_cycle_time || 0,
11035
- trend: item.trend_score === 1 ? 2 : 0,
11036
- predicted_output: 0,
11037
- efficiency: item.efficiency || 0,
11038
- action_threshold: item.total_day_output || 0
11039
- }));
11786
+ let transformedData = [];
11787
+ if (hasSpecificDateShift) {
11788
+ console.log(`[useAllWorkspaceMetrics] \u{1F4CA} Using specific date/shift: ${fallbackQueryDate} / ${fallbackQueryShiftId}`);
11789
+ const { data, error: fetchError } = await supabase.from(metricsTable).select(`
11790
+ workspace_name,
11791
+ total_output,
11792
+ avg_pph,
11793
+ efficiency,
11794
+ workspace_id,
11795
+ avg_cycle_time,
11796
+ performance_score,
11797
+ trend_score,
11798
+ line_id,
11799
+ total_day_output
11800
+ `).eq("date", fallbackQueryDate).eq("shift_id", fallbackQueryShiftId).in("workspace_id", allEnabledWorkspaceIds).order("efficiency", { ascending: false });
11801
+ if (fetchError) throw fetchError;
11802
+ transformedData = (data || []).map((item) => ({
11803
+ company_id: entityConfig.companyId || "unknown",
11804
+ line_id: item.line_id,
11805
+ shift_id: fallbackQueryShiftId,
11806
+ date: fallbackQueryDate,
11807
+ workspace_uuid: item.workspace_id,
11808
+ workspace_name: item.workspace_name,
11809
+ action_count: item.total_output || 0,
11810
+ pph: item.avg_pph || 0,
11811
+ performance_score: item.performance_score || 0,
11812
+ avg_cycle_time: item.avg_cycle_time || 0,
11813
+ trend: item.trend_score === 1 ? 2 : 0,
11814
+ predicted_output: 0,
11815
+ efficiency: item.efficiency || 0,
11816
+ action_threshold: item.total_day_output || 0
11817
+ }));
11818
+ } else if (shiftGroups.length > 0) {
11819
+ console.log(`[useAllWorkspaceMetrics] \u{1F3ED} Fetching per-shift: ${shiftGroups.length} group(s)`);
11820
+ const queryPromises = shiftGroups.map(async (group) => {
11821
+ const groupWorkspaceIds = group.lineIds.flatMap(
11822
+ (lineId) => lineWorkspaceMap.get(lineId) || []
11823
+ );
11824
+ if (groupWorkspaceIds.length === 0) {
11825
+ return [];
11826
+ }
11827
+ console.log(`[useAllWorkspaceMetrics] \u{1F4CA} Fetching shift ${group.shiftId} (${group.shiftName}):`, {
11828
+ lineIds: group.lineIds,
11829
+ date: group.date,
11830
+ workspaceCount: groupWorkspaceIds.length
11831
+ });
11832
+ const { data, error: fetchError } = await supabase.from(metricsTable).select(`
11833
+ workspace_name,
11834
+ total_output,
11835
+ avg_pph,
11836
+ efficiency,
11837
+ workspace_id,
11838
+ avg_cycle_time,
11839
+ performance_score,
11840
+ trend_score,
11841
+ line_id,
11842
+ total_day_output
11843
+ `).eq("date", group.date).eq("shift_id", group.shiftId).in("workspace_id", groupWorkspaceIds);
11844
+ if (fetchError) throw fetchError;
11845
+ return (data || []).map((item) => ({
11846
+ company_id: entityConfig.companyId || "unknown",
11847
+ line_id: item.line_id,
11848
+ shift_id: group.shiftId,
11849
+ date: group.date,
11850
+ workspace_uuid: item.workspace_id,
11851
+ workspace_name: item.workspace_name,
11852
+ action_count: item.total_output || 0,
11853
+ pph: item.avg_pph || 0,
11854
+ performance_score: item.performance_score || 0,
11855
+ avg_cycle_time: item.avg_cycle_time || 0,
11856
+ trend: item.trend_score === 1 ? 2 : 0,
11857
+ predicted_output: 0,
11858
+ efficiency: item.efficiency || 0,
11859
+ action_threshold: item.total_day_output || 0
11860
+ }));
11861
+ });
11862
+ const results = await Promise.all(queryPromises);
11863
+ transformedData = results.flat().sort((a, b) => (b.efficiency || 0) - (a.efficiency || 0));
11864
+ }
11040
11865
  setWorkspaces((prevWorkspaces) => {
11041
11866
  if (prevWorkspaces.length !== transformedData.length) {
11042
11867
  return transformedData;
@@ -11058,51 +11883,78 @@ var useAllWorkspaceMetrics = (options) => {
11058
11883
  setLoading(false);
11059
11884
  isFetchingRef.current = false;
11060
11885
  }
11061
- }, [queryDate, queryShiftId, metricsTable, supabase, entityConfig.companyId, entityConfig, options?.allowedLineIds, options?.enabled, isShiftConfigLoading]);
11886
+ }, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, supabase, entityConfig.companyId, configuredLineIds, options?.enabled, isShiftConfigLoading]);
11062
11887
  React24.useEffect(() => {
11063
11888
  if (!initialized) {
11064
11889
  fetchWorkspaceMetrics();
11065
11890
  }
11891
+ const validDateShiftCombos = /* @__PURE__ */ new Set();
11892
+ if (hasSpecificDateShift) {
11893
+ validDateShiftCombos.add(`${fallbackQueryDate}-${fallbackQueryShiftId}`);
11894
+ } else {
11895
+ shiftGroups.forEach((group) => {
11896
+ validDateShiftCombos.add(`${group.date}-${group.shiftId}`);
11897
+ });
11898
+ }
11066
11899
  const setupSubscription = () => {
11067
- const channel2 = supabase.channel(`all-workspace-metrics-${Date.now()}`).on(
11068
- "postgres_changes",
11900
+ if (!metricsTable || configuredLineIds.length === 0) return [];
11901
+ const groupsToSubscribe = hasSpecificDateShift || shiftGroups.length === 0 ? [
11069
11902
  {
11070
- event: "*",
11071
- schema,
11072
- table: metricsTable
11073
- },
11074
- async (payload) => {
11075
- const data = payload.new || payload.old;
11076
- if (data?.date !== queryDate || data?.shift_id !== queryShiftId) {
11077
- return;
11078
- }
11079
- if (fetchTimeoutRef.current) {
11080
- clearTimeout(fetchTimeoutRef.current);
11081
- }
11082
- fetchTimeoutRef.current = setTimeout(async () => {
11083
- if (!isFetchingRef.current) {
11084
- await fetchWorkspaceMetrics();
11903
+ date: fallbackQueryDate,
11904
+ shiftId: fallbackQueryShiftId,
11905
+ lineIds: configuredLineIds
11906
+ }
11907
+ ] : shiftGroups.map((group) => ({
11908
+ date: group.date,
11909
+ shiftId: group.shiftId,
11910
+ lineIds: group.lineIds
11911
+ }));
11912
+ return groupsToSubscribe.map((group, index) => {
11913
+ const lineIdFilterPart = `line_id=in.(${group.lineIds.map((id3) => `"${id3}"`).join(",")})`;
11914
+ const filter2 = `date=eq.${group.date},shift_id=eq.${group.shiftId},${lineIdFilterPart}`;
11915
+ const channelName = `all-workspace-metrics-g${index}-${group.date}-${group.shiftId}-${Date.now()}`.replace(
11916
+ /[^a-zA-Z0-9_-]/g,
11917
+ ""
11918
+ );
11919
+ return supabase.channel(channelName).on(
11920
+ "postgres_changes",
11921
+ {
11922
+ event: "*",
11923
+ schema,
11924
+ table: metricsTable,
11925
+ filter: filter2
11926
+ },
11927
+ async (payload) => {
11928
+ const data = payload.new || payload.old;
11929
+ const dataKey = `${data?.date}-${data?.shift_id}`;
11930
+ if (!validDateShiftCombos.has(dataKey)) {
11931
+ return;
11085
11932
  }
11086
- fetchTimeoutRef.current = null;
11087
- }, 300);
11088
- }
11089
- ).subscribe();
11090
- return channel2;
11933
+ if (fetchTimeoutRef.current) {
11934
+ clearTimeout(fetchTimeoutRef.current);
11935
+ }
11936
+ fetchTimeoutRef.current = setTimeout(async () => {
11937
+ if (!isFetchingRef.current) {
11938
+ await fetchWorkspaceMetrics();
11939
+ }
11940
+ fetchTimeoutRef.current = null;
11941
+ }, 300);
11942
+ }
11943
+ ).subscribe();
11944
+ });
11091
11945
  };
11092
- const channel = setupSubscription();
11946
+ const channels = setupSubscription();
11093
11947
  return () => {
11094
11948
  if (fetchTimeoutRef.current) {
11095
11949
  clearTimeout(fetchTimeoutRef.current);
11096
11950
  fetchTimeoutRef.current = null;
11097
11951
  }
11098
- if (channel) {
11099
- supabase.removeChannel(channel);
11100
- }
11952
+ channels.forEach((channel) => supabase.removeChannel(channel));
11101
11953
  };
11102
- }, [queryDate, queryShiftId, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId]);
11954
+ }, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId, configuredLineIds]);
11103
11955
  React24.useEffect(() => {
11104
11956
  setInitialized(false);
11105
- }, [queryDate, queryShiftId]);
11957
+ }, [fallbackQueryDate, fallbackQueryShiftId, shiftGroups]);
11106
11958
  const refreshWorkspaces = React24.useCallback(() => fetchWorkspaceMetrics(), [fetchWorkspaceMetrics]);
11107
11959
  return React24.useMemo(
11108
11960
  () => ({ workspaces, loading, error, refreshWorkspaces }),
@@ -11591,127 +12443,6 @@ function useClipTypesWithCounts(workspaceId, date, shiftId, totalOutput, options
11591
12443
  counts
11592
12444
  };
11593
12445
  }
11594
- var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
11595
- const [shiftConfigMap, setShiftConfigMap] = React24.useState(/* @__PURE__ */ new Map());
11596
- const [isLoading, setIsLoading] = React24.useState(true);
11597
- const [error, setError] = React24.useState(null);
11598
- const supabase = useSupabase();
11599
- const lineIdsKey = React24.useMemo(() => lineIds.sort().join(","), [lineIds]);
11600
- React24.useEffect(() => {
11601
- if (!lineIds || lineIds.length === 0) {
11602
- setShiftConfigMap(/* @__PURE__ */ new Map());
11603
- setIsLoading(false);
11604
- return;
11605
- }
11606
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11607
- const validLineIds = lineIds.filter((id3) => uuidRegex.test(id3));
11608
- if (validLineIds.length === 0) {
11609
- console.warn("[useMultiLineShiftConfigs] No valid line IDs provided");
11610
- setShiftConfigMap(/* @__PURE__ */ new Map());
11611
- setIsLoading(false);
11612
- return;
11613
- }
11614
- let mounted = true;
11615
- const calculateBreakDuration2 = (startTime, endTime) => {
11616
- const [sh, sm] = startTime.split(":").map(Number);
11617
- const [eh, em] = endTime.split(":").map(Number);
11618
- let startMinutes = sh * 60 + sm;
11619
- let endMinutes = eh * 60 + em;
11620
- if (endMinutes < startMinutes) {
11621
- endMinutes += 24 * 60;
11622
- }
11623
- return endMinutes - startMinutes;
11624
- };
11625
- const stripSeconds = (timeStr) => {
11626
- if (!timeStr) return timeStr;
11627
- return timeStr.substring(0, 5);
11628
- };
11629
- const buildShiftConfigFromRows = (shifts, fallback) => {
11630
- const mapped = shifts.map((shift) => ({
11631
- shiftId: shift.shift_id,
11632
- shiftName: shift.shift_name || `Shift ${shift.shift_id}`,
11633
- startTime: stripSeconds(shift.start_time),
11634
- endTime: stripSeconds(shift.end_time),
11635
- breaks: Array.isArray(shift.breaks) ? shift.breaks.map((b) => ({
11636
- startTime: b.start || b.startTime || "00:00",
11637
- endTime: b.end || b.endTime || "00:00",
11638
- duration: calculateBreakDuration2(
11639
- b.start || b.startTime || "00:00",
11640
- b.end || b.endTime || "00:00"
11641
- ),
11642
- remarks: b.remarks || b.name || ""
11643
- })) : [],
11644
- timezone: shift.timezone
11645
- }));
11646
- const day = mapped.find((s) => s.shiftId === 0);
11647
- const night = mapped.find((s) => s.shiftId === 1);
11648
- return {
11649
- shifts: mapped,
11650
- timezone: mapped[0]?.timezone,
11651
- dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallback?.dayShift,
11652
- nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallback?.nightShift,
11653
- transitionPeriodMinutes: fallback?.transitionPeriodMinutes || 0
11654
- };
11655
- };
11656
- const fetchAllConfigs = async () => {
11657
- try {
11658
- setIsLoading(true);
11659
- setError(null);
11660
- console.log(`[useMultiLineShiftConfigs] Fetching shift configs for ${validLineIds.length} lines`);
11661
- const { data, error: fetchError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").in("line_id", validLineIds);
11662
- if (fetchError) {
11663
- console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
11664
- throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
11665
- }
11666
- const lineShiftsMap = /* @__PURE__ */ new Map();
11667
- data?.forEach((row) => {
11668
- if (!lineShiftsMap.has(row.line_id)) {
11669
- lineShiftsMap.set(row.line_id, []);
11670
- }
11671
- lineShiftsMap.get(row.line_id).push(row);
11672
- });
11673
- const configMap = /* @__PURE__ */ new Map();
11674
- lineShiftsMap.forEach((shifts, lineId) => {
11675
- const config = buildShiftConfigFromRows(shifts, fallbackConfig);
11676
- configMap.set(lineId, config);
11677
- });
11678
- validLineIds.forEach((lineId) => {
11679
- if (!configMap.has(lineId) && fallbackConfig) {
11680
- console.warn(`[useMultiLineShiftConfigs] No config found for line ${lineId}, using fallback`);
11681
- configMap.set(lineId, fallbackConfig);
11682
- }
11683
- });
11684
- console.log(`[useMultiLineShiftConfigs] Built configs for ${configMap.size} lines`);
11685
- if (mounted) {
11686
- setShiftConfigMap(configMap);
11687
- setIsLoading(false);
11688
- }
11689
- } catch (err) {
11690
- console.error("[useMultiLineShiftConfigs] Error:", err);
11691
- if (mounted) {
11692
- setError(err instanceof Error ? err.message : "Failed to fetch shift configs");
11693
- if (fallbackConfig) {
11694
- const fallbackMap = /* @__PURE__ */ new Map();
11695
- validLineIds.forEach((lineId) => {
11696
- fallbackMap.set(lineId, fallbackConfig);
11697
- });
11698
- setShiftConfigMap(fallbackMap);
11699
- }
11700
- setIsLoading(false);
11701
- }
11702
- }
11703
- };
11704
- fetchAllConfigs();
11705
- return () => {
11706
- mounted = false;
11707
- };
11708
- }, [lineIdsKey, supabase]);
11709
- return {
11710
- shiftConfigMap,
11711
- isLoading,
11712
- error
11713
- };
11714
- };
11715
12446
  var MAX_RETRIES = 10;
11716
12447
  var RETRY_DELAY = 500;
11717
12448
  function useNavigation(customNavigate) {
@@ -12658,6 +13389,85 @@ function useLineSupervisor(lineId) {
12658
13389
  error
12659
13390
  };
12660
13391
  }
13392
+ var useSupervisorsByLineIds = (lineIds, options) => {
13393
+ const supabase = useSupabase();
13394
+ const enabled = options?.enabled ?? true;
13395
+ const lineIdsKey = React24.useMemo(() => (lineIds || []).slice().sort().join(","), [lineIds]);
13396
+ const targetLineIdSet = React24.useMemo(() => new Set(lineIds || []), [lineIdsKey]);
13397
+ const [supervisorsByLineId, setSupervisorsByLineId] = React24.useState(/* @__PURE__ */ new Map());
13398
+ const [supervisorNamesByLineId, setSupervisorNamesByLineId] = React24.useState(/* @__PURE__ */ new Map());
13399
+ const [isLoading, setIsLoading] = React24.useState(true);
13400
+ const [error, setError] = React24.useState(null);
13401
+ const fetchSupervisors = React24.useCallback(async () => {
13402
+ if (!enabled || !supabase) {
13403
+ setIsLoading(false);
13404
+ return;
13405
+ }
13406
+ try {
13407
+ setIsLoading(true);
13408
+ setError(null);
13409
+ const { data, error: fetchError } = await supabase.from("user_roles").select("user_id, email, properties").eq("role_level", "supervisor");
13410
+ if (fetchError) {
13411
+ throw fetchError;
13412
+ }
13413
+ const nextSupervisorsByLineId = /* @__PURE__ */ new Map();
13414
+ (data || []).forEach((row) => {
13415
+ const assignedLineIds = row?.properties?.line_id || row?.properties?.line_ids || [];
13416
+ if (!Array.isArray(assignedLineIds) || assignedLineIds.length === 0) return;
13417
+ const email = row.email || "";
13418
+ const displayName = (typeof email === "string" && email.includes("@") ? email.split("@")[0] : email) || email;
13419
+ const supervisor = {
13420
+ userId: row.user_id,
13421
+ email,
13422
+ displayName
13423
+ };
13424
+ assignedLineIds.forEach((lineId) => {
13425
+ if (typeof lineId !== "string") return;
13426
+ if (targetLineIdSet.size > 0 && !targetLineIdSet.has(lineId)) return;
13427
+ const existing = nextSupervisorsByLineId.get(lineId) || [];
13428
+ existing.push(supervisor);
13429
+ nextSupervisorsByLineId.set(lineId, existing);
13430
+ });
13431
+ });
13432
+ const nextSupervisorNamesByLineId = /* @__PURE__ */ new Map();
13433
+ nextSupervisorsByLineId.forEach((supervisors, lineId) => {
13434
+ nextSupervisorNamesByLineId.set(lineId, supervisors.map((s) => s.displayName).join(", "));
13435
+ });
13436
+ setSupervisorsByLineId(nextSupervisorsByLineId);
13437
+ setSupervisorNamesByLineId(nextSupervisorNamesByLineId);
13438
+ } catch (err) {
13439
+ setError(err instanceof Error ? err : new Error("Failed to fetch supervisors"));
13440
+ setSupervisorsByLineId(/* @__PURE__ */ new Map());
13441
+ setSupervisorNamesByLineId(/* @__PURE__ */ new Map());
13442
+ } finally {
13443
+ setIsLoading(false);
13444
+ }
13445
+ }, [enabled, supabase, targetLineIdSet]);
13446
+ React24.useEffect(() => {
13447
+ fetchSupervisors();
13448
+ }, [fetchSupervisors, lineIdsKey]);
13449
+ React24.useEffect(() => {
13450
+ if (!enabled || !supabase) return;
13451
+ let channel = null;
13452
+ channel = supabase.channel("supervisors-by-line").on(
13453
+ "postgres_changes",
13454
+ { event: "*", schema: "public", table: "user_roles", filter: "role_level=eq.supervisor" },
13455
+ () => {
13456
+ fetchSupervisors();
13457
+ }
13458
+ ).subscribe();
13459
+ return () => {
13460
+ if (channel) supabase.removeChannel(channel);
13461
+ };
13462
+ }, [enabled, supabase, fetchSupervisors]);
13463
+ return {
13464
+ supervisorNamesByLineId,
13465
+ supervisorsByLineId,
13466
+ isLoading,
13467
+ error,
13468
+ refetch: fetchSupervisors
13469
+ };
13470
+ };
12661
13471
  function useIdleTimeReasons({
12662
13472
  workspaceId,
12663
13473
  lineId,
@@ -13063,6 +13873,102 @@ var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig, li
13063
13873
  return fullName;
13064
13874
  };
13065
13875
 
13876
+ // src/lib/utils/kpis.ts
13877
+ var toNumber = (value) => {
13878
+ if (typeof value === "number" && Number.isFinite(value)) return value;
13879
+ if (typeof value === "string" && value.trim() !== "") {
13880
+ const parsed = Number(value);
13881
+ return Number.isFinite(parsed) ? parsed : 0;
13882
+ }
13883
+ return 0;
13884
+ };
13885
+ var createDefaultKPIs = () => ({
13886
+ underperformingWorkers: { current: 0, total: 0, change: 0 },
13887
+ efficiency: { value: 0, change: 0 },
13888
+ outputProgress: { current: 0, target: 0, idealOutput: 0, change: 0 },
13889
+ avgCycleTime: { value: 0, change: 0 },
13890
+ qualityCompliance: { value: 95, change: 0 }
13891
+ });
13892
+ var buildKPIsFromLineMetricsRow = (row) => {
13893
+ if (!row) return createDefaultKPIs();
13894
+ const avgEfficiency = toNumber(row.avg_efficiency);
13895
+ const avgCycleTime = toNumber(row.avg_cycle_time);
13896
+ const currentOutput = toNumber(row.current_output);
13897
+ const lineThreshold = toNumber(row.line_threshold);
13898
+ const idealOutput = toNumber(row.ideal_output) || lineThreshold || 0;
13899
+ const underperformingWorkspaces = toNumber(row.underperforming_workspaces);
13900
+ const totalWorkspaces = toNumber(row.total_workspaces);
13901
+ return {
13902
+ underperformingWorkers: {
13903
+ current: underperformingWorkspaces,
13904
+ total: totalWorkspaces,
13905
+ change: 0
13906
+ },
13907
+ efficiency: {
13908
+ value: avgEfficiency,
13909
+ change: 0
13910
+ },
13911
+ outputProgress: {
13912
+ current: currentOutput,
13913
+ target: lineThreshold,
13914
+ idealOutput,
13915
+ change: 0
13916
+ },
13917
+ avgCycleTime: {
13918
+ value: avgCycleTime,
13919
+ change: 0
13920
+ },
13921
+ qualityCompliance: {
13922
+ value: 95,
13923
+ change: 0
13924
+ }
13925
+ };
13926
+ };
13927
+ var aggregateKPIsFromLineMetricsRows = (rows) => {
13928
+ if (!rows || rows.length === 0) return createDefaultKPIs();
13929
+ const currentOutputSum = rows.reduce((sum, row) => sum + toNumber(row.current_output), 0);
13930
+ const lineThresholdSum = rows.reduce((sum, row) => sum + toNumber(row.line_threshold), 0);
13931
+ const idealOutputSum = rows.reduce(
13932
+ (sum, row) => sum + (toNumber(row.ideal_output) || toNumber(row.line_threshold)),
13933
+ 0
13934
+ );
13935
+ const totalWorkspacesAll = rows.reduce((sum, row) => sum + toNumber(row.total_workspaces), 0);
13936
+ const weightedEfficiencySum = rows.reduce(
13937
+ (sum, row) => sum + toNumber(row.avg_efficiency) * toNumber(row.total_workspaces),
13938
+ 0
13939
+ );
13940
+ const avgEfficiency = totalWorkspacesAll > 0 ? weightedEfficiencySum / totalWorkspacesAll : 0;
13941
+ const numLines = rows.length;
13942
+ const avgCycleTime = numLines > 0 ? rows.reduce((sum, row) => sum + toNumber(row.avg_cycle_time), 0) / numLines : 0;
13943
+ const totalUnderperforming = rows.reduce((sum, row) => sum + toNumber(row.underperforming_workspaces), 0);
13944
+ const totalWorkspaces = rows.reduce((sum, row) => sum + toNumber(row.total_workspaces), 0);
13945
+ return {
13946
+ underperformingWorkers: {
13947
+ current: totalUnderperforming,
13948
+ total: totalWorkspaces,
13949
+ change: 0
13950
+ },
13951
+ efficiency: {
13952
+ value: avgEfficiency,
13953
+ change: 0
13954
+ },
13955
+ outputProgress: {
13956
+ current: currentOutputSum,
13957
+ target: lineThresholdSum,
13958
+ idealOutput: idealOutputSum,
13959
+ change: 0
13960
+ },
13961
+ avgCycleTime: {
13962
+ value: avgCycleTime,
13963
+ change: 0
13964
+ },
13965
+ qualityCompliance: {
13966
+ value: 95,
13967
+ change: 0
13968
+ }
13969
+ };
13970
+ };
13971
+
13066
13972
  // ../../node_modules/clsx/dist/clsx.mjs
13067
13973
  function r(e) {
13068
13974
  var t, f, n = "";
@@ -28099,6 +29005,7 @@ if (typeof document !== "undefined") {
28099
29005
  }
28100
29006
  }
28101
29007
  var BASE_HLS_CONFIG = {
29008
+ // Keep buffer small to reduce wasted downloads on slow links
28102
29009
  maxBufferLength: 3,
28103
29010
  maxMaxBufferLength: 8,
28104
29011
  maxBufferSize: 50 * 1e3 * 1e3,
@@ -28106,10 +29013,10 @@ var BASE_HLS_CONFIG = {
28106
29013
  manifestLoadingTimeOut: 15e3,
28107
29014
  manifestLoadingMaxRetry: 3,
28108
29015
  manifestLoadingRetryDelay: 500,
28109
- levelLoadingTimeOut: 6e4,
29016
+ levelLoadingTimeOut: 2e4,
28110
29017
  levelLoadingMaxRetry: 5,
28111
29018
  levelLoadingRetryDelay: 500,
28112
- fragLoadingTimeOut: 6e4,
29019
+ fragLoadingTimeOut: 2e4,
28113
29020
  fragLoadingMaxRetry: 5,
28114
29021
  fragLoadingRetryDelay: 500,
28115
29022
  startPosition: -1,
@@ -28122,7 +29029,10 @@ var BASE_HLS_CONFIG = {
28122
29029
  abrBandWidthFactor: 0.95,
28123
29030
  abrBandWidthUpFactor: 0.7,
28124
29031
  abrMaxWithRealBitrate: false,
28125
- abrEwmaDefaultEstimate: 5e7
29032
+ // Favor a conservative first rendition on constrained networks
29033
+ abrEwmaDefaultEstimate: 1e6,
29034
+ startLevel: 0,
29035
+ capLevelToPlayerSize: true
28126
29036
  };
28127
29037
  var HlsVideoPlayer = React24.forwardRef(({
28128
29038
  src,
@@ -29206,7 +30116,7 @@ var getSupabaseClient2 = () => {
29206
30116
  }
29207
30117
  return supabaseJs.createClient(url, key);
29208
30118
  };
29209
- var getAuthToken3 = async () => {
30119
+ var getAuthToken4 = async () => {
29210
30120
  try {
29211
30121
  const supabase = getSupabaseClient2();
29212
30122
  const { data: { session } } = await supabase.auth.getSession();
@@ -29229,7 +30139,7 @@ function useWorkspaceCrop(workspaceId) {
29229
30139
  setIsLoading(true);
29230
30140
  setError(null);
29231
30141
  try {
29232
- const token = await getAuthToken3();
30142
+ const token = await getAuthToken4();
29233
30143
  if (!token) {
29234
30144
  throw new Error("Authentication required");
29235
30145
  }
@@ -30278,7 +31188,7 @@ var FileManagerFilters = ({
30278
31188
  const ROOT_CAUSE_OPTIONS = [
30279
31189
  "Operator Absent",
30280
31190
  "Operator Idle",
30281
- "Machine Maintenance",
31191
+ "Machine Downtime",
30282
31192
  "No Material"
30283
31193
  ];
30284
31194
  const getIdleTimeRootCause = React24.useCallback((clipId) => {
@@ -30318,7 +31228,7 @@ var FileManagerFilters = ({
30318
31228
  method: "POST",
30319
31229
  headers: {
30320
31230
  "Content-Type": "application/json",
30321
- "Authorization": `Bearer ${await getAuthToken4()}`
31231
+ "Authorization": `Bearer ${await getAuthToken5()}`
30322
31232
  },
30323
31233
  body: JSON.stringify({
30324
31234
  action: "clip-metadata",
@@ -30351,7 +31261,7 @@ var FileManagerFilters = ({
30351
31261
  });
30352
31262
  }
30353
31263
  }, [workspaceId, date, shift]);
30354
- const getAuthToken4 = async () => {
31264
+ const getAuthToken5 = async () => {
30355
31265
  try {
30356
31266
  const { createClient: createClient5 } = await import('@supabase/supabase-js');
30357
31267
  const supabase = createClient5(
@@ -30377,7 +31287,7 @@ var FileManagerFilters = ({
30377
31287
  method: "POST",
30378
31288
  headers: {
30379
31289
  "Content-Type": "application/json",
30380
- "Authorization": `Bearer ${await getAuthToken4()}`
31290
+ "Authorization": `Bearer ${await getAuthToken5()}`
30381
31291
  },
30382
31292
  body: JSON.stringify({
30383
31293
  action: "percentile-clips",
@@ -32021,7 +32931,7 @@ var BottlenecksContent = ({
32021
32931
  fetchClipCounts();
32022
32932
  }
32023
32933
  }, [workspaceId, effectiveDateString, effectiveShiftId, s3ClipsService, fetchClipCounts, isEffectiveShiftReady]);
32024
- const getAuthToken4 = React24.useCallback(async () => {
32934
+ const getAuthToken5 = React24.useCallback(async () => {
32025
32935
  try {
32026
32936
  const { createClient: createClient5 } = await import('@supabase/supabase-js');
32027
32937
  const supabase = createClient5(
@@ -32040,7 +32950,7 @@ var BottlenecksContent = ({
32040
32950
  const fetchTriageClips = async () => {
32041
32951
  setIsLoadingTriageClips(true);
32042
32952
  try {
32043
- const token = await getAuthToken4();
32953
+ const token = await getAuthToken5();
32044
32954
  if (!token) {
32045
32955
  console.error("[BottlenecksContent] No auth token available");
32046
32956
  return;
@@ -32088,7 +32998,7 @@ var BottlenecksContent = ({
32088
32998
  }
32089
32999
  };
32090
33000
  fetchTriageClips();
32091
- }, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken4, isEffectiveShiftReady]);
33001
+ }, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken5, isEffectiveShiftReady]);
32092
33002
  React24.useEffect(() => {
32093
33003
  if (!triageMode || triageClips.length === 0 || !session?.access_token) {
32094
33004
  return;
@@ -32419,7 +33329,20 @@ var BottlenecksContent = ({
32419
33329
  updateActiveFilter(categoryId);
32420
33330
  }
32421
33331
  try {
32422
- await loadCategoryMetadata(categoryId, false);
33332
+ const metadataPromise = loadCategoryMetadata(categoryId, false).catch((err) => {
33333
+ console.warn(`[BottlenecksContent] Metadata fetch error (non-blocking):`, err);
33334
+ return null;
33335
+ });
33336
+ const video = await s3ClipsService.getClipById(clipId);
33337
+ if (video) {
33338
+ setPendingVideo(video);
33339
+ setCurrentClipId(clipId);
33340
+ setAllVideos([video]);
33341
+ setCurrentIndex(0);
33342
+ } else {
33343
+ throw new Error(`Failed to load video data for clip ${clipId}`);
33344
+ }
33345
+ await metadataPromise;
32423
33346
  let metadataArray = categoryMetadataRef.current;
32424
33347
  const clipExistsInMetadata = metadataArray.some((clip) => clip.clipId === clipId);
32425
33348
  if (metadataArray.length === 0 || !clipExistsInMetadata) {
@@ -32436,16 +33359,7 @@ var BottlenecksContent = ({
32436
33359
  }
32437
33360
  setCurrentMetadataIndex(clickedClipIndex);
32438
33361
  currentMetadataIndexRef.current = clickedClipIndex;
32439
- const video = await s3ClipsService.getClipById(clipId);
32440
- if (video) {
32441
- setPendingVideo(video);
32442
- setCurrentClipId(clipId);
32443
- setAllVideos([video]);
32444
- setCurrentIndex(0);
32445
- console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
32446
- } else {
32447
- throw new Error(`Failed to load video data for clip ${clipId}`);
32448
- }
33362
+ console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
32449
33363
  } catch (error2) {
32450
33364
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
32451
33365
  if (isMountedRef.current) {
@@ -35332,7 +36246,7 @@ var STATIC_COLORS = {
35332
36246
  // red-600 - Critical/Urgent
35333
36247
  "No Material": "#f59e0b",
35334
36248
  // amber-500 - Warning/Supply Chain
35335
- "Machine Maintenance": "#3b82f6",
36249
+ "Machine Downtime": "#3b82f6",
35336
36250
  // blue-500 - Scheduled/Technical
35337
36251
  "Operator Idle": "#8b5cf6"
35338
36252
  // violet-500 - Low Priority/Behavioral
@@ -37717,7 +38631,7 @@ var WorkspaceWhatsAppShareButton = ({
37717
38631
  }
37718
38632
  );
37719
38633
  };
37720
- var WorkspacePdfGenerator = ({ workspace, className }) => {
38634
+ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
37721
38635
  const [isGenerating, setIsGenerating] = React24.useState(false);
37722
38636
  const entityConfig = useEntityConfig();
37723
38637
  const generatePDF = async () => {
@@ -37792,8 +38706,10 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
37792
38706
  doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
37793
38707
  };
37794
38708
  const perfOverviewStartY = 93;
38709
+ const hasIdleTimeReason = idleTimeReasons && idleTimeReasons.length > 0;
38710
+ const perfOverviewHeight = hasIdleTimeReason ? 80 : 70;
37795
38711
  doc.setFillColor(245, 245, 245);
37796
- doc.roundedRect(15, perfOverviewStartY, 180, 60, 3, 3, "F");
38712
+ doc.roundedRect(15, perfOverviewStartY, 180, perfOverviewHeight, 3, 3, "F");
37797
38713
  doc.setFontSize(18);
37798
38714
  doc.setFont("helvetica", "bold");
37799
38715
  doc.setTextColor(40, 40, 40);
@@ -37817,32 +38733,68 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
37817
38733
  doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 2);
37818
38734
  doc.setFont("helvetica", "bold");
37819
38735
  doc.text(`${workspace.avg_pph.toFixed(1)} (Standard: ${workspace.pph_threshold.toFixed(1)})`, 120, kpiStartY + kpiSpacing * 2);
38736
+ createKPIBox(kpiStartY + kpiSpacing * 3);
38737
+ doc.setFont("helvetica", "normal");
38738
+ doc.text("Total Idle Time:", 25, kpiStartY + kpiSpacing * 3);
38739
+ doc.setFont("helvetica", "bold");
38740
+ const idleTimeFormatted = formatIdleTime(workspace.idle_time);
38741
+ doc.text(idleTimeFormatted, 120, kpiStartY + kpiSpacing * 3);
38742
+ if (hasIdleTimeReason) {
38743
+ createKPIBox(kpiStartY + kpiSpacing * 4);
38744
+ doc.setFont("helvetica", "normal");
38745
+ doc.text("Top Idle Reason:", 25, kpiStartY + kpiSpacing * 4);
38746
+ doc.setFont("helvetica", "bold");
38747
+ const topReason = idleTimeReasons[0];
38748
+ const reasonName = topReason.name.replace(/_/g, " ");
38749
+ const reasonText = `${reasonName} (${topReason.value.toFixed(1)}%)`;
38750
+ doc.text(reasonText, 120, kpiStartY + kpiSpacing * 4);
38751
+ }
38752
+ const separatorBeforeHourlyY = hasIdleTimeReason ? 183 : 173;
37820
38753
  doc.setDrawColor(180, 180, 180);
37821
38754
  doc.setLineWidth(0.8);
37822
- doc.line(20, 163, 190, 163);
37823
- const hourlyPerfStartY = 168;
38755
+ doc.line(20, separatorBeforeHourlyY, 190, separatorBeforeHourlyY);
38756
+ const hourlyPerfStartY = hasIdleTimeReason ? 188 : 178;
37824
38757
  const hourlyData = workspace.hourly_action_counts || [];
37825
38758
  const hourlyTarget = workspace.pph_threshold;
37826
- const tableStartY = 199;
37827
- const rowHeight = 8;
38759
+ const pageHeight = doc.internal.pageSize.height;
38760
+ const maxContentY = pageHeight - 15;
38761
+ const baseTableStartY = hasIdleTimeReason ? 219 : 209;
38762
+ let tableStartY = baseTableStartY;
38763
+ let rowHeight = 8;
38764
+ let headerFontSize = 11;
38765
+ let contentFontSize = 11;
38766
+ let titleFontSize = 18;
37828
38767
  const bottomPadding = 8;
38768
+ const estimatedEndY = tableStartY + hourlyData.length * rowHeight + bottomPadding;
38769
+ if (estimatedEndY > maxContentY) {
38770
+ rowHeight = 6.5;
38771
+ headerFontSize = 9;
38772
+ contentFontSize = 9;
38773
+ titleFontSize = 16;
38774
+ tableStartY = hasIdleTimeReason ? 215 : 205;
38775
+ }
37829
38776
  const backgroundHeight = tableStartY - hourlyPerfStartY + hourlyData.length * rowHeight + bottomPadding;
37830
38777
  doc.setFillColor(245, 245, 245);
37831
38778
  doc.roundedRect(15, hourlyPerfStartY, 180, backgroundHeight, 3, 3, "F");
37832
- doc.setFontSize(18);
38779
+ doc.setFontSize(titleFontSize);
37833
38780
  doc.setFont("helvetica", "bold");
37834
38781
  doc.setTextColor(40, 40, 40);
37835
- doc.text("Hourly Performance", 20, 178);
38782
+ const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
38783
+ doc.text("Hourly Performance", 20, hourlyTitleY);
37836
38784
  doc.setTextColor(0, 0, 0);
37837
- doc.setFontSize(11);
38785
+ doc.setFontSize(headerFontSize);
37838
38786
  doc.setFont("helvetica", "bold");
37839
- doc.text("Time Range", 25, 188);
37840
- doc.text("Output", 95, 188);
37841
- doc.text("Target", 135, 188);
37842
- doc.text("Status", 170, 188);
38787
+ const baseHeaderY = hasIdleTimeReason ? 208 : 198;
38788
+ const headerY = titleFontSize === 16 ? hasIdleTimeReason ? 206 : 196 : baseHeaderY;
38789
+ doc.text("Time Range", 25, headerY);
38790
+ doc.text("Output", 95, headerY);
38791
+ doc.text("Target", 135, headerY);
38792
+ doc.text("Status", 170, headerY);
37843
38793
  doc.setLineWidth(0.3);
37844
38794
  doc.setDrawColor(200, 200, 200);
37845
- doc.line(20, 191, 190, 191);
38795
+ const separatorY = headerY + 3;
38796
+ doc.line(20, separatorY, 190, separatorY);
38797
+ doc.setFontSize(contentFontSize);
37846
38798
  doc.setFont("helvetica", "normal");
37847
38799
  let yPos = tableStartY;
37848
38800
  const workspaceDate = new Date(workspace.date);
@@ -45266,6 +46218,10 @@ function HomeView({
45266
46218
  const [diagnosisModalOpen, setDiagnosisModalOpen] = React24.useState(false);
45267
46219
  const [diagnoses, setDiagnoses] = React24.useState([]);
45268
46220
  const timezone = useAppTimezone();
46221
+ const { shiftConfigMap: lineShiftConfigs } = useMultiLineShiftConfigs(
46222
+ allLineIds,
46223
+ dashboardConfig?.shiftConfig
46224
+ );
45269
46225
  React24.useEffect(() => {
45270
46226
  const initDisplayNames = async () => {
45271
46227
  try {
@@ -45290,23 +46246,6 @@ function HomeView({
45290
46246
  loading: displayNamesLoading,
45291
46247
  error: displayNamesError
45292
46248
  } = useWorkspaceDisplayNames(displayNameLineId, void 0);
45293
- React24.useCallback(() => {
45294
- console.log("Refetching KPIs after line metrics update");
45295
- }, []);
45296
- const {
45297
- kpis,
45298
- isLoading: kpisLoading,
45299
- error: kpisError,
45300
- refetch: refetchKPIs
45301
- } = useLineKPIs({
45302
- lineId: selectedLineId
45303
- });
45304
- const onLineMetricsUpdate = React24.useCallback(() => {
45305
- const timer = setTimeout(() => {
45306
- refetchKPIs();
45307
- }, 1e3);
45308
- return () => clearTimeout(timer);
45309
- }, [refetchKPIs]);
45310
46249
  const {
45311
46250
  workspaceMetrics,
45312
46251
  lineMetrics,
@@ -45315,10 +46254,22 @@ function HomeView({
45315
46254
  refetch: refetchMetrics
45316
46255
  } = useDashboardMetrics({
45317
46256
  lineId: selectedLineId,
45318
- onLineMetricsUpdate,
45319
46257
  userAccessibleLineIds: allLineIds
45320
46258
  // Pass user's accessible lines for supervisor filtering
45321
46259
  });
46260
+ const kpis = React24.useMemo(() => {
46261
+ const lineMetricsRows = lineMetrics || [];
46262
+ if (selectedLineId === factoryViewId) {
46263
+ if (metricsLoading && lineMetricsRows.length === 0) return null;
46264
+ return aggregateKPIsFromLineMetricsRows(lineMetricsRows);
46265
+ }
46266
+ const row = lineMetricsRows.find((r2) => r2?.line_id === selectedLineId);
46267
+ if (!row) {
46268
+ if (metricsLoading) return null;
46269
+ return buildKPIsFromLineMetricsRow(null);
46270
+ }
46271
+ return buildKPIsFromLineMetricsRow(row);
46272
+ }, [selectedLineId, factoryViewId, lineMetrics, metricsLoading]);
45322
46273
  const {
45323
46274
  activeBreaks: allActiveBreaks,
45324
46275
  isLoading: breaksLoading,
@@ -45363,12 +46314,18 @@ function HomeView({
45363
46314
  label: "Diagnose",
45364
46315
  onClick: async () => {
45365
46316
  console.log("\u{1F50D} Investigating bottleneck:", bottleneck.log_number);
45366
- const operationalDate = getOperationalDate(
45367
- timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC"
45368
- );
45369
46317
  const tz = timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC";
45370
- const shiftResult = getCurrentShift(tz, dashboardConfig?.shiftConfig);
46318
+ const lineShiftConfig = bottleneck.line_id && lineShiftConfigs.get(bottleneck.line_id);
46319
+ const effectiveShiftConfig = lineShiftConfig || dashboardConfig?.shiftConfig;
46320
+ const shiftResult = getCurrentShift(tz, effectiveShiftConfig);
45371
46321
  const currentShift = shiftResult.shiftId;
46322
+ const operationalDate = shiftResult.date;
46323
+ console.log("\u{1F550} Using shift config for line:", {
46324
+ lineId: bottleneck.line_id,
46325
+ hasLineConfig: !!lineShiftConfig,
46326
+ shiftId: currentShift,
46327
+ date: operationalDate
46328
+ });
45372
46329
  console.log("\u{1F3AC} [Investigate] Opening clips modal with data:", {
45373
46330
  workspaceId: bottleneck.workspace_id,
45374
46331
  ticketId: bottleneck.id,
@@ -45438,12 +46395,18 @@ function HomeView({
45438
46395
  state,
45439
46396
  priority: bottleneck.priority
45440
46397
  });
45441
- const operationalDate = getOperationalDate(
45442
- timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC"
45443
- );
45444
46398
  const tz = timezone || dashboardConfig.dateTimeConfig?.defaultTimezone || "UTC";
45445
- const shiftResult = getCurrentShift(tz, dashboardConfig?.shiftConfig);
46399
+ const lineShiftConfig = bottleneck.line_id && lineShiftConfigs.get(bottleneck.line_id);
46400
+ const effectiveShiftConfig = lineShiftConfig || dashboardConfig?.shiftConfig;
46401
+ const shiftResult = getCurrentShift(tz, effectiveShiftConfig);
45446
46402
  const currentShift = shiftResult.shiftId;
46403
+ const operationalDate = shiftResult.date;
46404
+ console.log("\u{1F550} Using shift config for line:", {
46405
+ lineId: bottleneck.line_id,
46406
+ hasLineConfig: !!lineShiftConfig,
46407
+ shiftId: currentShift,
46408
+ date: operationalDate
46409
+ });
45447
46410
  setBottleneckModalData({
45448
46411
  workspaceId: bottleneck.workspace_id,
45449
46412
  workspaceName: bottleneck_workspace.workspace_name || "Unknown Workspace",
@@ -45500,7 +46463,7 @@ function HomeView({
45500
46463
  };
45501
46464
  setBottleneckNotification(errorNotification);
45502
46465
  }
45503
- }, [notificationService, timezone, dashboardConfig]);
46466
+ }, [notificationService, timezone, dashboardConfig, lineShiftConfigs]);
45504
46467
  React24.useEffect(() => {
45505
46468
  const ticketsEnabled = dashboardConfig?.ticketsConfig?.enabled ?? true;
45506
46469
  if (!ticketsEnabled) {
@@ -45625,12 +46588,10 @@ function HomeView({
45625
46588
  React24.useEffect(() => {
45626
46589
  if (metricsError) {
45627
46590
  setErrorMessage(metricsError.message);
45628
- } else if (kpisError) {
45629
- setErrorMessage(kpisError.message);
45630
46591
  } else {
45631
46592
  setErrorMessage(null);
45632
46593
  }
45633
- }, [metricsError, kpisError]);
46594
+ }, [metricsError]);
45634
46595
  const handleLineChange = React24.useCallback((value) => {
45635
46596
  setIsChangingFilter(true);
45636
46597
  setSelectedLineId(value);
@@ -45646,14 +46607,14 @@ function HomeView({
45646
46607
  }
45647
46608
  }, [LINE_FILTER_STORAGE_KEY]);
45648
46609
  React24.useEffect(() => {
45649
- if (!metricsLoading && !kpisLoading && isChangingFilter) {
46610
+ if (!metricsLoading && isChangingFilter) {
45650
46611
  if (workspaceMetrics.length > 0 || selectedLineId === factoryViewId) {
45651
46612
  setIsChangingFilter(false);
45652
46613
  }
45653
46614
  }
45654
- }, [metricsLoading, kpisLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
46615
+ }, [metricsLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
45655
46616
  React24.useEffect(() => {
45656
- if (!metricsLoading && !kpisLoading && !hasInitialDataLoaded) {
46617
+ if (!metricsLoading && !hasInitialDataLoaded) {
45657
46618
  setHasInitialDataLoaded(true);
45658
46619
  trackCoreEvent("Home View Loaded", {
45659
46620
  default_line_id: defaultLineId,
@@ -45661,7 +46622,7 @@ function HomeView({
45661
46622
  is_supervisor: isSupervisor
45662
46623
  });
45663
46624
  }
45664
- }, [metricsLoading, kpisLoading, hasInitialDataLoaded, defaultLineId, factoryViewId, isSupervisor]);
46625
+ }, [metricsLoading, hasInitialDataLoaded, defaultLineId, factoryViewId, isSupervisor]);
45665
46626
  const lineTitle = React24.useMemo(() => {
45666
46627
  return factoryName;
45667
46628
  }, [factoryName]);
@@ -45675,7 +46636,7 @@ function HomeView({
45675
46636
  ] });
45676
46637
  }, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
45677
46638
  const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
45678
- const isDataLoading = metricsLoading || kpisLoading;
46639
+ const isDataLoading = metricsLoading;
45679
46640
  if (isInitialLoading) {
45680
46641
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingPageCmp, { message: "Loading Dashboard..." });
45681
46642
  }
@@ -45819,7 +46780,7 @@ function withWorkspaceDisplayNames(Component3, options = {}) {
45819
46780
  const lineIdsKey = React24.useMemo(() => {
45820
46781
  if (!props.lineIds) return "";
45821
46782
  if (Array.isArray(props.lineIds)) {
45822
- return props.lineIds.sort().join(",");
46783
+ return props.lineIds.slice().sort().join(",");
45823
46784
  }
45824
46785
  const values = Object.values(props.lineIds).filter(Boolean);
45825
46786
  return values.sort().join(",");
@@ -46942,9 +47903,15 @@ var KPIDetailView = ({
46942
47903
  };
46943
47904
  var KPIDetailViewWithDisplayNames = withSelectedLineDisplayNames(KPIDetailView);
46944
47905
  var KPIDetailView_default = KPIDetailViewWithDisplayNames;
46945
- var LineCard = ({ line, onClick, supervisorEnabled = false }) => {
46946
- const { kpis, isLoading, error } = useLineKPIs({ lineId: line.id });
46947
- const { supervisorName } = useLineSupervisor(line.id);
47906
+ var LineCard = ({
47907
+ line,
47908
+ kpis,
47909
+ isLoading,
47910
+ error,
47911
+ onClick,
47912
+ supervisorEnabled = false,
47913
+ supervisorName
47914
+ }) => {
46948
47915
  const isOnTrack = React24__namespace.default.useMemo(() => {
46949
47916
  if (!kpis) return null;
46950
47917
  return kpis.efficiency.value > 90;
@@ -47053,6 +48020,7 @@ var KPIsOverviewView = ({
47053
48020
  const [error, setError] = React24.useState(null);
47054
48021
  const supabase = useSupabase();
47055
48022
  const dashboardConfig = useDashboardConfig();
48023
+ const entityConfig = useEntityConfig();
47056
48024
  const navigation = useNavigation(navigate);
47057
48025
  const dateTimeConfig = useDateTimeConfig();
47058
48026
  const representativeLineId = lineIds?.[0] || lines[0]?.id;
@@ -47060,6 +48028,27 @@ var KPIsOverviewView = ({
47060
48028
  const supervisorEnabled = dashboardConfig?.supervisorConfig?.enabled || false;
47061
48029
  const dbTimezone = useAppTimezone();
47062
48030
  const configuredTimezone = dbTimezone || dateTimeConfig.defaultTimezone || "UTC";
48031
+ const factoryViewId = entityConfig.factoryViewId || "factory";
48032
+ const {
48033
+ lineMetrics,
48034
+ isLoading: metricsLoading,
48035
+ error: metricsError
48036
+ } = useDashboardMetrics({
48037
+ lineId: factoryViewId,
48038
+ userAccessibleLineIds: lineIds
48039
+ });
48040
+ const defaultKPIs = React24__namespace.default.useMemo(() => createDefaultKPIs(), []);
48041
+ const kpisByLineId = React24__namespace.default.useMemo(() => {
48042
+ const map = /* @__PURE__ */ new Map();
48043
+ lineMetrics.forEach((row) => {
48044
+ if (row?.line_id) map.set(row.line_id, buildKPIsFromLineMetricsRow(row));
48045
+ });
48046
+ return map;
48047
+ }, [lineMetrics]);
48048
+ const visibleLineIds = React24__namespace.default.useMemo(() => lines.map((l) => l.id), [lines]);
48049
+ const { supervisorNamesByLineId } = useSupervisorsByLineIds(visibleLineIds, {
48050
+ enabled: supervisorEnabled && visibleLineIds.length > 0
48051
+ });
47063
48052
  React24.useEffect(() => {
47064
48053
  trackCorePageView("KPIs Overview");
47065
48054
  }, []);
@@ -47277,8 +48266,12 @@ var KPIsOverviewView = ({
47277
48266
  LineCard,
47278
48267
  {
47279
48268
  line,
48269
+ kpis: metricsError ? null : kpisByLineId.get(line.id) ?? (metricsLoading ? null : defaultKPIs),
48270
+ isLoading: metricsLoading,
48271
+ error: metricsError,
47280
48272
  onClick: (kpis) => handleLineClick(line, kpis),
47281
- supervisorEnabled
48273
+ supervisorEnabled,
48274
+ supervisorName: supervisorNamesByLineId.get(line.id) || null
47282
48275
  },
47283
48276
  line.id
47284
48277
  )) }) })
@@ -47443,6 +48436,7 @@ var LeaderboardDetailView = React24.memo(({
47443
48436
  const entityConfig = useEntityConfig();
47444
48437
  const [sortAscending, setSortAscending] = React24.useState(false);
47445
48438
  const timezone = useAppTimezone();
48439
+ const staticShiftConfig = useShiftConfig();
47446
48440
  const [isMobile, setIsMobile] = React24.useState(false);
47447
48441
  React24__namespace.default.useEffect(() => {
47448
48442
  const checkMobile = () => setIsMobile(window.innerWidth < 640);
@@ -47471,9 +48465,27 @@ var LeaderboardDetailView = React24.memo(({
47471
48465
  () => typeof shift === "number" ? shift : typeof shift === "string" ? parseInt(shift) : void 0,
47472
48466
  [shift]
47473
48467
  );
47474
- const availableLineId = Object.keys(configuredLineNames)[0];
47475
- const effectiveLineId = lineId || userAccessibleLineIds?.[0] || availableLineId;
47476
- const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(effectiveLineId);
48468
+ const configuredLineIds = React24.useMemo(() => {
48469
+ const allLineIds = getConfiguredLineIds(entityConfig);
48470
+ if (userAccessibleLineIds) {
48471
+ return allLineIds.filter((id3) => userAccessibleLineIds.includes(id3));
48472
+ }
48473
+ return allLineIds;
48474
+ }, [entityConfig, userAccessibleLineIds]);
48475
+ const shouldFetchShiftConfigs = !date && shiftId === void 0;
48476
+ const {
48477
+ shiftConfigMap: multiLineShiftConfigMap,
48478
+ isLoading: isShiftConfigLoading
48479
+ } = useMultiLineShiftConfigs(
48480
+ shouldFetchShiftConfigs ? configuredLineIds : [],
48481
+ staticShiftConfig
48482
+ );
48483
+ const shiftGroups = React24.useMemo(() => {
48484
+ if (!shouldFetchShiftConfigs || isShiftConfigLoading || multiLineShiftConfigMap.size === 0) {
48485
+ return [];
48486
+ }
48487
+ return groupLinesByShift(multiLineShiftConfigMap, timezone || "Asia/Kolkata");
48488
+ }, [shouldFetchShiftConfigs, isShiftConfigLoading, multiLineShiftConfigMap, timezone]);
47477
48489
  const {
47478
48490
  workspaces,
47479
48491
  loading: workspacesLoading,
@@ -47481,21 +48493,28 @@ var LeaderboardDetailView = React24.memo(({
47481
48493
  refreshWorkspaces
47482
48494
  } = useAllWorkspaceMetrics({
47483
48495
  initialDate: date,
47484
- initialShiftId: typeof shift === "number" ? shift : typeof shift === "string" ? parseInt(shift) : void 0,
48496
+ initialShiftId: shiftId,
47485
48497
  allowedLineIds: userAccessibleLineIds,
47486
- // Filter to user's accessible lines only
47487
48498
  enabled: !isShiftConfigLoading
47488
- // Pass enabled flag
47489
48499
  });
47490
48500
  const getShiftName = React24.useCallback((shiftId2) => {
47491
48501
  if (shiftId2 !== void 0) {
47492
- return getShiftNameById(shiftId2, timezone || "Asia/Kolkata", shiftConfig);
48502
+ return getShiftNameById(shiftId2, timezone || "Asia/Kolkata", staticShiftConfig);
47493
48503
  }
47494
- const currentShift = getCurrentShift(timezone || "Asia/Kolkata", shiftConfig);
47495
- return currentShift.shiftName || getShiftNameById(currentShift.shiftId, timezone || "Asia/Kolkata", shiftConfig);
47496
- }, [timezone, shiftConfig]);
48504
+ if (shiftGroups.length > 1) {
48505
+ const shiftNames = shiftGroups.map((g) => g.shiftName).join(" & ");
48506
+ return shiftNames;
48507
+ } else if (shiftGroups.length === 1) {
48508
+ return shiftGroups[0].shiftName;
48509
+ }
48510
+ const currentShift = getCurrentShift(timezone || "Asia/Kolkata", staticShiftConfig);
48511
+ return currentShift.shiftName || getShiftNameById(currentShift.shiftId, timezone || "Asia/Kolkata", staticShiftConfig);
48512
+ }, [timezone, staticShiftConfig, shiftGroups]);
47497
48513
  const getShiftIcon = React24.useCallback((shiftId2) => {
47498
- const effectiveShiftId = shiftId2 !== void 0 ? shiftId2 : getCurrentShift(timezone || "Asia/Kolkata", shiftConfig).shiftId;
48514
+ if (shiftId2 === void 0 && shiftGroups.length > 1) {
48515
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) });
48516
+ }
48517
+ const effectiveShiftId = shiftId2 !== void 0 ? shiftId2 : shiftGroups.length === 1 ? shiftGroups[0].shiftId : getCurrentShift(timezone || "Asia/Kolkata", staticShiftConfig).shiftId;
47499
48518
  const shiftNameLower = getShiftName(effectiveShiftId).toLowerCase();
47500
48519
  if (shiftNameLower.includes("day") || shiftNameLower.includes("morning")) {
47501
48520
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" }) });
@@ -47507,7 +48526,7 @@ var LeaderboardDetailView = React24.memo(({
47507
48526
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" }) });
47508
48527
  }
47509
48528
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) });
47510
- }, [getShiftName]);
48529
+ }, [getShiftName, shiftGroups, timezone, staticShiftConfig]);
47511
48530
  const formatDate2 = React24.useCallback((date2) => {
47512
48531
  return new Intl.DateTimeFormat("en-US", {
47513
48532
  weekday: "long",
@@ -48076,7 +49095,7 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
48076
49095
  const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
48077
49096
  return Number(hoursDiff.toFixed(1));
48078
49097
  };
48079
- var calculateBreakDuration = (startTime, endTime) => {
49098
+ var calculateBreakDuration2 = (startTime, endTime) => {
48080
49099
  const [startHour, startMinute] = startTime.split(":").map(Number);
48081
49100
  const [endHour, endMinute] = endTime.split(":").map(Number);
48082
49101
  let startMinutes = startHour * 60 + startMinute;
@@ -48092,7 +49111,7 @@ var parseBreaksFromDB = (dbBreaks) => {
48092
49111
  return dbBreaks.map((breakItem) => ({
48093
49112
  startTime: breakItem.start || breakItem.startTime || "00:00",
48094
49113
  endTime: breakItem.end || breakItem.endTime || "00:00",
48095
- duration: calculateBreakDuration(
49114
+ duration: calculateBreakDuration2(
48096
49115
  breakItem.start || breakItem.startTime || "00:00",
48097
49116
  breakItem.end || breakItem.endTime || "00:00"
48098
49117
  ),
@@ -48102,7 +49121,7 @@ var parseBreaksFromDB = (dbBreaks) => {
48102
49121
  return dbBreaks.breaks.map((breakItem) => ({
48103
49122
  startTime: breakItem.start || breakItem.startTime || "00:00",
48104
49123
  endTime: breakItem.end || breakItem.endTime || "00:00",
48105
- duration: calculateBreakDuration(
49124
+ duration: calculateBreakDuration2(
48106
49125
  breakItem.start || breakItem.startTime || "00:00",
48107
49126
  breakItem.end || breakItem.endTime || "00:00"
48108
49127
  ),
@@ -48739,6 +49758,13 @@ var ShiftsView = ({
48739
49758
  if (nightResult.error) {
48740
49759
  throw new Error(`Failed to save night shift: ${nightResult.error.message}`);
48741
49760
  }
49761
+ const updatedRows = [
49762
+ ...dayResult.data || [],
49763
+ ...nightResult.data || []
49764
+ ];
49765
+ if (updatedRows.length > 0) {
49766
+ shiftConfigStore.setFromOperatingHoursRows(lineId, updatedRows, shiftConfigStore.get(lineId));
49767
+ }
48742
49768
  setLineConfigs((prev) => prev.map(
48743
49769
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
48744
49770
  ));
@@ -49708,6 +50734,7 @@ var TargetsViewUI = ({
49708
50734
  onUpdateSelectedSKU,
49709
50735
  skuRequired = false
49710
50736
  }) => {
50737
+ const { displayNames: workspaceDisplayNames } = useWorkspaceDisplayNames();
49711
50738
  if (isLoading) {
49712
50739
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading targets..." }) }) });
49713
50740
  }
@@ -49855,7 +50882,7 @@ var TargetsViewUI = ({
49855
50882
  ] })
49856
50883
  ] }) }),
49857
50884
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-gray-100", children: line.workspaces.map((workspace) => {
49858
- const formattedName = formatWorkspaceName(workspace.name, lineId);
50885
+ const formattedName = workspaceDisplayNames[`${lineId}_${workspace.name}`] || formatWorkspaceName(workspace.name, lineId);
49859
50886
  return /* @__PURE__ */ jsxRuntime.jsxs(
49860
50887
  "div",
49861
50888
  {
@@ -50579,8 +51606,17 @@ var TargetsView = ({
50579
51606
  };
50580
51607
  const handleUpdateWorkspaceDisplayName = React24.useCallback(async (workspaceId, displayName) => {
50581
51608
  try {
50582
- await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
50583
- await forceRefreshWorkspaceDisplayNames();
51609
+ const updated = await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
51610
+ if (updated?.line_id && updated?.workspace_id) {
51611
+ upsertWorkspaceDisplayNameInCache({
51612
+ lineId: updated.line_id,
51613
+ workspaceId: updated.workspace_id,
51614
+ displayName: updated?.display_name || displayName,
51615
+ enabled: updated?.enable
51616
+ });
51617
+ } else {
51618
+ await forceRefreshWorkspaceDisplayNames();
51619
+ }
50584
51620
  sonner.toast.success("Workspace name updated successfully");
50585
51621
  } catch (error) {
50586
51622
  console.error("Error updating workspace display name:", error);
@@ -51259,7 +52295,13 @@ var WorkspaceDetailView = ({
51259
52295
  }
51260
52296
  )
51261
52297
  ] }),
51262
- activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsxRuntime.jsx(WorkspacePdfGenerator, { workspace }) }),
52298
+ activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsxRuntime.jsx(
52299
+ WorkspacePdfGenerator,
52300
+ {
52301
+ workspace,
52302
+ idleTimeReasons: idleTimeChartData
52303
+ }
52304
+ ) }),
51263
52305
  activeTab === "monthly_history" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(
51264
52306
  WorkspaceMonthlyPdfGenerator,
51265
52307
  {
@@ -52314,6 +53356,7 @@ var WorkspaceHealthView = ({
52314
53356
  const [selectedWorkspace, setSelectedWorkspace] = React24.useState(null);
52315
53357
  const [selectedDate, setSelectedDate] = React24.useState(void 0);
52316
53358
  const [selectedShiftId, setSelectedShiftId] = React24.useState(void 0);
53359
+ const [showDatePicker, setShowDatePicker] = React24.useState(false);
52317
53360
  const effectiveTimezone = timezone || "UTC";
52318
53361
  const currentShiftDetails = getCurrentShift(effectiveTimezone, shiftConfig);
52319
53362
  const operationalDate = currentShiftDetails.date;
@@ -52362,28 +53405,6 @@ var WorkspaceHealthView = ({
52362
53405
  const handleCloseDetails = React24.useCallback(() => {
52363
53406
  setSelectedWorkspace(null);
52364
53407
  }, []);
52365
- const handleExport = React24.useCallback(() => {
52366
- const csv = [
52367
- ["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
52368
- ...workspaces.map((w) => [
52369
- w.workspace_display_name || "",
52370
- w.line_name || "",
52371
- w.company_name || "",
52372
- w.status,
52373
- w.last_heartbeat,
52374
- w.consecutive_misses?.toString() || "0"
52375
- ])
52376
- ].map((row) => row.join(",")).join("\n");
52377
- const blob = new Blob([csv], { type: "text/csv" });
52378
- const url = window.URL.createObjectURL(blob);
52379
- const a = document.createElement("a");
52380
- a.href = url;
52381
- a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
52382
- document.body.appendChild(a);
52383
- a.click();
52384
- document.body.removeChild(a);
52385
- window.URL.revokeObjectURL(url);
52386
- }, [workspaces]);
52387
53408
  const getStatusIcon = (status) => {
52388
53409
  switch (status) {
52389
53410
  case "healthy":
@@ -52421,104 +53442,42 @@ var WorkspaceHealthView = ({
52421
53442
  }
52422
53443
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
52423
53444
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
52424
- /* @__PURE__ */ jsxRuntime.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: [
52425
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm:hidden", children: [
52426
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [
52427
- /* @__PURE__ */ jsxRuntime.jsx(
52428
- BackButtonMinimal,
52429
- {
52430
- onClick: () => router$1.push("/"),
52431
- text: "Back",
52432
- size: "sm",
52433
- "aria-label": "Navigate back to dashboard"
52434
- }
52435
- ),
52436
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
52437
- /* @__PURE__ */ jsxRuntime.jsx(
52438
- "button",
52439
- {
52440
- onClick: () => {
52441
- refetch();
52442
- },
52443
- className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52444
- "aria-label": "Refresh",
52445
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4" })
52446
- }
52447
- ),
52448
- /* @__PURE__ */ jsxRuntime.jsx(
52449
- "button",
52450
- {
52451
- onClick: handleExport,
52452
- className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52453
- "aria-label": "Export CSV",
52454
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" })
52455
- }
52456
- )
52457
- ] })
52458
- ] }),
52459
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2", children: [
52460
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-gray-900", children: "System Health" }),
52461
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2 w-2", children: [
52462
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
52463
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-emerald-500" })
52464
- ] })
52465
- ] }) })
53445
+ /* @__PURE__ */ jsxRuntime.jsx("header", { className: "sticky top-0 z-10 bg-white border-b border-gray-200 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 sm:px-6 lg:px-8 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
53446
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(
53447
+ BackButtonMinimal,
53448
+ {
53449
+ onClick: () => router$1.push("/"),
53450
+ text: "Back",
53451
+ size: "default",
53452
+ "aria-label": "Navigate back to dashboard"
53453
+ }
53454
+ ) }),
53455
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
53456
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "System Health" }),
53457
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center", children: "Monitor workspace connectivity and operational status" })
52466
53458
  ] }),
52467
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
52468
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(
52469
- BackButtonMinimal,
53459
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
53460
+ /* @__PURE__ */ jsxRuntime.jsx(
53461
+ "button",
52470
53462
  {
52471
- onClick: () => router$1.push("/"),
52472
- text: "Back",
52473
- size: "default",
52474
- "aria-label": "Navigate back to dashboard"
53463
+ onClick: () => setShowDatePicker(!showDatePicker),
53464
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
53465
+ "aria-label": "Select date",
53466
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "h-5 w-5" })
52475
53467
  }
52476
- ) }),
52477
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2 max-w-[calc(100%-200px)]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
52478
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg md:text-xl lg:text-2xl xl:text-3xl font-semibold text-gray-900 truncate", children: "System Health" }),
52479
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
52480
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
52481
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
52482
- ] })
52483
- ] }) }),
52484
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 flex gap-2", children: [
52485
- /* @__PURE__ */ jsxRuntime.jsx(
52486
- "button",
52487
- {
52488
- onClick: () => {
52489
- refetch();
52490
- },
52491
- className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52492
- "aria-label": "Refresh",
52493
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
52494
- }
52495
- ),
52496
- /* @__PURE__ */ jsxRuntime.jsx(
52497
- "button",
52498
- {
52499
- onClick: handleExport,
52500
- className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52501
- "aria-label": "Export CSV",
52502
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
52503
- }
52504
- )
52505
- ] }),
52506
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
52507
- ] }) }),
52508
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 sm:mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
52509
- HealthDateShiftSelector,
52510
- {
52511
- selectedDate: selectedDate || operationalDate,
52512
- selectedShiftId: selectedShiftId ?? currentShiftDetails.shiftId,
52513
- shiftConfig,
52514
- timezone: effectiveTimezone,
52515
- onDateChange: setSelectedDate,
52516
- onShiftChange: setSelectedShiftId,
52517
- isCurrentShift: isViewingCurrentShift,
52518
- onReturnToLive: handleReturnToLive
52519
- }
52520
- ) })
52521
- ] }),
53468
+ ),
53469
+ /* @__PURE__ */ jsxRuntime.jsx(
53470
+ "button",
53471
+ {
53472
+ onClick: () => refetch(),
53473
+ disabled: loading,
53474
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
53475
+ "aria-label": "Refresh",
53476
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-5 w-5 ${loading ? "animate-spin" : ""}` })
53477
+ }
53478
+ )
53479
+ ] })
53480
+ ] }) }) }),
52522
53481
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
52523
53482
  summary && /* @__PURE__ */ jsxRuntime.jsxs(
52524
53483
  motion.div,
@@ -52594,6 +53553,72 @@ var WorkspaceHealthView = ({
52594
53553
  )
52595
53554
  ] })
52596
53555
  ] }),
53556
+ showDatePicker && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
53557
+ /* @__PURE__ */ jsxRuntime.jsx(
53558
+ "div",
53559
+ {
53560
+ className: "fixed inset-0 bg-black/20 z-40",
53561
+ onClick: () => setShowDatePicker(false)
53562
+ }
53563
+ ),
53564
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed top-20 right-4 md:right-8 z-50 bg-white rounded-lg shadow-xl border border-gray-200 p-4 w-80", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
53565
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
53566
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Date" }),
53567
+ /* @__PURE__ */ jsxRuntime.jsx(
53568
+ "input",
53569
+ {
53570
+ type: "date",
53571
+ value: selectedDate || operationalDate,
53572
+ max: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
53573
+ onChange: (e) => {
53574
+ setSelectedDate(e.target.value);
53575
+ },
53576
+ className: "w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
53577
+ }
53578
+ )
53579
+ ] }),
53580
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
53581
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Shift" }),
53582
+ /* @__PURE__ */ jsxRuntime.jsx(
53583
+ "select",
53584
+ {
53585
+ value: selectedShiftId ?? currentShiftDetails.shiftId,
53586
+ onChange: (e) => setSelectedShiftId(Number(e.target.value)),
53587
+ className: "w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white",
53588
+ children: (shiftConfig?.shifts || []).map((shift) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: shift.shiftId, children: [
53589
+ shift.shiftName,
53590
+ " (",
53591
+ shift.startTime,
53592
+ " - ",
53593
+ shift.endTime,
53594
+ ")"
53595
+ ] }, shift.shiftId))
53596
+ }
53597
+ )
53598
+ ] }),
53599
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-2", children: [
53600
+ !isViewingCurrentShift && /* @__PURE__ */ jsxRuntime.jsx(
53601
+ "button",
53602
+ {
53603
+ onClick: () => {
53604
+ handleReturnToLive();
53605
+ setShowDatePicker(false);
53606
+ },
53607
+ className: "flex-1 px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors",
53608
+ children: "Return to Live"
53609
+ }
53610
+ ),
53611
+ /* @__PURE__ */ jsxRuntime.jsx(
53612
+ "button",
53613
+ {
53614
+ onClick: () => setShowDatePicker(false),
53615
+ className: "flex-1 px-3 py-2 text-sm text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors",
53616
+ children: "Done"
53617
+ }
53618
+ )
53619
+ ] })
53620
+ ] }) })
53621
+ ] }),
52597
53622
  /* @__PURE__ */ jsxRuntime.jsx(
52598
53623
  WorkspaceUptimeDetailModal_default,
52599
53624
  {
@@ -53743,7 +54768,7 @@ function DailyBarChart({
53743
54768
  axisLine: false,
53744
54769
  tick: (props) => {
53745
54770
  const { x, y, payload } = props;
53746
- if (payload.value === 0) return null;
54771
+ if (payload.value === 0) return /* @__PURE__ */ jsxRuntime.jsx("g", {});
53747
54772
  const hours = Math.round(payload.value / (1e3 * 60 * 60) * 10) / 10;
53748
54773
  return /* @__PURE__ */ jsxRuntime.jsxs("text", { x, y, dy: 4, textAnchor: "end", className: "text-xs fill-gray-400", children: [
53749
54774
  hours,
@@ -54098,7 +55123,7 @@ var UserManagementTable = ({
54098
55123
  }
54099
55124
  ),
54100
55125
  /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Assignments" }),
54101
- showUsageStats && /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Weekly usage" }),
55126
+ showUsageStats && /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Average Daily usage" }),
54102
55127
  /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Actions" })
54103
55128
  ] }) }),
54104
55129
  /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: filteredAndSortedUsers.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: showUsageStats ? 5 : 4, className: "px-6 py-12", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center text-gray-400 gap-2", children: [
@@ -54110,12 +55135,25 @@ var UserManagementTable = ({
54110
55135
  const canChangeRole = permissions.canChangeRole(user);
54111
55136
  const canRemove = permissions.canRemoveUser(user);
54112
55137
  const hasActions = canChangeRole || canRemove;
55138
+ const canShowUsageModal = showUsageStats && (user.role_level === "plant_head" || user.role_level === "supervisor");
55139
+ const handleRowClick = (e) => {
55140
+ const target = e.target;
55141
+ if (target.closest("button") || target.closest("select") || target.closest("input") || target.closest('[role="button"]') || target.closest("[data-interactive]")) {
55142
+ return;
55143
+ }
55144
+ if (canShowUsageModal) {
55145
+ setUsageDetailUserId(user.user_id);
55146
+ setShowUsageDetailModal(true);
55147
+ }
55148
+ };
54113
55149
  return /* @__PURE__ */ jsxRuntime.jsxs(
54114
55150
  "tr",
54115
55151
  {
55152
+ onClick: handleRowClick,
54116
55153
  className: cn(
54117
- "hover:bg-gray-50 transition-colors",
54118
- isDeactivated && "opacity-60"
55154
+ "transition-all duration-150",
55155
+ isDeactivated && "opacity-60",
55156
+ canShowUsageModal ? "cursor-pointer hover:bg-blue-50/50 hover:shadow-sm" : "hover:bg-gray-50"
54119
55157
  ),
54120
55158
  children: [
54121
55159
  /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
@@ -54181,10 +55219,7 @@ var UserManagementTable = ({
54181
55219
  setShowUsageDetailModal(true);
54182
55220
  },
54183
55221
  className: "group flex items-center gap-3 hover:bg-gray-50 -mx-2 px-2 py-1.5 rounded-lg transition-all duration-200",
54184
- children: isUsageLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse bg-gray-200 h-5 w-20 rounded" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
54185
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-base font-semibold ${avgDailyUsage?.[user.user_id] && avgDailyUsage[user.user_id] > 0 ? "text-gray-900 group-hover:text-blue-600" : "text-gray-400"} transition-colors`, children: formatDuration3(avgDailyUsage?.[user.user_id] || 0) }),
54186
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowRight, { className: "w-4 h-4 text-gray-400 group-hover:text-blue-600 group-hover:translate-x-0.5 transition-all" })
54187
- ] })
55222
+ children: isUsageLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse bg-gray-200 h-5 w-20 rounded" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-base font-semibold ${avgDailyUsage?.[user.user_id] && avgDailyUsage[user.user_id] > 0 ? "text-gray-900 group-hover:text-blue-600" : "text-gray-400"} transition-colors`, children: formatDuration3(avgDailyUsage?.[user.user_id] || 0) })
54188
55223
  }
54189
55224
  ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-400", children: "-" }) }),
54190
55225
  /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right", children: hasActions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -54232,25 +55267,50 @@ var UserManagementTable = ({
54232
55267
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-1", children: [
54233
55268
  (() => {
54234
55269
  const user = users.find((u) => u.user_id === openActionMenuId);
54235
- const isCurrentUser = user?.user_id === currentUserId;
54236
- const canChangeRole = user && permissions.canChangeRole(user);
54237
- return canChangeRole && /* @__PURE__ */ jsxRuntime.jsxs(
55270
+ const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
55271
+ return canShowUsage && /* @__PURE__ */ jsxRuntime.jsxs(
54238
55272
  "button",
54239
55273
  {
54240
55274
  onClick: () => {
54241
55275
  if (user) {
54242
- handleChangeRole(user);
55276
+ setUsageDetailUserId(user.user_id);
55277
+ setShowUsageDetailModal(true);
55278
+ handleCloseActionMenu();
54243
55279
  }
54244
55280
  },
54245
- className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
54246
- disabled: isCurrentUser,
55281
+ className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
54247
55282
  children: [
54248
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserCog, { className: "w-4 h-4" }),
54249
- "Change Role"
55283
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BarChart3, { className: "w-4 h-4" }),
55284
+ "View Detailed Usage"
54250
55285
  ]
54251
55286
  }
54252
55287
  );
54253
55288
  })(),
55289
+ (() => {
55290
+ const user = users.find((u) => u.user_id === openActionMenuId);
55291
+ const isCurrentUser = user?.user_id === currentUserId;
55292
+ const canChangeRole = user && permissions.canChangeRole(user);
55293
+ const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
55294
+ return canChangeRole && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
55295
+ canShowUsage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-200 my-1" }),
55296
+ /* @__PURE__ */ jsxRuntime.jsxs(
55297
+ "button",
55298
+ {
55299
+ onClick: () => {
55300
+ if (user) {
55301
+ handleChangeRole(user);
55302
+ }
55303
+ },
55304
+ className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
55305
+ disabled: isCurrentUser,
55306
+ children: [
55307
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserCog, { className: "w-4 h-4" }),
55308
+ "Change Role"
55309
+ ]
55310
+ }
55311
+ )
55312
+ ] });
55313
+ })(),
54254
55314
  (() => {
54255
55315
  const user = users.find((u) => u.user_id === openActionMenuId);
54256
55316
  const isCurrentUser = user?.user_id === currentUserId;
@@ -54792,7 +55852,7 @@ var TeamManagementView = ({
54792
55852
  }, {});
54793
55853
  }, [usageData, usageDateRange.daysElapsed]);
54794
55854
  const pageTitle = user?.role_level === "optifye" ? "Platform Users" : "Team Management";
54795
- const pageDescription = user?.role_level === "optifye" ? "Manage users across all companies" : user?.role_level === "owner" ? "Manage your team members" : "Manage supervisors in your factory";
55855
+ const pageDescription = user?.role_level === "optifye" ? "Manage users across all companies" : user?.role_level === "owner" ? "Manage team members and view their daily usage" : "Manage supervisors in your factory";
54796
55856
  const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "plant_head"].includes(user.role_level) : false;
54797
55857
  const loadData = React24.useCallback(async () => {
54798
55858
  if (!supabase) {
@@ -56844,10 +57904,13 @@ exports.WorkspacePdfExportButton = WorkspacePdfExportButton;
56844
57904
  exports.WorkspacePdfGenerator = WorkspacePdfGenerator;
56845
57905
  exports.WorkspaceWhatsAppShareButton = WorkspaceWhatsAppShareButton;
56846
57906
  exports.actionService = actionService;
57907
+ exports.aggregateKPIsFromLineMetricsRows = aggregateKPIsFromLineMetricsRows;
56847
57908
  exports.apiUtils = apiUtils;
57909
+ exports.areAllLinesOnSameShift = areAllLinesOnSameShift;
56848
57910
  exports.authCoreService = authCoreService;
56849
57911
  exports.authOTPService = authOTPService;
56850
57912
  exports.authRateLimitService = authRateLimitService;
57913
+ exports.buildKPIsFromLineMetricsRow = buildKPIsFromLineMetricsRow;
56851
57914
  exports.checkRateLimit = checkRateLimit2;
56852
57915
  exports.clearAllRateLimits = clearAllRateLimits2;
56853
57916
  exports.clearRateLimit = clearRateLimit2;
@@ -56855,6 +57918,7 @@ exports.clearS3VideoCache = clearS3VideoCache;
56855
57918
  exports.clearS3VideoFromCache = clearS3VideoFromCache;
56856
57919
  exports.clearWorkspaceDisplayNamesCache = clearWorkspaceDisplayNamesCache;
56857
57920
  exports.cn = cn;
57921
+ exports.createDefaultKPIs = createDefaultKPIs;
56858
57922
  exports.createInvitationService = createInvitationService;
56859
57923
  exports.createLinesService = createLinesService;
56860
57924
  exports.createSessionTracker = createSessionTracker;
@@ -56879,6 +57943,7 @@ exports.fromUrlFriendlyName = fromUrlFriendlyName;
56879
57943
  exports.getAllLineDisplayNames = getAllLineDisplayNames;
56880
57944
  exports.getAllThreadMessages = getAllThreadMessages;
56881
57945
  exports.getAllWorkspaceDisplayNamesAsync = getAllWorkspaceDisplayNamesAsync;
57946
+ exports.getAllWorkspaceDisplayNamesSnapshot = getAllWorkspaceDisplayNamesSnapshot;
56882
57947
  exports.getAnonClient = getAnonClient;
56883
57948
  exports.getAvailableShiftIds = getAvailableShiftIds;
56884
57949
  exports.getBrowserName = getBrowserName;
@@ -56890,6 +57955,7 @@ exports.getConfiguredLineIds = getConfiguredLineIds;
56890
57955
  exports.getCoreSessionRecordingProperties = getCoreSessionRecordingProperties;
56891
57956
  exports.getCoreSessionReplayUrl = getCoreSessionReplayUrl;
56892
57957
  exports.getCurrentShift = getCurrentShift;
57958
+ exports.getCurrentShiftForLine = getCurrentShiftForLine;
56893
57959
  exports.getCurrentTimeInZone = getCurrentTimeInZone;
56894
57960
  exports.getDashboardHeaderTimeInZone = getDashboardHeaderTimeInZone;
56895
57961
  exports.getDaysDifferenceInZone = getDaysDifferenceInZone;
@@ -56912,6 +57978,7 @@ exports.getShortWorkspaceDisplayNameAsync = getShortWorkspaceDisplayNameAsync;
56912
57978
  exports.getStoredWorkspaceMappings = getStoredWorkspaceMappings;
56913
57979
  exports.getSubscriptionManager = getSubscriptionManager;
56914
57980
  exports.getThreadMessages = getThreadMessages;
57981
+ exports.getUniformShiftGroup = getUniformShiftGroup;
56915
57982
  exports.getUserThreads = getUserThreads;
56916
57983
  exports.getUserThreadsPaginated = getUserThreadsPaginated;
56917
57984
  exports.getWorkspaceDisplayName = getWorkspaceDisplayName;
@@ -56919,6 +57986,7 @@ exports.getWorkspaceDisplayNameAsync = getWorkspaceDisplayNameAsync;
56919
57986
  exports.getWorkspaceDisplayNamesMap = getWorkspaceDisplayNamesMap;
56920
57987
  exports.getWorkspaceFromUrl = getWorkspaceFromUrl;
56921
57988
  exports.getWorkspaceNavigationParams = getWorkspaceNavigationParams;
57989
+ exports.groupLinesByShift = groupLinesByShift;
56922
57990
  exports.hasAnyShiftData = hasAnyShiftData;
56923
57991
  exports.identifyCoreUser = identifyCoreUser;
56924
57992
  exports.initializeCoreMixpanel = initializeCoreMixpanel;
@@ -56960,12 +58028,14 @@ exports.startCoreSessionRecording = startCoreSessionRecording;
56960
58028
  exports.stopCoreSessionRecording = stopCoreSessionRecording;
56961
58029
  exports.storeWorkspaceMapping = storeWorkspaceMapping;
56962
58030
  exports.streamProxyConfig = streamProxyConfig;
58031
+ exports.subscribeWorkspaceDisplayNames = subscribeWorkspaceDisplayNames;
56963
58032
  exports.throttledReloadDashboard = throttledReloadDashboard;
56964
58033
  exports.toUrlFriendlyName = toUrlFriendlyName;
56965
58034
  exports.trackCoreEvent = trackCoreEvent;
56966
58035
  exports.trackCorePageView = trackCorePageView;
56967
58036
  exports.transformToChartData = transformToChartData;
56968
58037
  exports.updateThreadTitle = updateThreadTitle;
58038
+ exports.upsertWorkspaceDisplayNameInCache = upsertWorkspaceDisplayNameInCache;
56969
58039
  exports.useAccessControl = useAccessControl;
56970
58040
  exports.useActiveBreaks = useActiveBreaks;
56971
58041
  exports.useActiveLineId = useActiveLineId;
@@ -57029,6 +58099,7 @@ exports.useSubscriptionManager = useSubscriptionManager;
57029
58099
  exports.useSubscriptionManagerSafe = useSubscriptionManagerSafe;
57030
58100
  exports.useSupabase = useSupabase;
57031
58101
  exports.useSupabaseClient = useSupabaseClient;
58102
+ exports.useSupervisorsByLineIds = useSupervisorsByLineIds;
57032
58103
  exports.useTargets = useTargets;
57033
58104
  exports.useTeamManagementPermissions = useTeamManagementPermissions;
57034
58105
  exports.useTheme = useTheme;