@optifye/dashboard-core 6.10.1 → 6.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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);
@@ -8872,11 +9108,12 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
8872
9108
  const [isLoading, setIsLoading] = React24.useState(true);
8873
9109
  const [error, setError] = React24.useState(null);
8874
9110
  const supabase = useSupabase();
8875
- const lineIdsKey = React24.useMemo(() => lineIds.sort().join(","), [lineIds]);
9111
+ const lineIdsKey = React24.useMemo(() => lineIds.slice().sort().join(","), [lineIds]);
8876
9112
  React24.useEffect(() => {
8877
9113
  if (!lineIds || lineIds.length === 0) {
8878
9114
  setShiftConfigMap(/* @__PURE__ */ new Map());
8879
9115
  setIsLoading(false);
9116
+ setError(null);
8880
9117
  return;
8881
9118
  }
8882
9119
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -8885,56 +9122,56 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
8885
9122
  console.warn("[useMultiLineShiftConfigs] No valid line IDs provided");
8886
9123
  setShiftConfigMap(/* @__PURE__ */ new Map());
8887
9124
  setIsLoading(false);
9125
+ setError(null);
8888
9126
  return;
8889
9127
  }
8890
9128
  let mounted = true;
8891
- const calculateBreakDuration2 = (startTime, endTime) => {
8892
- const [sh, sm] = startTime.split(":").map(Number);
8893
- const [eh, em] = endTime.split(":").map(Number);
8894
- let startMinutes = sh * 60 + sm;
8895
- let endMinutes = eh * 60 + em;
8896
- if (endMinutes < startMinutes) {
8897
- endMinutes += 24 * 60;
8898
- }
8899
- return endMinutes - startMinutes;
8900
- };
8901
- const stripSeconds = (timeStr) => {
8902
- if (!timeStr) return timeStr;
8903
- return timeStr.substring(0, 5);
8904
- };
8905
- const buildShiftConfigFromRows = (shifts, fallback) => {
8906
- const mapped = shifts.map((shift) => ({
8907
- shiftId: shift.shift_id,
8908
- shiftName: shift.shift_name || `Shift ${shift.shift_id}`,
8909
- startTime: stripSeconds(shift.start_time),
8910
- endTime: stripSeconds(shift.end_time),
8911
- breaks: Array.isArray(shift.breaks) ? shift.breaks.map((b) => ({
8912
- startTime: b.start || b.startTime || "00:00",
8913
- endTime: b.end || b.endTime || "00:00",
8914
- duration: calculateBreakDuration2(
8915
- b.start || b.startTime || "00:00",
8916
- b.end || b.endTime || "00:00"
8917
- ),
8918
- remarks: b.remarks || b.name || ""
8919
- })) : [],
8920
- timezone: shift.timezone
8921
- }));
8922
- const day = mapped.find((s) => s.shiftId === 0);
8923
- const night = mapped.find((s) => s.shiftId === 1);
8924
- return {
8925
- shifts: mapped,
8926
- timezone: mapped[0]?.timezone,
8927
- dayShift: day ? { id: day.shiftId, startTime: day.startTime, endTime: day.endTime, name: day.shiftName } : fallback?.dayShift,
8928
- nightShift: night ? { id: night.shiftId, startTime: night.startTime, endTime: night.endTime, name: night.shiftName } : fallback?.nightShift,
8929
- transitionPeriodMinutes: fallback?.transitionPeriodMinutes || 0
8930
- };
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
+ });
8931
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
+ );
8932
9159
  const fetchAllConfigs = async () => {
8933
9160
  try {
8934
- setIsLoading(true);
8935
- setError(null);
8936
- console.log(`[useMultiLineShiftConfigs] Fetching shift configs for ${validLineIds.length} lines`);
8937
- 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);
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);
8938
9175
  if (fetchError) {
8939
9176
  console.error("[useMultiLineShiftConfigs] Error fetching shift configs:", fetchError);
8940
9177
  throw new Error(`Failed to fetch shift configs: ${fetchError.message}`);
@@ -8946,33 +9183,23 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
8946
9183
  }
8947
9184
  lineShiftsMap.get(row.line_id).push(row);
8948
9185
  });
8949
- const configMap = /* @__PURE__ */ new Map();
8950
9186
  lineShiftsMap.forEach((shifts, lineId) => {
8951
- const config = buildShiftConfigFromRows(shifts, fallbackConfig);
8952
- configMap.set(lineId, config);
9187
+ shiftConfigStore.setFromOperatingHoursRows(lineId, shifts, fallbackConfig);
8953
9188
  });
8954
- validLineIds.forEach((lineId) => {
8955
- if (!configMap.has(lineId) && fallbackConfig) {
9189
+ missingLineIds.forEach((lineId) => {
9190
+ if (!lineShiftsMap.has(lineId) && fallbackConfig) {
8956
9191
  console.warn(`[useMultiLineShiftConfigs] No config found for line ${lineId}, using fallback`);
8957
- configMap.set(lineId, fallbackConfig);
9192
+ shiftConfigStore.set(lineId, fallbackConfig);
8958
9193
  }
8959
9194
  });
8960
- console.log(`[useMultiLineShiftConfigs] Built configs for ${configMap.size} lines`);
9195
+ console.log(`[useMultiLineShiftConfigs] Stored configs for ${lineShiftsMap.size} lines`);
8961
9196
  if (mounted) {
8962
- setShiftConfigMap(configMap);
8963
9197
  setIsLoading(false);
8964
9198
  }
8965
9199
  } catch (err) {
8966
9200
  console.error("[useMultiLineShiftConfigs] Error:", err);
8967
9201
  if (mounted) {
8968
9202
  setError(err instanceof Error ? err.message : "Failed to fetch shift configs");
8969
- if (fallbackConfig) {
8970
- const fallbackMap = /* @__PURE__ */ new Map();
8971
- validLineIds.forEach((lineId) => {
8972
- fallbackMap.set(lineId, fallbackConfig);
8973
- });
8974
- setShiftConfigMap(fallbackMap);
8975
- }
8976
9203
  setIsLoading(false);
8977
9204
  }
8978
9205
  }
@@ -8980,6 +9207,8 @@ var useMultiLineShiftConfigs = (lineIds, fallbackConfig) => {
8980
9207
  fetchAllConfigs();
8981
9208
  return () => {
8982
9209
  mounted = false;
9210
+ unsubscribeStore();
9211
+ unsubscribeRealtimeList.forEach((unsub) => unsub());
8983
9212
  };
8984
9213
  }, [lineIdsKey, supabase]);
8985
9214
  return {
@@ -9322,8 +9551,10 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds
9322
9551
  const operationalDateForSubscription = currentShiftDetails.date;
9323
9552
  const targetLineIds = [currentLineIdToUse];
9324
9553
  if (targetLineIds.length === 0) return;
9325
- const wsMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
9326
- const lineMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
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}`;
9327
9558
  const createSubscription = (table, filter2, channelNameBase, callback) => {
9328
9559
  const channelName = `${channelNameBase}-${Date.now()}`.replace(/[^a-zA-Z0-9_-]/g, "");
9329
9560
  const channel = supabase.channel(channelName).on(
@@ -9800,12 +10031,12 @@ var useRealtimeLineMetrics = ({
9800
10031
  const companyId = entityConfig.companyId;
9801
10032
  const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
9802
10033
  const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
9803
- let enabledWorkspaceIds = [];
9804
- for (const lineId2 of targetLineIds) {
9805
- const workspaces = await workspaceService.getWorkspaces(lineId2);
9806
- const enabledIds = workspaces.filter((ws) => ws.enable === true).map((ws) => ws.id);
9807
- enabledWorkspaceIds.push(...enabledIds);
9808
- }
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
+ );
9809
10040
  const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
9810
10041
  line_id,
9811
10042
  workspace_id,
@@ -9865,8 +10096,8 @@ var useRealtimeLineMetrics = ({
9865
10096
  const companyId = entityConfig.companyId;
9866
10097
  const metricsTablePrefix = getMetricsTablePrefix(companyId || "");
9867
10098
  const metricsTable = `${metricsTablePrefix}_${(companyId || "").replace(/-/g, "_")}`;
9868
- const workspaces = await workspaceService.getWorkspaces(lineIdRef.current);
9869
- 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);
9870
10101
  const { data: workspaceData, error: workspaceError } = enabledWorkspaceIds.length > 0 ? await supabase.from(metricsTable).select(`
9871
10102
  workspace_id,
9872
10103
  workspace_name,
@@ -9986,20 +10217,23 @@ var useRealtimeLineMetrics = ({
9986
10217
  const companyId = entityConfig.companyId;
9987
10218
  const metricsTablePrefix = getMetricsTablePrefix();
9988
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}`;
9989
10224
  const lineMetricsChannel = supabase.channel(`line-metrics-${timestamp}`).on(
9990
10225
  "postgres_changes",
9991
10226
  {
9992
10227
  event: "*",
9993
10228
  schema: "public",
9994
10229
  table: "line_metrics",
9995
- filter: `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`
10230
+ filter: filter2
9996
10231
  },
9997
10232
  async (payload) => {
9998
10233
  const payloadData = payload.new;
9999
10234
  if (process.env.NODE_ENV === "development") {
10000
10235
  console.log("Line metrics update received:", payloadData);
10001
10236
  }
10002
- const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
10003
10237
  if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
10004
10238
  queueUpdate();
10005
10239
  }
@@ -10015,14 +10249,13 @@ var useRealtimeLineMetrics = ({
10015
10249
  event: "*",
10016
10250
  schema: "public",
10017
10251
  table: metricsTable,
10018
- filter: `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`
10252
+ filter: filter2
10019
10253
  },
10020
10254
  async (payload) => {
10021
10255
  const payloadData = payload.new;
10022
10256
  if (process.env.NODE_ENV === "development") {
10023
10257
  console.log(`${metricsTablePrefix} update received:`, payloadData);
10024
10258
  }
10025
- const currentDate = urlDate || getOperationalDate(timezone || dateTimeConfig.defaultTimezone || "UTC");
10026
10259
  if (payloadData?.date === currentDate && payloadData?.shift_id === shiftId) {
10027
10260
  queueUpdate();
10028
10261
  }
@@ -10033,7 +10266,7 @@ var useRealtimeLineMetrics = ({
10033
10266
  }
10034
10267
  });
10035
10268
  channelsRef.current = [lineMetricsChannel, metricsChannel];
10036
- }, [supabase, queueUpdate, urlDate, shiftId, entityConfig, dateTimeConfig.defaultTimezone]);
10269
+ }, [supabase, queueUpdate, urlDate, shiftId, entityConfig, timezone, dateTimeConfig.defaultTimezone]);
10037
10270
  const prevShiftIdRef = React24.useRef(void 0);
10038
10271
  React24.useEffect(() => {
10039
10272
  if (!lineId) return;
@@ -10812,34 +11045,16 @@ var useFactoryOverviewMetrics = (date, shiftId) => {
10812
11045
  if (lineIds.length === 0) {
10813
11046
  throw new Error("No lines configured in entityConfig");
10814
11047
  }
10815
- const { data: { session } } = await supabase.auth.getSession();
10816
- if (!session?.access_token) {
10817
- throw new Error("No authentication token available");
10818
- }
10819
- const apiUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
10820
- if (!apiUrl) {
10821
- throw new Error("Backend URL is not configured. Please set NEXT_PUBLIC_BACKEND_URL in your environment.");
10822
- }
10823
11048
  const params = new URLSearchParams({
10824
11049
  line_ids: lineIds.join(","),
10825
11050
  date: queryDate,
10826
11051
  shift_id: queryShiftId.toString(),
10827
11052
  company_id: entityConfig.companyId || ""
10828
11053
  });
10829
- const response = await fetch(
10830
- `${apiUrl}/api/dashboard/factory-overview?${params.toString()}`,
10831
- {
10832
- headers: {
10833
- "Authorization": `Bearer ${session.access_token}`,
10834
- "Content-Type": "application/json"
10835
- }
10836
- }
11054
+ const data = await fetchBackendJson(
11055
+ supabase,
11056
+ `/api/dashboard/factory-overview?${params.toString()}`
10837
11057
  );
10838
- if (!response.ok) {
10839
- const errorText = await response.text();
10840
- throw new Error(`Backend API error (${response.status}): ${errorText}`);
10841
- }
10842
- const data = await response.json();
10843
11058
  setMetrics(data);
10844
11059
  } catch (err) {
10845
11060
  console.error("[useFactoryOverviewMetrics] Error fetching factory overview:", err);
@@ -10868,6 +11083,46 @@ var isInitialized = false;
10868
11083
  var isInitializing = false;
10869
11084
  var initializedWithLineIds = [];
10870
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
+ };
10871
11126
  function getCurrentLineIds() {
10872
11127
  try {
10873
11128
  const config = _getDashboardConfigInstance();
@@ -10907,15 +11162,20 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
10907
11162
  console.log("\u{1F504} Target line IDs for workspace filtering:", targetLineIds);
10908
11163
  runtimeWorkspaceDisplayNames = {};
10909
11164
  if (targetLineIds.length > 0) {
10910
- for (const lineId of targetLineIds) {
10911
- console.log(`\u{1F504} Fetching workspaces for line: ${lineId}`);
10912
- 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 }) => {
10913
11173
  runtimeWorkspaceDisplayNames[lineId] = {};
10914
11174
  lineDisplayNamesMap.forEach((displayName, workspaceId) => {
10915
11175
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
10916
11176
  });
10917
11177
  console.log(`\u2705 Stored ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
10918
- }
11178
+ });
10919
11179
  } else {
10920
11180
  console.warn("\u26A0\uFE0F No line IDs found, fetching all workspaces (less efficient)");
10921
11181
  const allWorkspacesMap = await workspaceService.getWorkspaceDisplayNames();
@@ -10928,6 +11188,7 @@ async function initializeWorkspaceDisplayNames(explicitLineId) {
10928
11188
  initializedWithLineIds = targetLineIds;
10929
11189
  console.log("\u2705 Workspace display names initialized from Supabase:", runtimeWorkspaceDisplayNames);
10930
11190
  console.log("\u2705 Initialized with line IDs:", initializedWithLineIds);
11191
+ notifyWorkspaceDisplayNamesListeners(explicitLineId);
10931
11192
  } catch (error) {
10932
11193
  console.error("\u274C Failed to initialize workspace display names from Supabase:", error);
10933
11194
  } finally {
@@ -10950,6 +11211,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
10950
11211
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
10951
11212
  });
10952
11213
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
11214
+ notifyWorkspaceDisplayNamesListeners(lineId);
10953
11215
  } catch (error) {
10954
11216
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
10955
11217
  }
@@ -10968,6 +11230,7 @@ var preInitializeWorkspaceDisplayNames = async (lineId) => {
10968
11230
  runtimeWorkspaceDisplayNames[lineId][workspaceId] = displayName;
10969
11231
  });
10970
11232
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId}`);
11233
+ notifyWorkspaceDisplayNamesListeners(lineId);
10971
11234
  } catch (error) {
10972
11235
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
10973
11236
  }
@@ -11007,6 +11270,7 @@ var getWorkspaceDisplayName = (workspaceId, lineId) => {
11007
11270
  runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
11008
11271
  });
11009
11272
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
11273
+ notifyWorkspaceDisplayNamesListeners(lineId);
11010
11274
  }).catch((error) => {
11011
11275
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
11012
11276
  });
@@ -11047,6 +11311,7 @@ var getShortWorkspaceDisplayName = (workspaceId, lineId) => {
11047
11311
  runtimeWorkspaceDisplayNames[lineId][workspaceId2] = displayName2;
11048
11312
  });
11049
11313
  console.log(`\u2705 Added ${lineDisplayNamesMap.size} workspaces for line ${lineId} to cache`);
11314
+ notifyWorkspaceDisplayNamesListeners(lineId);
11050
11315
  }).catch((error) => {
11051
11316
  console.error(`\u274C Failed to fetch workspaces for line ${lineId}:`, error);
11052
11317
  });
@@ -11088,6 +11353,9 @@ var getAllWorkspaceDisplayNamesAsync = async (companyId, lineId) => {
11088
11353
  while (isInitializing) {
11089
11354
  await new Promise((resolve) => setTimeout(resolve, 100));
11090
11355
  }
11356
+ if (lineId && isInitialized && !runtimeWorkspaceDisplayNames[lineId]) {
11357
+ await preInitializeWorkspaceDisplayNames(lineId);
11358
+ }
11091
11359
  if (lineId && runtimeWorkspaceDisplayNames[lineId]) {
11092
11360
  return { ...runtimeWorkspaceDisplayNames[lineId] };
11093
11361
  }
@@ -11120,6 +11388,7 @@ var refreshWorkspaceDisplayNames = async (companyId) => {
11120
11388
  runtimeWorkspaceDisplayNames = {};
11121
11389
  isInitialized = false;
11122
11390
  await initializeWorkspaceDisplayNames();
11391
+ notifyWorkspaceDisplayNamesListeners();
11123
11392
  };
11124
11393
  var clearWorkspaceDisplayNamesCache = () => {
11125
11394
  workspaceService.clearWorkspaceDisplayNamesCache();
@@ -11128,6 +11397,7 @@ var clearWorkspaceDisplayNamesCache = () => {
11128
11397
  isInitializing = false;
11129
11398
  initializedWithLineIds = [];
11130
11399
  initializationPromise = null;
11400
+ notifyWorkspaceDisplayNamesListeners();
11131
11401
  };
11132
11402
 
11133
11403
  // src/lib/hooks/useWorkspaceDisplayNames.ts
@@ -11150,6 +11420,23 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
11150
11420
  React24.useEffect(() => {
11151
11421
  fetchDisplayNames();
11152
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]);
11153
11440
  return {
11154
11441
  displayNames,
11155
11442
  loading,
@@ -11158,7 +11445,7 @@ var useWorkspaceDisplayNames = (lineId, companyId) => {
11158
11445
  };
11159
11446
  };
11160
11447
  var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
11161
- const [displayName, setDisplayName] = React24.useState(workspaceId);
11448
+ const [displayName, setDisplayName] = React24.useState(() => getWorkspaceDisplayName(workspaceId, lineId));
11162
11449
  const [loading, setLoading] = React24.useState(true);
11163
11450
  const [error, setError] = React24.useState(null);
11164
11451
  const fetchDisplayName = React24.useCallback(async () => {
@@ -11177,6 +11464,18 @@ var useWorkspaceDisplayName = (workspaceId, lineId, companyId) => {
11177
11464
  React24.useEffect(() => {
11178
11465
  fetchDisplayName();
11179
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]);
11180
11479
  return {
11181
11480
  displayName,
11182
11481
  loading,
@@ -11207,6 +11506,18 @@ var useWorkspaceDisplayNamesMap = (workspaceIds, lineId, companyId) => {
11207
11506
  React24.useEffect(() => {
11208
11507
  fetchDisplayNames();
11209
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]);
11210
11521
  return {
11211
11522
  displayNames,
11212
11523
  loading,
@@ -11453,13 +11764,19 @@ var useAllWorkspaceMetrics = (options) => {
11453
11764
  setError(null);
11454
11765
  try {
11455
11766
  const lineWorkspaceMap = /* @__PURE__ */ new Map();
11456
- let allEnabledWorkspaceIds = [];
11457
- for (const lineId of configuredLineIds) {
11458
- const lineWorkspaces = await workspaceService.getWorkspaces(lineId);
11459
- const enabledIds = lineWorkspaces.filter((ws) => ws.enable === true).map((ws) => ws.id);
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 }) => {
11460
11776
  lineWorkspaceMap.set(lineId, enabledIds);
11461
- allEnabledWorkspaceIds.push(...enabledIds);
11462
- }
11777
+ enabledIds.forEach((id3) => allEnabledWorkspaceIdSet.add(id3));
11778
+ });
11779
+ const allEnabledWorkspaceIds = Array.from(allEnabledWorkspaceIdSet);
11463
11780
  if (allEnabledWorkspaceIds.length === 0) {
11464
11781
  setWorkspaces([]);
11465
11782
  setInitialized(true);
@@ -11580,43 +11897,61 @@ var useAllWorkspaceMetrics = (options) => {
11580
11897
  });
11581
11898
  }
11582
11899
  const setupSubscription = () => {
11583
- const channel2 = supabase.channel(`all-workspace-metrics-${Date.now()}`).on(
11584
- "postgres_changes",
11900
+ if (!metricsTable || configuredLineIds.length === 0) return [];
11901
+ const groupsToSubscribe = hasSpecificDateShift || shiftGroups.length === 0 ? [
11585
11902
  {
11586
- event: "*",
11587
- schema,
11588
- table: metricsTable
11589
- },
11590
- async (payload) => {
11591
- const data = payload.new || payload.old;
11592
- const dataKey = `${data?.date}-${data?.shift_id}`;
11593
- if (!validDateShiftCombos.has(dataKey)) {
11594
- return;
11595
- }
11596
- if (fetchTimeoutRef.current) {
11597
- clearTimeout(fetchTimeoutRef.current);
11598
- }
11599
- fetchTimeoutRef.current = setTimeout(async () => {
11600
- if (!isFetchingRef.current) {
11601
- await fetchWorkspaceMetrics();
11602
- }
11603
- fetchTimeoutRef.current = null;
11604
- }, 300);
11903
+ date: fallbackQueryDate,
11904
+ shiftId: fallbackQueryShiftId,
11905
+ lineIds: configuredLineIds
11605
11906
  }
11606
- ).subscribe();
11607
- return channel2;
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;
11932
+ }
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
+ });
11608
11945
  };
11609
- const channel = setupSubscription();
11946
+ const channels = setupSubscription();
11610
11947
  return () => {
11611
11948
  if (fetchTimeoutRef.current) {
11612
11949
  clearTimeout(fetchTimeoutRef.current);
11613
11950
  fetchTimeoutRef.current = null;
11614
11951
  }
11615
- if (channel) {
11616
- supabase.removeChannel(channel);
11617
- }
11952
+ channels.forEach((channel) => supabase.removeChannel(channel));
11618
11953
  };
11619
- }, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId]);
11954
+ }, [fallbackQueryDate, fallbackQueryShiftId, hasSpecificDateShift, shiftGroups, metricsTable, fetchWorkspaceMetrics, initialized, supabase, schema, entityConfig.companyId, configuredLineIds]);
11620
11955
  React24.useEffect(() => {
11621
11956
  setInitialized(false);
11622
11957
  }, [fallbackQueryDate, fallbackQueryShiftId, shiftGroups]);
@@ -13054,6 +13389,85 @@ function useLineSupervisor(lineId) {
13054
13389
  error
13055
13390
  };
13056
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
+ };
13057
13471
  function useIdleTimeReasons({
13058
13472
  workspaceId,
13059
13473
  lineId,
@@ -13459,6 +13873,102 @@ var getConfigurableShortWorkspaceDisplayName = (workspaceId, workspaceConfig, li
13459
13873
  return fullName;
13460
13874
  };
13461
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
+
13462
13972
  // ../../node_modules/clsx/dist/clsx.mjs
13463
13973
  function r(e) {
13464
13974
  var t, f, n = "";
@@ -28495,6 +29005,7 @@ if (typeof document !== "undefined") {
28495
29005
  }
28496
29006
  }
28497
29007
  var BASE_HLS_CONFIG = {
29008
+ // Keep buffer small to reduce wasted downloads on slow links
28498
29009
  maxBufferLength: 3,
28499
29010
  maxMaxBufferLength: 8,
28500
29011
  maxBufferSize: 50 * 1e3 * 1e3,
@@ -28502,10 +29013,10 @@ var BASE_HLS_CONFIG = {
28502
29013
  manifestLoadingTimeOut: 15e3,
28503
29014
  manifestLoadingMaxRetry: 3,
28504
29015
  manifestLoadingRetryDelay: 500,
28505
- levelLoadingTimeOut: 6e4,
29016
+ levelLoadingTimeOut: 2e4,
28506
29017
  levelLoadingMaxRetry: 5,
28507
29018
  levelLoadingRetryDelay: 500,
28508
- fragLoadingTimeOut: 6e4,
29019
+ fragLoadingTimeOut: 2e4,
28509
29020
  fragLoadingMaxRetry: 5,
28510
29021
  fragLoadingRetryDelay: 500,
28511
29022
  startPosition: -1,
@@ -28518,7 +29029,10 @@ var BASE_HLS_CONFIG = {
28518
29029
  abrBandWidthFactor: 0.95,
28519
29030
  abrBandWidthUpFactor: 0.7,
28520
29031
  abrMaxWithRealBitrate: false,
28521
- abrEwmaDefaultEstimate: 5e7
29032
+ // Favor a conservative first rendition on constrained networks
29033
+ abrEwmaDefaultEstimate: 1e6,
29034
+ startLevel: 0,
29035
+ capLevelToPlayerSize: true
28522
29036
  };
28523
29037
  var HlsVideoPlayer = React24.forwardRef(({
28524
29038
  src,
@@ -29602,7 +30116,7 @@ var getSupabaseClient2 = () => {
29602
30116
  }
29603
30117
  return supabaseJs.createClient(url, key);
29604
30118
  };
29605
- var getAuthToken3 = async () => {
30119
+ var getAuthToken4 = async () => {
29606
30120
  try {
29607
30121
  const supabase = getSupabaseClient2();
29608
30122
  const { data: { session } } = await supabase.auth.getSession();
@@ -29625,7 +30139,7 @@ function useWorkspaceCrop(workspaceId) {
29625
30139
  setIsLoading(true);
29626
30140
  setError(null);
29627
30141
  try {
29628
- const token = await getAuthToken3();
30142
+ const token = await getAuthToken4();
29629
30143
  if (!token) {
29630
30144
  throw new Error("Authentication required");
29631
30145
  }
@@ -30674,7 +31188,7 @@ var FileManagerFilters = ({
30674
31188
  const ROOT_CAUSE_OPTIONS = [
30675
31189
  "Operator Absent",
30676
31190
  "Operator Idle",
30677
- "Machine Maintenance",
31191
+ "Machine Downtime",
30678
31192
  "No Material"
30679
31193
  ];
30680
31194
  const getIdleTimeRootCause = React24.useCallback((clipId) => {
@@ -30714,7 +31228,7 @@ var FileManagerFilters = ({
30714
31228
  method: "POST",
30715
31229
  headers: {
30716
31230
  "Content-Type": "application/json",
30717
- "Authorization": `Bearer ${await getAuthToken4()}`
31231
+ "Authorization": `Bearer ${await getAuthToken5()}`
30718
31232
  },
30719
31233
  body: JSON.stringify({
30720
31234
  action: "clip-metadata",
@@ -30747,7 +31261,7 @@ var FileManagerFilters = ({
30747
31261
  });
30748
31262
  }
30749
31263
  }, [workspaceId, date, shift]);
30750
- const getAuthToken4 = async () => {
31264
+ const getAuthToken5 = async () => {
30751
31265
  try {
30752
31266
  const { createClient: createClient5 } = await import('@supabase/supabase-js');
30753
31267
  const supabase = createClient5(
@@ -30773,7 +31287,7 @@ var FileManagerFilters = ({
30773
31287
  method: "POST",
30774
31288
  headers: {
30775
31289
  "Content-Type": "application/json",
30776
- "Authorization": `Bearer ${await getAuthToken4()}`
31290
+ "Authorization": `Bearer ${await getAuthToken5()}`
30777
31291
  },
30778
31292
  body: JSON.stringify({
30779
31293
  action: "percentile-clips",
@@ -32417,7 +32931,7 @@ var BottlenecksContent = ({
32417
32931
  fetchClipCounts();
32418
32932
  }
32419
32933
  }, [workspaceId, effectiveDateString, effectiveShiftId, s3ClipsService, fetchClipCounts, isEffectiveShiftReady]);
32420
- const getAuthToken4 = React24.useCallback(async () => {
32934
+ const getAuthToken5 = React24.useCallback(async () => {
32421
32935
  try {
32422
32936
  const { createClient: createClient5 } = await import('@supabase/supabase-js');
32423
32937
  const supabase = createClient5(
@@ -32436,7 +32950,7 @@ var BottlenecksContent = ({
32436
32950
  const fetchTriageClips = async () => {
32437
32951
  setIsLoadingTriageClips(true);
32438
32952
  try {
32439
- const token = await getAuthToken4();
32953
+ const token = await getAuthToken5();
32440
32954
  if (!token) {
32441
32955
  console.error("[BottlenecksContent] No auth token available");
32442
32956
  return;
@@ -32484,7 +32998,7 @@ var BottlenecksContent = ({
32484
32998
  }
32485
32999
  };
32486
33000
  fetchTriageClips();
32487
- }, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken4, isEffectiveShiftReady]);
33001
+ }, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken5, isEffectiveShiftReady]);
32488
33002
  React24.useEffect(() => {
32489
33003
  if (!triageMode || triageClips.length === 0 || !session?.access_token) {
32490
33004
  return;
@@ -32815,7 +33329,20 @@ var BottlenecksContent = ({
32815
33329
  updateActiveFilter(categoryId);
32816
33330
  }
32817
33331
  try {
32818
- 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;
32819
33346
  let metadataArray = categoryMetadataRef.current;
32820
33347
  const clipExistsInMetadata = metadataArray.some((clip) => clip.clipId === clipId);
32821
33348
  if (metadataArray.length === 0 || !clipExistsInMetadata) {
@@ -32832,16 +33359,7 @@ var BottlenecksContent = ({
32832
33359
  }
32833
33360
  setCurrentMetadataIndex(clickedClipIndex);
32834
33361
  currentMetadataIndexRef.current = clickedClipIndex;
32835
- const video = await s3ClipsService.getClipById(clipId);
32836
- if (video) {
32837
- setPendingVideo(video);
32838
- setCurrentClipId(clipId);
32839
- setAllVideos([video]);
32840
- setCurrentIndex(0);
32841
- console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
32842
- } else {
32843
- throw new Error(`Failed to load video data for clip ${clipId}`);
32844
- }
33362
+ console.log(`[BottlenecksContent] Loaded clip ${clipId} (${clickedClipIndex + 1}/${metadataArray.length})`);
32845
33363
  } catch (error2) {
32846
33364
  console.error(`[BottlenecksContent] Error loading clip by ID (${clipId}):`, error2);
32847
33365
  if (isMountedRef.current) {
@@ -35728,7 +36246,7 @@ var STATIC_COLORS = {
35728
36246
  // red-600 - Critical/Urgent
35729
36247
  "No Material": "#f59e0b",
35730
36248
  // amber-500 - Warning/Supply Chain
35731
- "Machine Maintenance": "#3b82f6",
36249
+ "Machine Downtime": "#3b82f6",
35732
36250
  // blue-500 - Scheduled/Technical
35733
36251
  "Operator Idle": "#8b5cf6"
35734
36252
  // violet-500 - Low Priority/Behavioral
@@ -38113,7 +38631,7 @@ var WorkspaceWhatsAppShareButton = ({
38113
38631
  }
38114
38632
  );
38115
38633
  };
38116
- var WorkspacePdfGenerator = ({ workspace, className }) => {
38634
+ var WorkspacePdfGenerator = ({ workspace, className, idleTimeReasons }) => {
38117
38635
  const [isGenerating, setIsGenerating] = React24.useState(false);
38118
38636
  const entityConfig = useEntityConfig();
38119
38637
  const generatePDF = async () => {
@@ -38188,8 +38706,10 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
38188
38706
  doc.roundedRect(22, y - 7, 165, 12, 2, 2, "S");
38189
38707
  };
38190
38708
  const perfOverviewStartY = 93;
38709
+ const hasIdleTimeReason = idleTimeReasons && idleTimeReasons.length > 0;
38710
+ const perfOverviewHeight = hasIdleTimeReason ? 80 : 70;
38191
38711
  doc.setFillColor(245, 245, 245);
38192
- doc.roundedRect(15, perfOverviewStartY, 180, 60, 3, 3, "F");
38712
+ doc.roundedRect(15, perfOverviewStartY, 180, perfOverviewHeight, 3, 3, "F");
38193
38713
  doc.setFontSize(18);
38194
38714
  doc.setFont("helvetica", "bold");
38195
38715
  doc.setTextColor(40, 40, 40);
@@ -38213,32 +38733,68 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
38213
38733
  doc.text("PPH (Pieces Per Hour):", 25, kpiStartY + kpiSpacing * 2);
38214
38734
  doc.setFont("helvetica", "bold");
38215
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;
38216
38753
  doc.setDrawColor(180, 180, 180);
38217
38754
  doc.setLineWidth(0.8);
38218
- doc.line(20, 163, 190, 163);
38219
- const hourlyPerfStartY = 168;
38755
+ doc.line(20, separatorBeforeHourlyY, 190, separatorBeforeHourlyY);
38756
+ const hourlyPerfStartY = hasIdleTimeReason ? 188 : 178;
38220
38757
  const hourlyData = workspace.hourly_action_counts || [];
38221
38758
  const hourlyTarget = workspace.pph_threshold;
38222
- const tableStartY = 199;
38223
- 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;
38224
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
+ }
38225
38776
  const backgroundHeight = tableStartY - hourlyPerfStartY + hourlyData.length * rowHeight + bottomPadding;
38226
38777
  doc.setFillColor(245, 245, 245);
38227
38778
  doc.roundedRect(15, hourlyPerfStartY, 180, backgroundHeight, 3, 3, "F");
38228
- doc.setFontSize(18);
38779
+ doc.setFontSize(titleFontSize);
38229
38780
  doc.setFont("helvetica", "bold");
38230
38781
  doc.setTextColor(40, 40, 40);
38231
- doc.text("Hourly Performance", 20, 178);
38782
+ const hourlyTitleY = hasIdleTimeReason ? 198 : 188;
38783
+ doc.text("Hourly Performance", 20, hourlyTitleY);
38232
38784
  doc.setTextColor(0, 0, 0);
38233
- doc.setFontSize(11);
38785
+ doc.setFontSize(headerFontSize);
38234
38786
  doc.setFont("helvetica", "bold");
38235
- doc.text("Time Range", 25, 188);
38236
- doc.text("Output", 95, 188);
38237
- doc.text("Target", 135, 188);
38238
- 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);
38239
38793
  doc.setLineWidth(0.3);
38240
38794
  doc.setDrawColor(200, 200, 200);
38241
- doc.line(20, 191, 190, 191);
38795
+ const separatorY = headerY + 3;
38796
+ doc.line(20, separatorY, 190, separatorY);
38797
+ doc.setFontSize(contentFontSize);
38242
38798
  doc.setFont("helvetica", "normal");
38243
38799
  let yPos = tableStartY;
38244
38800
  const workspaceDate = new Date(workspace.date);
@@ -45690,23 +46246,6 @@ function HomeView({
45690
46246
  loading: displayNamesLoading,
45691
46247
  error: displayNamesError
45692
46248
  } = useWorkspaceDisplayNames(displayNameLineId, void 0);
45693
- React24.useCallback(() => {
45694
- console.log("Refetching KPIs after line metrics update");
45695
- }, []);
45696
- const {
45697
- kpis,
45698
- isLoading: kpisLoading,
45699
- error: kpisError,
45700
- refetch: refetchKPIs
45701
- } = useLineKPIs({
45702
- lineId: selectedLineId
45703
- });
45704
- const onLineMetricsUpdate = React24.useCallback(() => {
45705
- const timer = setTimeout(() => {
45706
- refetchKPIs();
45707
- }, 1e3);
45708
- return () => clearTimeout(timer);
45709
- }, [refetchKPIs]);
45710
46249
  const {
45711
46250
  workspaceMetrics,
45712
46251
  lineMetrics,
@@ -45715,10 +46254,22 @@ function HomeView({
45715
46254
  refetch: refetchMetrics
45716
46255
  } = useDashboardMetrics({
45717
46256
  lineId: selectedLineId,
45718
- onLineMetricsUpdate,
45719
46257
  userAccessibleLineIds: allLineIds
45720
46258
  // Pass user's accessible lines for supervisor filtering
45721
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]);
45722
46273
  const {
45723
46274
  activeBreaks: allActiveBreaks,
45724
46275
  isLoading: breaksLoading,
@@ -46037,12 +46588,10 @@ function HomeView({
46037
46588
  React24.useEffect(() => {
46038
46589
  if (metricsError) {
46039
46590
  setErrorMessage(metricsError.message);
46040
- } else if (kpisError) {
46041
- setErrorMessage(kpisError.message);
46042
46591
  } else {
46043
46592
  setErrorMessage(null);
46044
46593
  }
46045
- }, [metricsError, kpisError]);
46594
+ }, [metricsError]);
46046
46595
  const handleLineChange = React24.useCallback((value) => {
46047
46596
  setIsChangingFilter(true);
46048
46597
  setSelectedLineId(value);
@@ -46058,14 +46607,14 @@ function HomeView({
46058
46607
  }
46059
46608
  }, [LINE_FILTER_STORAGE_KEY]);
46060
46609
  React24.useEffect(() => {
46061
- if (!metricsLoading && !kpisLoading && isChangingFilter) {
46610
+ if (!metricsLoading && isChangingFilter) {
46062
46611
  if (workspaceMetrics.length > 0 || selectedLineId === factoryViewId) {
46063
46612
  setIsChangingFilter(false);
46064
46613
  }
46065
46614
  }
46066
- }, [metricsLoading, kpisLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
46615
+ }, [metricsLoading, workspaceMetrics, isChangingFilter, selectedLineId, factoryViewId]);
46067
46616
  React24.useEffect(() => {
46068
- if (!metricsLoading && !kpisLoading && !hasInitialDataLoaded) {
46617
+ if (!metricsLoading && !hasInitialDataLoaded) {
46069
46618
  setHasInitialDataLoaded(true);
46070
46619
  trackCoreEvent("Home View Loaded", {
46071
46620
  default_line_id: defaultLineId,
@@ -46073,7 +46622,7 @@ function HomeView({
46073
46622
  is_supervisor: isSupervisor
46074
46623
  });
46075
46624
  }
46076
- }, [metricsLoading, kpisLoading, hasInitialDataLoaded, defaultLineId, factoryViewId, isSupervisor]);
46625
+ }, [metricsLoading, hasInitialDataLoaded, defaultLineId, factoryViewId, isSupervisor]);
46077
46626
  const lineTitle = React24.useMemo(() => {
46078
46627
  return factoryName;
46079
46628
  }, [factoryName]);
@@ -46087,7 +46636,7 @@ function HomeView({
46087
46636
  ] });
46088
46637
  }, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
46089
46638
  const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized;
46090
- const isDataLoading = metricsLoading || kpisLoading;
46639
+ const isDataLoading = metricsLoading;
46091
46640
  if (isInitialLoading) {
46092
46641
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingPageCmp, { message: "Loading Dashboard..." });
46093
46642
  }
@@ -46231,7 +46780,7 @@ function withWorkspaceDisplayNames(Component3, options = {}) {
46231
46780
  const lineIdsKey = React24.useMemo(() => {
46232
46781
  if (!props.lineIds) return "";
46233
46782
  if (Array.isArray(props.lineIds)) {
46234
- return props.lineIds.sort().join(",");
46783
+ return props.lineIds.slice().sort().join(",");
46235
46784
  }
46236
46785
  const values = Object.values(props.lineIds).filter(Boolean);
46237
46786
  return values.sort().join(",");
@@ -47354,9 +47903,15 @@ var KPIDetailView = ({
47354
47903
  };
47355
47904
  var KPIDetailViewWithDisplayNames = withSelectedLineDisplayNames(KPIDetailView);
47356
47905
  var KPIDetailView_default = KPIDetailViewWithDisplayNames;
47357
- var LineCard = ({ line, onClick, supervisorEnabled = false }) => {
47358
- const { kpis, isLoading, error } = useLineKPIs({ lineId: line.id });
47359
- const { supervisorName } = useLineSupervisor(line.id);
47906
+ var LineCard = ({
47907
+ line,
47908
+ kpis,
47909
+ isLoading,
47910
+ error,
47911
+ onClick,
47912
+ supervisorEnabled = false,
47913
+ supervisorName
47914
+ }) => {
47360
47915
  const isOnTrack = React24__namespace.default.useMemo(() => {
47361
47916
  if (!kpis) return null;
47362
47917
  return kpis.efficiency.value > 90;
@@ -47465,6 +48020,7 @@ var KPIsOverviewView = ({
47465
48020
  const [error, setError] = React24.useState(null);
47466
48021
  const supabase = useSupabase();
47467
48022
  const dashboardConfig = useDashboardConfig();
48023
+ const entityConfig = useEntityConfig();
47468
48024
  const navigation = useNavigation(navigate);
47469
48025
  const dateTimeConfig = useDateTimeConfig();
47470
48026
  const representativeLineId = lineIds?.[0] || lines[0]?.id;
@@ -47472,6 +48028,27 @@ var KPIsOverviewView = ({
47472
48028
  const supervisorEnabled = dashboardConfig?.supervisorConfig?.enabled || false;
47473
48029
  const dbTimezone = useAppTimezone();
47474
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
+ });
47475
48052
  React24.useEffect(() => {
47476
48053
  trackCorePageView("KPIs Overview");
47477
48054
  }, []);
@@ -47689,8 +48266,12 @@ var KPIsOverviewView = ({
47689
48266
  LineCard,
47690
48267
  {
47691
48268
  line,
48269
+ kpis: metricsError ? null : kpisByLineId.get(line.id) ?? (metricsLoading ? null : defaultKPIs),
48270
+ isLoading: metricsLoading,
48271
+ error: metricsError,
47692
48272
  onClick: (kpis) => handleLineClick(line, kpis),
47693
- supervisorEnabled
48273
+ supervisorEnabled,
48274
+ supervisorName: supervisorNamesByLineId.get(line.id) || null
47694
48275
  },
47695
48276
  line.id
47696
48277
  )) }) })
@@ -48514,43 +49095,6 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
48514
49095
  const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
48515
49096
  return Number(hoursDiff.toFixed(1));
48516
49097
  };
48517
- var calculateBreakDuration = (startTime, endTime) => {
48518
- const [startHour, startMinute] = startTime.split(":").map(Number);
48519
- const [endHour, endMinute] = endTime.split(":").map(Number);
48520
- let startMinutes = startHour * 60 + startMinute;
48521
- let endMinutes = endHour * 60 + endMinute;
48522
- if (endMinutes < startMinutes) {
48523
- endMinutes += 24 * 60;
48524
- }
48525
- return endMinutes - startMinutes;
48526
- };
48527
- var parseBreaksFromDB = (dbBreaks) => {
48528
- if (!dbBreaks) return [];
48529
- if (Array.isArray(dbBreaks)) {
48530
- return dbBreaks.map((breakItem) => ({
48531
- startTime: breakItem.start || breakItem.startTime || "00:00",
48532
- endTime: breakItem.end || breakItem.endTime || "00:00",
48533
- duration: calculateBreakDuration(
48534
- breakItem.start || breakItem.startTime || "00:00",
48535
- breakItem.end || breakItem.endTime || "00:00"
48536
- ),
48537
- remarks: breakItem.remarks || breakItem.name || ""
48538
- }));
48539
- } else if (dbBreaks.breaks && Array.isArray(dbBreaks.breaks)) {
48540
- return dbBreaks.breaks.map((breakItem) => ({
48541
- startTime: breakItem.start || breakItem.startTime || "00:00",
48542
- endTime: breakItem.end || breakItem.endTime || "00:00",
48543
- duration: calculateBreakDuration(
48544
- breakItem.start || breakItem.startTime || "00:00",
48545
- breakItem.end || breakItem.endTime || "00:00"
48546
- ),
48547
- remarks: breakItem.remarks || breakItem.name || ""
48548
- }));
48549
- } else {
48550
- console.warn("Unexpected breaks format:", dbBreaks);
48551
- return [];
48552
- }
48553
- };
48554
49098
  var getStoredLineState = (lineId) => {
48555
49099
  try {
48556
49100
  return JSON.parse(localStorage.getItem(`line_${lineId}_open`) || "false");
@@ -48842,16 +49386,9 @@ var ShiftsView = ({
48842
49386
  () => lineIds.map((id3) => ({
48843
49387
  id: id3,
48844
49388
  name: lineNames[id3] || `Line ${id3.substring(0, 4)}`,
48845
- dayShift: {
48846
- startTime: "08:00",
48847
- endTime: "16:00",
48848
- breaks: []
48849
- },
48850
- nightShift: {
48851
- startTime: "20:00",
48852
- endTime: "04:00",
48853
- breaks: []
48854
- },
49389
+ timezone: "Asia/Kolkata",
49390
+ shifts: [],
49391
+ // Will be populated from DB
48855
49392
  isOpen: true,
48856
49393
  isSaving: false,
48857
49394
  saveSuccess: false
@@ -48889,58 +49426,37 @@ var ShiftsView = ({
48889
49426
  setLoading(false);
48890
49427
  return;
48891
49428
  }
48892
- const { data: dayShiftOperatingHours, error: dayShiftError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 0).in("line_id", enabledLineIds);
48893
- if (dayShiftError) {
48894
- console.error("Error fetching day shift operating hours:", dayShiftError);
48895
- showToast("error", "Error loading day shift data");
48896
- setError("Failed to load day shift data");
48897
- return;
48898
- }
48899
- const { data: nightShiftOperatingHours, error: nightShiftError } = await supabase.from("line_operating_hours").select("line_id, start_time, end_time, breaks").eq("shift_id", 1).in("line_id", enabledLineIds);
48900
- if (nightShiftError) {
48901
- console.error("Error fetching night shift operating hours:", nightShiftError);
48902
- showToast("error", "Error loading night shift data");
48903
- setError("Failed to load night shift data");
49429
+ const { data: allShifts, error: shiftsError } = await supabase.from("line_operating_hours").select("line_id, shift_id, shift_name, start_time, end_time, breaks, timezone").in("line_id", enabledLineIds);
49430
+ if (shiftsError) {
49431
+ console.error("Error fetching shifts:", shiftsError);
49432
+ showToast("error", "Error loading shift data");
49433
+ setError("Failed to load shift data");
48904
49434
  return;
48905
49435
  }
48906
- const dayShiftHoursMap = (dayShiftOperatingHours || []).reduce((map, item) => {
48907
- map[item.line_id] = {
48908
- startTime: item.start_time,
48909
- endTime: item.end_time,
48910
- breaks: parseBreaksFromDB(item.breaks)
48911
- };
48912
- return map;
49436
+ const shiftsByLine = (allShifts || []).reduce((acc, row) => {
49437
+ if (!acc[row.line_id]) acc[row.line_id] = [];
49438
+ acc[row.line_id].push(row);
49439
+ return acc;
48913
49440
  }, {});
48914
- const nightShiftHoursMap = (nightShiftOperatingHours || []).reduce((map, item) => {
48915
- map[item.line_id] = {
48916
- startTime: item.start_time,
48917
- endTime: item.end_time,
48918
- breaks: parseBreaksFromDB(item.breaks)
48919
- };
48920
- return map;
49441
+ const { data: linesData } = await supabase.from("lines").select("id, line_name").in("id", enabledLineIds);
49442
+ const lineNameMap = (linesData || []).reduce((acc, line) => {
49443
+ if (line.line_name) acc[line.id] = line.line_name;
49444
+ return acc;
48921
49445
  }, {});
48922
49446
  setLineConfigs((prev) => {
48923
49447
  const enabledConfigs = prev.filter((config) => enabledLineIds.includes(config.id));
48924
49448
  return enabledConfigs.map((config) => {
48925
- const typedConfig = config;
48926
- const lineId = typedConfig.id;
48927
- const newConfig = { ...typedConfig };
48928
- if (dayShiftHoursMap[lineId]) {
48929
- newConfig.dayShift = {
48930
- ...newConfig.dayShift,
48931
- ...dayShiftHoursMap[lineId]
48932
- };
48933
- }
48934
- if (nightShiftHoursMap[lineId]) {
48935
- newConfig.nightShift = {
48936
- ...newConfig.nightShift,
48937
- ...nightShiftHoursMap[lineId]
48938
- };
48939
- }
48940
- if (newConfig.isOpen === void 0) {
48941
- newConfig.isOpen = getStoredLineState(lineId);
48942
- }
48943
- return newConfig;
49449
+ const rows = shiftsByLine[config.id] || [];
49450
+ const builtConfig = buildShiftConfigFromOperatingHoursRows(rows);
49451
+ const lineName = lineNameMap[config.id] || lineNames[config.id] || `Line ${config.id.substring(0, 4)}`;
49452
+ const sortedShifts = [...builtConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
49453
+ return {
49454
+ ...config,
49455
+ name: lineName,
49456
+ shifts: sortedShifts,
49457
+ timezone: builtConfig.timezone || config.timezone,
49458
+ isOpen: config.isOpen ?? getStoredLineState(config.id)
49459
+ };
48944
49460
  });
48945
49461
  });
48946
49462
  setLoading(false);
@@ -48963,183 +49479,79 @@ var ShiftsView = ({
48963
49479
  );
48964
49480
  });
48965
49481
  }, []);
48966
- const updateDayShiftStartTime = React24.useCallback((lineId, value) => {
48967
- setLineConfigs((prev) => prev.map((config) => {
48968
- const typedConfig = config;
48969
- if (typedConfig.id === lineId) {
48970
- const updatedDayShift = { ...typedConfig.dayShift, startTime: value };
48971
- return {
48972
- ...typedConfig,
48973
- dayShift: updatedDayShift
48974
- };
48975
- }
48976
- return typedConfig;
48977
- }));
48978
- }, []);
48979
- const updateDayShiftEndTime = React24.useCallback((lineId, value) => {
48980
- setLineConfigs((prev) => prev.map((config) => {
48981
- const typedConfig = config;
48982
- if (typedConfig.id === lineId) {
48983
- const updatedDayShift = { ...typedConfig.dayShift, endTime: value };
48984
- return {
48985
- ...typedConfig,
48986
- dayShift: updatedDayShift
48987
- };
48988
- }
48989
- return typedConfig;
48990
- }));
48991
- }, []);
48992
- const updateNightShiftStartTime = React24.useCallback((lineId, value) => {
48993
- setLineConfigs((prev) => prev.map((config) => {
48994
- const typedConfig = config;
48995
- if (typedConfig.id === lineId) {
48996
- const updatedNightShift = { ...typedConfig.nightShift, startTime: value };
48997
- return {
48998
- ...typedConfig,
48999
- nightShift: updatedNightShift
49000
- };
49001
- }
49002
- return typedConfig;
49003
- }));
49004
- }, []);
49005
- const updateNightShiftEndTime = React24.useCallback((lineId, value) => {
49006
- setLineConfigs((prev) => prev.map((config) => {
49007
- const typedConfig = config;
49008
- if (typedConfig.id === lineId) {
49009
- const updatedNightShift = { ...typedConfig.nightShift, endTime: value };
49010
- return {
49011
- ...typedConfig,
49012
- nightShift: updatedNightShift
49013
- };
49014
- }
49015
- return typedConfig;
49016
- }));
49017
- }, []);
49018
- const addDayShiftBreak = React24.useCallback((lineId) => {
49019
- setLineConfigs((prev) => prev.map((config) => {
49020
- const typedConfig = config;
49021
- if (typedConfig.id === lineId) {
49022
- const dayShift = { ...typedConfig.dayShift };
49023
- const newBreak = {
49024
- startTime: dayShift.startTime,
49025
- endTime: dayShift.startTime,
49026
- duration: 0,
49027
- remarks: ""
49028
- };
49029
- return {
49030
- ...typedConfig,
49031
- dayShift: {
49032
- ...dayShift,
49033
- breaks: [...dayShift.breaks, newBreak]
49034
- }
49035
- };
49036
- }
49037
- return typedConfig;
49038
- }));
49039
- }, []);
49040
- const addNightShiftBreak = React24.useCallback((lineId) => {
49482
+ const updateShiftTime = React24.useCallback((lineId, shiftIndex, field, value) => {
49041
49483
  setLineConfigs((prev) => prev.map((config) => {
49042
- const typedConfig = config;
49043
- if (typedConfig.id === lineId) {
49044
- const nightShift = { ...typedConfig.nightShift };
49045
- const newBreak = {
49046
- startTime: nightShift.startTime,
49047
- endTime: nightShift.startTime,
49048
- duration: 0
49049
- };
49050
- return {
49051
- ...typedConfig,
49052
- nightShift: {
49053
- ...nightShift,
49054
- breaks: [...nightShift.breaks, newBreak]
49055
- }
49056
- };
49057
- }
49058
- return typedConfig;
49059
- }));
49060
- }, []);
49061
- const updateDayShiftBreak = React24.useCallback((lineId, index, field, value) => {
49062
- setLineConfigs((prev) => prev.map((config) => {
49063
- const typedConfig = config;
49064
- if (typedConfig.id === lineId) {
49065
- const dayShift = { ...typedConfig.dayShift };
49066
- const newBreaks = [...dayShift.breaks];
49067
- newBreaks[index] = { ...newBreaks[index], [field]: value };
49068
- if (field === "startTime" || field === "endTime") {
49069
- const startParts = newBreaks[index].startTime.split(":").map(Number);
49070
- const endParts = newBreaks[index].endTime.split(":").map(Number);
49071
- let startMinutes = startParts[0] * 60 + startParts[1];
49072
- let endMinutes = endParts[0] * 60 + endParts[1];
49073
- if (endMinutes < startMinutes) {
49074
- endMinutes += 24 * 60;
49075
- }
49076
- newBreaks[index].duration = endMinutes - startMinutes;
49484
+ if (config.id === lineId && config.shifts) {
49485
+ const newShifts = [...config.shifts];
49486
+ if (newShifts[shiftIndex]) {
49487
+ newShifts[shiftIndex] = { ...newShifts[shiftIndex], [field]: value };
49077
49488
  }
49078
- return {
49079
- ...typedConfig,
49080
- dayShift: {
49081
- ...dayShift,
49082
- breaks: newBreaks
49083
- }
49084
- };
49489
+ return { ...config, shifts: newShifts };
49085
49490
  }
49086
- return typedConfig;
49491
+ return config;
49087
49492
  }));
49088
49493
  }, []);
49089
- const updateNightShiftBreak = React24.useCallback((lineId, index, field, value) => {
49494
+ const addShiftBreak = React24.useCallback((lineId, shiftIndex) => {
49090
49495
  setLineConfigs((prev) => prev.map((config) => {
49091
- const typedConfig = config;
49092
- if (typedConfig.id === lineId) {
49093
- const nightShift = { ...typedConfig.nightShift };
49094
- const newBreaks = [...nightShift.breaks];
49095
- newBreaks[index] = { ...newBreaks[index], [field]: value };
49096
- if (field === "startTime" || field === "endTime") {
49097
- const startParts = newBreaks[index].startTime.split(":").map(Number);
49098
- const endParts = newBreaks[index].endTime.split(":").map(Number);
49099
- let startMinutes = startParts[0] * 60 + startParts[1];
49100
- let endMinutes = endParts[0] * 60 + endParts[1];
49101
- if (endMinutes < startMinutes) {
49102
- endMinutes += 24 * 60;
49103
- }
49104
- newBreaks[index].duration = endMinutes - startMinutes;
49496
+ if (config.id === lineId && config.shifts) {
49497
+ const newShifts = [...config.shifts];
49498
+ if (newShifts[shiftIndex]) {
49499
+ const shift = newShifts[shiftIndex];
49500
+ const newBreak = {
49501
+ startTime: shift.startTime,
49502
+ endTime: shift.startTime,
49503
+ duration: 0,
49504
+ remarks: ""
49505
+ };
49506
+ newShifts[shiftIndex] = {
49507
+ ...shift,
49508
+ breaks: [...shift.breaks || [], newBreak]
49509
+ };
49105
49510
  }
49106
- return {
49107
- ...typedConfig,
49108
- nightShift: {
49109
- ...nightShift,
49110
- breaks: newBreaks
49111
- }
49112
- };
49511
+ return { ...config, shifts: newShifts };
49113
49512
  }
49114
- return typedConfig;
49513
+ return config;
49115
49514
  }));
49116
49515
  }, []);
49117
- const removeDayShiftBreak = React24.useCallback((lineId, index) => {
49516
+ const updateShiftBreak = React24.useCallback((lineId, shiftIndex, breakIndex, field, value) => {
49118
49517
  setLineConfigs((prev) => prev.map((config) => {
49119
- if (config.id === lineId) {
49120
- const dayShift = { ...config.dayShift };
49121
- return {
49122
- ...config,
49123
- dayShift: {
49124
- ...dayShift,
49125
- breaks: dayShift.breaks.filter((_, i) => i !== index)
49518
+ if (config.id === lineId && config.shifts) {
49519
+ const newShifts = [...config.shifts];
49520
+ if (newShifts[shiftIndex]) {
49521
+ const shift = newShifts[shiftIndex];
49522
+ const newBreaks = [...shift.breaks || []];
49523
+ if (newBreaks[breakIndex]) {
49524
+ newBreaks[breakIndex] = { ...newBreaks[breakIndex], [field]: value };
49525
+ if (field === "startTime" || field === "endTime") {
49526
+ const startParts = newBreaks[breakIndex].startTime.split(":").map(Number);
49527
+ const endParts = newBreaks[breakIndex].endTime.split(":").map(Number);
49528
+ let startMinutes = startParts[0] * 60 + startParts[1];
49529
+ let endMinutes = endParts[0] * 60 + endParts[1];
49530
+ if (endMinutes < startMinutes) {
49531
+ endMinutes += 24 * 60;
49532
+ }
49533
+ newBreaks[breakIndex].duration = endMinutes - startMinutes;
49534
+ }
49126
49535
  }
49127
- };
49536
+ newShifts[shiftIndex] = { ...shift, breaks: newBreaks };
49537
+ }
49538
+ return { ...config, shifts: newShifts };
49128
49539
  }
49129
49540
  return config;
49130
49541
  }));
49131
49542
  }, []);
49132
- const removeNightShiftBreak = React24.useCallback((lineId, index) => {
49543
+ const removeShiftBreak = React24.useCallback((lineId, shiftIndex, breakIndex) => {
49133
49544
  setLineConfigs((prev) => prev.map((config) => {
49134
- if (config.id === lineId) {
49135
- const nightShift = { ...config.nightShift };
49136
- return {
49137
- ...config,
49138
- nightShift: {
49139
- ...nightShift,
49140
- breaks: nightShift.breaks.filter((_, i) => i !== index)
49141
- }
49142
- };
49545
+ if (config.id === lineId && config.shifts) {
49546
+ const newShifts = [...config.shifts];
49547
+ if (newShifts[shiftIndex]) {
49548
+ const shift = newShifts[shiftIndex];
49549
+ newShifts[shiftIndex] = {
49550
+ ...shift,
49551
+ breaks: (shift.breaks || []).filter((_, i) => i !== breakIndex)
49552
+ };
49553
+ }
49554
+ return { ...config, shifts: newShifts };
49143
49555
  }
49144
49556
  return config;
49145
49557
  }));
@@ -49153,29 +49565,26 @@ var ShiftsView = ({
49153
49565
  if (!lineConfig) {
49154
49566
  throw new Error("Line configuration not found");
49155
49567
  }
49156
- const dayShiftData = {
49157
- line_id: lineId,
49158
- shift_id: 0,
49159
- shift_name: "Day Shift",
49160
- start_time: lineConfig.dayShift.startTime,
49161
- end_time: lineConfig.dayShift.endTime,
49162
- breaks: formatBreaks(lineConfig.dayShift.breaks)
49163
- };
49164
- const nightShiftData = {
49165
- line_id: lineId,
49166
- shift_id: 1,
49167
- shift_name: "Night Shift",
49168
- start_time: lineConfig.nightShift.startTime,
49169
- end_time: lineConfig.nightShift.endTime,
49170
- breaks: formatBreaks(lineConfig.nightShift.breaks)
49171
- };
49172
- const dayResult = await supabase.from("line_operating_hours").upsert(dayShiftData).select();
49173
- if (dayResult.error) {
49174
- throw new Error(`Failed to save day shift: ${dayResult.error.message}`);
49568
+ const allSavedRows = [];
49569
+ for (const shift of lineConfig.shifts || []) {
49570
+ const shiftData = {
49571
+ line_id: lineId,
49572
+ shift_id: shift.shiftId,
49573
+ shift_name: shift.shiftName,
49574
+ start_time: shift.startTime,
49575
+ end_time: shift.endTime,
49576
+ breaks: formatBreaks(shift.breaks || [])
49577
+ };
49578
+ const { data, error: error2 } = await supabase.from("line_operating_hours").upsert(shiftData).select();
49579
+ if (error2) {
49580
+ throw new Error(`Failed to save shift ${shift.shiftName}: ${error2.message}`);
49581
+ }
49582
+ if (data) {
49583
+ allSavedRows.push(...data);
49584
+ }
49175
49585
  }
49176
- const nightResult = await supabase.from("line_operating_hours").upsert(nightShiftData).select();
49177
- if (nightResult.error) {
49178
- throw new Error(`Failed to save night shift: ${nightResult.error.message}`);
49586
+ if (allSavedRows.length > 0) {
49587
+ shiftConfigStore.setFromOperatingHoursRows(lineId, allSavedRows, shiftConfigStore.get(lineId));
49179
49588
  }
49180
49589
  setLineConfigs((prev) => prev.map(
49181
49590
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
@@ -49254,48 +49663,34 @@ var ShiftsView = ({
49254
49663
  )
49255
49664
  ] })
49256
49665
  ] }) }),
49257
- /* @__PURE__ */ jsxRuntime.jsxs("div", { id: `shift-panel-${config.id}`, className: "p-3 sm:p-4 md:p-6 border-t border-gray-200 w-full", children: [
49258
- /* @__PURE__ */ jsxRuntime.jsx(
49259
- ShiftPanel,
49260
- {
49261
- title: "Day Shift",
49262
- icon: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5 text-gray-600", 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" }) }),
49263
- startTime: config.dayShift.startTime,
49264
- endTime: config.dayShift.endTime,
49265
- breaks: config.dayShift.breaks,
49266
- onStartTimeChange: (value) => updateDayShiftStartTime(config.id, value),
49267
- onEndTimeChange: (value) => updateDayShiftEndTime(config.id, value),
49268
- onBreakUpdate: (index, field, value) => updateDayShiftBreak(config.id, index, field, value),
49269
- onBreakRemove: (index) => removeDayShiftBreak(config.id, index),
49270
- onBreakAdd: () => addDayShiftBreak(config.id),
49271
- shiftHours: calculateShiftHours(
49272
- config.dayShift.startTime,
49273
- config.dayShift.endTime,
49274
- config.dayShift.breaks
49275
- )
49276
- }
49277
- ),
49278
- /* @__PURE__ */ jsxRuntime.jsx(
49279
- ShiftPanel,
49280
- {
49281
- title: "Night Shift",
49282
- icon: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5 text-gray-600", 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" }) }),
49283
- startTime: config.nightShift.startTime,
49284
- endTime: config.nightShift.endTime,
49285
- breaks: config.nightShift.breaks,
49286
- onStartTimeChange: (value) => updateNightShiftStartTime(config.id, value),
49287
- onEndTimeChange: (value) => updateNightShiftEndTime(config.id, value),
49288
- onBreakUpdate: (index, field, value) => updateNightShiftBreak(config.id, index, field, value),
49289
- onBreakRemove: (index) => removeNightShiftBreak(config.id, index),
49290
- onBreakAdd: () => addNightShiftBreak(config.id),
49291
- shiftHours: calculateShiftHours(
49292
- config.nightShift.startTime,
49293
- config.nightShift.endTime,
49294
- config.nightShift.breaks
49295
- )
49296
- }
49297
- )
49298
- ] })
49666
+ /* @__PURE__ */ jsxRuntime.jsx("div", { id: `shift-panel-${config.id}`, className: "p-3 sm:p-4 md:p-6 border-t border-gray-200 w-full", children: config.shifts && config.shifts.length > 0 ? config.shifts.map((shift, shiftIndex) => /* @__PURE__ */ jsxRuntime.jsx(
49667
+ ShiftPanel,
49668
+ {
49669
+ title: shift.shiftName,
49670
+ icon: (
49671
+ // Icon based on shift name (case-insensitive)
49672
+ shift.shiftName.toLowerCase().includes("day") ? /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5 text-gray-600", 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" }) }) : shift.shiftName.toLowerCase().includes("night") ? /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5 text-gray-600", 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" }) }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "w-5 h-5 text-gray-600" })
49673
+ ),
49674
+ startTime: shift.startTime,
49675
+ endTime: shift.endTime,
49676
+ breaks: shift.breaks || [],
49677
+ onStartTimeChange: (value) => updateShiftTime(config.id, shiftIndex, "startTime", value),
49678
+ onEndTimeChange: (value) => updateShiftTime(config.id, shiftIndex, "endTime", value),
49679
+ onBreakUpdate: (breakIndex, field, value) => updateShiftBreak(config.id, shiftIndex, breakIndex, field, value),
49680
+ onBreakRemove: (breakIndex) => removeShiftBreak(config.id, shiftIndex, breakIndex),
49681
+ onBreakAdd: () => addShiftBreak(config.id, shiftIndex),
49682
+ shiftHours: calculateShiftHours(
49683
+ shift.startTime,
49684
+ shift.endTime,
49685
+ shift.breaks || []
49686
+ )
49687
+ },
49688
+ shift.shiftId
49689
+ )) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center text-gray-500 py-8", children: [
49690
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "w-12 h-12 mx-auto mb-3 text-gray-400" }),
49691
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "No shifts configured for this line" }),
49692
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400 mt-1", children: "Shifts will appear here once configured in the database" })
49693
+ ] }) })
49299
49694
  ] }, config.id)) })
49300
49695
  ] })
49301
49696
  ] });
@@ -50146,6 +50541,7 @@ var TargetsViewUI = ({
50146
50541
  onUpdateSelectedSKU,
50147
50542
  skuRequired = false
50148
50543
  }) => {
50544
+ const { displayNames: workspaceDisplayNames } = useWorkspaceDisplayNames();
50149
50545
  if (isLoading) {
50150
50546
  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..." }) }) });
50151
50547
  }
@@ -50293,7 +50689,7 @@ var TargetsViewUI = ({
50293
50689
  ] })
50294
50690
  ] }) }),
50295
50691
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-gray-100", children: line.workspaces.map((workspace) => {
50296
- const formattedName = formatWorkspaceName(workspace.name, lineId);
50692
+ const formattedName = workspaceDisplayNames[`${lineId}_${workspace.name}`] || formatWorkspaceName(workspace.name, lineId);
50297
50693
  return /* @__PURE__ */ jsxRuntime.jsxs(
50298
50694
  "div",
50299
50695
  {
@@ -51017,8 +51413,17 @@ var TargetsView = ({
51017
51413
  };
51018
51414
  const handleUpdateWorkspaceDisplayName = React24.useCallback(async (workspaceId, displayName) => {
51019
51415
  try {
51020
- await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
51021
- await forceRefreshWorkspaceDisplayNames();
51416
+ const updated = await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
51417
+ if (updated?.line_id && updated?.workspace_id) {
51418
+ upsertWorkspaceDisplayNameInCache({
51419
+ lineId: updated.line_id,
51420
+ workspaceId: updated.workspace_id,
51421
+ displayName: updated?.display_name || displayName,
51422
+ enabled: updated?.enable
51423
+ });
51424
+ } else {
51425
+ await forceRefreshWorkspaceDisplayNames();
51426
+ }
51022
51427
  sonner.toast.success("Workspace name updated successfully");
51023
51428
  } catch (error) {
51024
51429
  console.error("Error updating workspace display name:", error);
@@ -51697,7 +52102,13 @@ var WorkspaceDetailView = ({
51697
52102
  }
51698
52103
  )
51699
52104
  ] }),
51700
- 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 }) }),
52105
+ activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: renderHeaderActions ? renderHeaderActions(workspace) : /* @__PURE__ */ jsxRuntime.jsx(
52106
+ WorkspacePdfGenerator,
52107
+ {
52108
+ workspace,
52109
+ idleTimeReasons: idleTimeChartData
52110
+ }
52111
+ ) }),
51701
52112
  activeTab === "monthly_history" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5 lg:gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(
51702
52113
  WorkspaceMonthlyPdfGenerator,
51703
52114
  {
@@ -52752,6 +53163,7 @@ var WorkspaceHealthView = ({
52752
53163
  const [selectedWorkspace, setSelectedWorkspace] = React24.useState(null);
52753
53164
  const [selectedDate, setSelectedDate] = React24.useState(void 0);
52754
53165
  const [selectedShiftId, setSelectedShiftId] = React24.useState(void 0);
53166
+ const [showDatePicker, setShowDatePicker] = React24.useState(false);
52755
53167
  const effectiveTimezone = timezone || "UTC";
52756
53168
  const currentShiftDetails = getCurrentShift(effectiveTimezone, shiftConfig);
52757
53169
  const operationalDate = currentShiftDetails.date;
@@ -52770,8 +53182,9 @@ var WorkspaceHealthView = ({
52770
53182
  loading,
52771
53183
  error,
52772
53184
  refetch,
52773
- shiftConfig: loadedShiftConfig
53185
+ shiftConfig: loadedShiftConfig,
52774
53186
  // Get shift config from the hook to pass to modal
53187
+ shiftConfigMap
52775
53188
  } = useWorkspaceHealth({
52776
53189
  lineId: effectiveLineIdForFetch,
52777
53190
  // undefined in factory view = fetch all lines
@@ -52783,6 +53196,13 @@ var WorkspaceHealthView = ({
52783
53196
  date: selectedDate,
52784
53197
  shiftId: selectedShiftId
52785
53198
  });
53199
+ const modalShiftConfig = React24.useMemo(() => {
53200
+ if (!selectedWorkspace) return void 0;
53201
+ if (isFactoryView) {
53202
+ return shiftConfigMap.get(selectedWorkspace.line_id);
53203
+ }
53204
+ return loadedShiftConfig || void 0;
53205
+ }, [isFactoryView, loadedShiftConfig, selectedWorkspace, shiftConfigMap]);
52786
53206
  const handleWorkspaceClick = React24.useCallback(
52787
53207
  (workspace) => {
52788
53208
  const url = `/workspace/${workspace.workspace_id}`;
@@ -52800,28 +53220,6 @@ var WorkspaceHealthView = ({
52800
53220
  const handleCloseDetails = React24.useCallback(() => {
52801
53221
  setSelectedWorkspace(null);
52802
53222
  }, []);
52803
- const handleExport = React24.useCallback(() => {
52804
- const csv = [
52805
- ["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
52806
- ...workspaces.map((w) => [
52807
- w.workspace_display_name || "",
52808
- w.line_name || "",
52809
- w.company_name || "",
52810
- w.status,
52811
- w.last_heartbeat,
52812
- w.consecutive_misses?.toString() || "0"
52813
- ])
52814
- ].map((row) => row.join(",")).join("\n");
52815
- const blob = new Blob([csv], { type: "text/csv" });
52816
- const url = window.URL.createObjectURL(blob);
52817
- const a = document.createElement("a");
52818
- a.href = url;
52819
- a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
52820
- document.body.appendChild(a);
52821
- a.click();
52822
- document.body.removeChild(a);
52823
- window.URL.revokeObjectURL(url);
52824
- }, [workspaces]);
52825
53223
  const getStatusIcon = (status) => {
52826
53224
  switch (status) {
52827
53225
  case "healthy":
@@ -52859,104 +53257,42 @@ var WorkspaceHealthView = ({
52859
53257
  }
52860
53258
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
52861
53259
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
52862
- /* @__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: [
52863
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm:hidden", children: [
52864
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [
52865
- /* @__PURE__ */ jsxRuntime.jsx(
52866
- BackButtonMinimal,
52867
- {
52868
- onClick: () => router$1.push("/"),
52869
- text: "Back",
52870
- size: "sm",
52871
- "aria-label": "Navigate back to dashboard"
52872
- }
52873
- ),
52874
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
52875
- /* @__PURE__ */ jsxRuntime.jsx(
52876
- "button",
52877
- {
52878
- onClick: () => {
52879
- refetch();
52880
- },
52881
- className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52882
- "aria-label": "Refresh",
52883
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4" })
52884
- }
52885
- ),
52886
- /* @__PURE__ */ jsxRuntime.jsx(
52887
- "button",
52888
- {
52889
- onClick: handleExport,
52890
- className: "p-1.5 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52891
- "aria-label": "Export CSV",
52892
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" })
52893
- }
52894
- )
52895
- ] })
52896
- ] }),
52897
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2", children: [
52898
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base font-semibold text-gray-900", children: "System Health" }),
52899
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2 w-2", children: [
52900
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
52901
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-emerald-500" })
52902
- ] })
52903
- ] }) })
53260
+ /* @__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: [
53261
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsx(
53262
+ BackButtonMinimal,
53263
+ {
53264
+ onClick: () => router$1.push("/"),
53265
+ text: "Back",
53266
+ size: "default",
53267
+ "aria-label": "Navigate back to dashboard"
53268
+ }
53269
+ ) }),
53270
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center", children: [
53271
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl lg:text-3xl font-semibold text-gray-900 text-center", children: "System Health" }),
53272
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-1 text-center", children: "Monitor workspace connectivity and operational status" })
52904
53273
  ] }),
52905
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
52906
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(
52907
- BackButtonMinimal,
53274
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
53275
+ /* @__PURE__ */ jsxRuntime.jsx(
53276
+ "button",
52908
53277
  {
52909
- onClick: () => router$1.push("/"),
52910
- text: "Back",
52911
- size: "default",
52912
- "aria-label": "Navigate back to dashboard"
53278
+ onClick: () => setShowDatePicker(!showDatePicker),
53279
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
53280
+ "aria-label": "Select date",
53281
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "h-5 w-5" })
52913
53282
  }
52914
- ) }),
52915
- /* @__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: [
52916
- /* @__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" }),
52917
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
52918
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
52919
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
52920
- ] })
52921
- ] }) }),
52922
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 flex gap-2", children: [
52923
- /* @__PURE__ */ jsxRuntime.jsx(
52924
- "button",
52925
- {
52926
- onClick: () => {
52927
- refetch();
52928
- },
52929
- className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52930
- "aria-label": "Refresh",
52931
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
52932
- }
52933
- ),
52934
- /* @__PURE__ */ jsxRuntime.jsx(
52935
- "button",
52936
- {
52937
- onClick: handleExport,
52938
- className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
52939
- "aria-label": "Export CSV",
52940
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
52941
- }
52942
- )
52943
- ] }),
52944
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
52945
- ] }) }),
52946
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 sm:mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
52947
- HealthDateShiftSelector,
52948
- {
52949
- selectedDate: selectedDate || operationalDate,
52950
- selectedShiftId: selectedShiftId ?? currentShiftDetails.shiftId,
52951
- shiftConfig,
52952
- timezone: effectiveTimezone,
52953
- onDateChange: setSelectedDate,
52954
- onShiftChange: setSelectedShiftId,
52955
- isCurrentShift: isViewingCurrentShift,
52956
- onReturnToLive: handleReturnToLive
52957
- }
52958
- ) })
52959
- ] }),
53283
+ ),
53284
+ /* @__PURE__ */ jsxRuntime.jsx(
53285
+ "button",
53286
+ {
53287
+ onClick: () => refetch(),
53288
+ disabled: loading,
53289
+ className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
53290
+ "aria-label": "Refresh",
53291
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-5 w-5 ${loading ? "animate-spin" : ""}` })
53292
+ }
53293
+ )
53294
+ ] })
53295
+ ] }) }) }),
52960
53296
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
52961
53297
  summary && /* @__PURE__ */ jsxRuntime.jsxs(
52962
53298
  motion.div,
@@ -53032,13 +53368,79 @@ var WorkspaceHealthView = ({
53032
53368
  )
53033
53369
  ] })
53034
53370
  ] }),
53371
+ showDatePicker && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
53372
+ /* @__PURE__ */ jsxRuntime.jsx(
53373
+ "div",
53374
+ {
53375
+ className: "fixed inset-0 bg-black/20 z-40",
53376
+ onClick: () => setShowDatePicker(false)
53377
+ }
53378
+ ),
53379
+ /* @__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: [
53380
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
53381
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Date" }),
53382
+ /* @__PURE__ */ jsxRuntime.jsx(
53383
+ "input",
53384
+ {
53385
+ type: "date",
53386
+ value: selectedDate || operationalDate,
53387
+ max: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
53388
+ onChange: (e) => {
53389
+ setSelectedDate(e.target.value);
53390
+ },
53391
+ 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"
53392
+ }
53393
+ )
53394
+ ] }),
53395
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
53396
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-medium text-gray-500 mb-2", children: "Select Shift" }),
53397
+ /* @__PURE__ */ jsxRuntime.jsx(
53398
+ "select",
53399
+ {
53400
+ value: selectedShiftId ?? currentShiftDetails.shiftId,
53401
+ onChange: (e) => setSelectedShiftId(Number(e.target.value)),
53402
+ 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",
53403
+ children: (shiftConfig?.shifts || []).map((shift) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: shift.shiftId, children: [
53404
+ shift.shiftName,
53405
+ " (",
53406
+ shift.startTime,
53407
+ " - ",
53408
+ shift.endTime,
53409
+ ")"
53410
+ ] }, shift.shiftId))
53411
+ }
53412
+ )
53413
+ ] }),
53414
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-2", children: [
53415
+ !isViewingCurrentShift && /* @__PURE__ */ jsxRuntime.jsx(
53416
+ "button",
53417
+ {
53418
+ onClick: () => {
53419
+ handleReturnToLive();
53420
+ setShowDatePicker(false);
53421
+ },
53422
+ className: "flex-1 px-3 py-2 text-sm text-blue-600 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors",
53423
+ children: "Return to Live"
53424
+ }
53425
+ ),
53426
+ /* @__PURE__ */ jsxRuntime.jsx(
53427
+ "button",
53428
+ {
53429
+ onClick: () => setShowDatePicker(false),
53430
+ className: "flex-1 px-3 py-2 text-sm text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors",
53431
+ children: "Done"
53432
+ }
53433
+ )
53434
+ ] })
53435
+ ] }) })
53436
+ ] }),
53035
53437
  /* @__PURE__ */ jsxRuntime.jsx(
53036
53438
  WorkspaceUptimeDetailModal_default,
53037
53439
  {
53038
53440
  workspace: selectedWorkspace,
53039
53441
  isOpen: Boolean(selectedWorkspace),
53040
53442
  onClose: handleCloseDetails,
53041
- shiftConfig: loadedShiftConfig,
53443
+ shiftConfig: modalShiftConfig,
53042
53444
  date: selectedDate,
53043
53445
  shiftId: selectedShiftId
53044
53446
  }
@@ -54181,7 +54583,7 @@ function DailyBarChart({
54181
54583
  axisLine: false,
54182
54584
  tick: (props) => {
54183
54585
  const { x, y, payload } = props;
54184
- if (payload.value === 0) return null;
54586
+ if (payload.value === 0) return /* @__PURE__ */ jsxRuntime.jsx("g", {});
54185
54587
  const hours = Math.round(payload.value / (1e3 * 60 * 60) * 10) / 10;
54186
54588
  return /* @__PURE__ */ jsxRuntime.jsxs("text", { x, y, dy: 4, textAnchor: "end", className: "text-xs fill-gray-400", children: [
54187
54589
  hours,
@@ -54536,7 +54938,7 @@ var UserManagementTable = ({
54536
54938
  }
54537
54939
  ),
54538
54940
  /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Assignments" }),
54539
- 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" }),
54941
+ 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" }),
54540
54942
  /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Actions" })
54541
54943
  ] }) }),
54542
54944
  /* @__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: [
@@ -54548,12 +54950,25 @@ var UserManagementTable = ({
54548
54950
  const canChangeRole = permissions.canChangeRole(user);
54549
54951
  const canRemove = permissions.canRemoveUser(user);
54550
54952
  const hasActions = canChangeRole || canRemove;
54953
+ const canShowUsageModal = showUsageStats && (user.role_level === "plant_head" || user.role_level === "supervisor");
54954
+ const handleRowClick = (e) => {
54955
+ const target = e.target;
54956
+ if (target.closest("button") || target.closest("select") || target.closest("input") || target.closest('[role="button"]') || target.closest("[data-interactive]")) {
54957
+ return;
54958
+ }
54959
+ if (canShowUsageModal) {
54960
+ setUsageDetailUserId(user.user_id);
54961
+ setShowUsageDetailModal(true);
54962
+ }
54963
+ };
54551
54964
  return /* @__PURE__ */ jsxRuntime.jsxs(
54552
54965
  "tr",
54553
54966
  {
54967
+ onClick: handleRowClick,
54554
54968
  className: cn(
54555
- "hover:bg-gray-50 transition-colors",
54556
- isDeactivated && "opacity-60"
54969
+ "transition-all duration-150",
54970
+ isDeactivated && "opacity-60",
54971
+ canShowUsageModal ? "cursor-pointer hover:bg-blue-50/50 hover:shadow-sm" : "hover:bg-gray-50"
54557
54972
  ),
54558
54973
  children: [
54559
54974
  /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
@@ -54619,10 +55034,7 @@ var UserManagementTable = ({
54619
55034
  setShowUsageDetailModal(true);
54620
55035
  },
54621
55036
  className: "group flex items-center gap-3 hover:bg-gray-50 -mx-2 px-2 py-1.5 rounded-lg transition-all duration-200",
54622
- children: isUsageLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-pulse bg-gray-200 h-5 w-20 rounded" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
54623
- /* @__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) }),
54624
- /* @__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" })
54625
- ] })
55037
+ 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) })
54626
55038
  }
54627
55039
  ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-400", children: "-" }) }),
54628
55040
  /* @__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(
@@ -54670,25 +55082,50 @@ var UserManagementTable = ({
54670
55082
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-1", children: [
54671
55083
  (() => {
54672
55084
  const user = users.find((u) => u.user_id === openActionMenuId);
54673
- const isCurrentUser = user?.user_id === currentUserId;
54674
- const canChangeRole = user && permissions.canChangeRole(user);
54675
- return canChangeRole && /* @__PURE__ */ jsxRuntime.jsxs(
55085
+ const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
55086
+ return canShowUsage && /* @__PURE__ */ jsxRuntime.jsxs(
54676
55087
  "button",
54677
55088
  {
54678
55089
  onClick: () => {
54679
55090
  if (user) {
54680
- handleChangeRole(user);
55091
+ setUsageDetailUserId(user.user_id);
55092
+ setShowUsageDetailModal(true);
55093
+ handleCloseActionMenu();
54681
55094
  }
54682
55095
  },
54683
- 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",
54684
- disabled: isCurrentUser,
55096
+ className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
54685
55097
  children: [
54686
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserCog, { className: "w-4 h-4" }),
54687
- "Change Role"
55098
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BarChart3, { className: "w-4 h-4" }),
55099
+ "View Detailed Usage"
54688
55100
  ]
54689
55101
  }
54690
55102
  );
54691
55103
  })(),
55104
+ (() => {
55105
+ const user = users.find((u) => u.user_id === openActionMenuId);
55106
+ const isCurrentUser = user?.user_id === currentUserId;
55107
+ const canChangeRole = user && permissions.canChangeRole(user);
55108
+ const canShowUsage = showUsageStats && user && (user.role_level === "plant_head" || user.role_level === "supervisor");
55109
+ return canChangeRole && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
55110
+ canShowUsage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-gray-200 my-1" }),
55111
+ /* @__PURE__ */ jsxRuntime.jsxs(
55112
+ "button",
55113
+ {
55114
+ onClick: () => {
55115
+ if (user) {
55116
+ handleChangeRole(user);
55117
+ }
55118
+ },
55119
+ 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",
55120
+ disabled: isCurrentUser,
55121
+ children: [
55122
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserCog, { className: "w-4 h-4" }),
55123
+ "Change Role"
55124
+ ]
55125
+ }
55126
+ )
55127
+ ] });
55128
+ })(),
54692
55129
  (() => {
54693
55130
  const user = users.find((u) => u.user_id === openActionMenuId);
54694
55131
  const isCurrentUser = user?.user_id === currentUserId;
@@ -55230,7 +55667,7 @@ var TeamManagementView = ({
55230
55667
  }, {});
55231
55668
  }, [usageData, usageDateRange.daysElapsed]);
55232
55669
  const pageTitle = user?.role_level === "optifye" ? "Platform Users" : "Team Management";
55233
- const pageDescription = user?.role_level === "optifye" ? "Manage users across all companies" : user?.role_level === "owner" ? "Manage your team members" : "Manage supervisors in your factory";
55670
+ 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";
55234
55671
  const hasAccess = user ? user.role_level === void 0 || ["optifye", "owner", "plant_head"].includes(user.role_level) : false;
55235
55672
  const loadData = React24.useCallback(async () => {
55236
55673
  if (!supabase) {
@@ -57282,11 +57719,13 @@ exports.WorkspacePdfExportButton = WorkspacePdfExportButton;
57282
57719
  exports.WorkspacePdfGenerator = WorkspacePdfGenerator;
57283
57720
  exports.WorkspaceWhatsAppShareButton = WorkspaceWhatsAppShareButton;
57284
57721
  exports.actionService = actionService;
57722
+ exports.aggregateKPIsFromLineMetricsRows = aggregateKPIsFromLineMetricsRows;
57285
57723
  exports.apiUtils = apiUtils;
57286
57724
  exports.areAllLinesOnSameShift = areAllLinesOnSameShift;
57287
57725
  exports.authCoreService = authCoreService;
57288
57726
  exports.authOTPService = authOTPService;
57289
57727
  exports.authRateLimitService = authRateLimitService;
57728
+ exports.buildKPIsFromLineMetricsRow = buildKPIsFromLineMetricsRow;
57290
57729
  exports.checkRateLimit = checkRateLimit2;
57291
57730
  exports.clearAllRateLimits = clearAllRateLimits2;
57292
57731
  exports.clearRateLimit = clearRateLimit2;
@@ -57294,6 +57733,7 @@ exports.clearS3VideoCache = clearS3VideoCache;
57294
57733
  exports.clearS3VideoFromCache = clearS3VideoFromCache;
57295
57734
  exports.clearWorkspaceDisplayNamesCache = clearWorkspaceDisplayNamesCache;
57296
57735
  exports.cn = cn;
57736
+ exports.createDefaultKPIs = createDefaultKPIs;
57297
57737
  exports.createInvitationService = createInvitationService;
57298
57738
  exports.createLinesService = createLinesService;
57299
57739
  exports.createSessionTracker = createSessionTracker;
@@ -57318,6 +57758,7 @@ exports.fromUrlFriendlyName = fromUrlFriendlyName;
57318
57758
  exports.getAllLineDisplayNames = getAllLineDisplayNames;
57319
57759
  exports.getAllThreadMessages = getAllThreadMessages;
57320
57760
  exports.getAllWorkspaceDisplayNamesAsync = getAllWorkspaceDisplayNamesAsync;
57761
+ exports.getAllWorkspaceDisplayNamesSnapshot = getAllWorkspaceDisplayNamesSnapshot;
57321
57762
  exports.getAnonClient = getAnonClient;
57322
57763
  exports.getAvailableShiftIds = getAvailableShiftIds;
57323
57764
  exports.getBrowserName = getBrowserName;
@@ -57402,12 +57843,14 @@ exports.startCoreSessionRecording = startCoreSessionRecording;
57402
57843
  exports.stopCoreSessionRecording = stopCoreSessionRecording;
57403
57844
  exports.storeWorkspaceMapping = storeWorkspaceMapping;
57404
57845
  exports.streamProxyConfig = streamProxyConfig;
57846
+ exports.subscribeWorkspaceDisplayNames = subscribeWorkspaceDisplayNames;
57405
57847
  exports.throttledReloadDashboard = throttledReloadDashboard;
57406
57848
  exports.toUrlFriendlyName = toUrlFriendlyName;
57407
57849
  exports.trackCoreEvent = trackCoreEvent;
57408
57850
  exports.trackCorePageView = trackCorePageView;
57409
57851
  exports.transformToChartData = transformToChartData;
57410
57852
  exports.updateThreadTitle = updateThreadTitle;
57853
+ exports.upsertWorkspaceDisplayNameInCache = upsertWorkspaceDisplayNameInCache;
57411
57854
  exports.useAccessControl = useAccessControl;
57412
57855
  exports.useActiveBreaks = useActiveBreaks;
57413
57856
  exports.useActiveLineId = useActiveLineId;
@@ -57471,6 +57914,7 @@ exports.useSubscriptionManager = useSubscriptionManager;
57471
57914
  exports.useSubscriptionManagerSafe = useSubscriptionManagerSafe;
57472
57915
  exports.useSupabase = useSupabase;
57473
57916
  exports.useSupabaseClient = useSupabaseClient;
57917
+ exports.useSupervisorsByLineIds = useSupervisorsByLineIds;
57474
57918
  exports.useTargets = useTargets;
57475
57919
  exports.useTeamManagementPermissions = useTeamManagementPermissions;
57476
57920
  exports.useTheme = useTheme;