@optifye/dashboard-core 6.9.1 → 6.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -198,7 +198,7 @@ var DEFAULT_DATE_TIME_CONFIG = {
198
198
  };
199
199
  var DEFAULT_ENDPOINTS_CONFIG = {
200
200
  whatsapp: "/api/send-whatsapp-direct",
201
- agnoApiUrl: process.env.NEXT_PUBLIC_AGNO_URL || "https://optifye-agent-production.up.railway.app",
201
+ agnoApiUrl: process.env.NEXT_PUBLIC_AGNO_URL || "https://fastapi-production-111f9.up.railway.app",
202
202
  // Default AGNO API URL
203
203
  // Use environment variable for Slack webhook URL for privacy/security
204
204
  // Note: SLACK_WEBHOOK_URL is server-side only, NEXT_PUBLIC_SLACK_WEBHOOK_URL works client-side but is less secure
@@ -2058,6 +2058,59 @@ var workspaceService = {
2058
2058
  console.error("Error updating shift configuration:", error);
2059
2059
  throw error;
2060
2060
  }
2061
+ },
2062
+ /**
2063
+ * Fetch bulk targets data for multiple lines and shifts in a single API call
2064
+ * This replaces 120+ individual API calls with 1 optimized call
2065
+ *
2066
+ * @param params - Parameters for the bulk fetch
2067
+ * @returns Promise with complete targets data for all lines and shifts
2068
+ */
2069
+ async fetchBulkTargets(params) {
2070
+ try {
2071
+ const token = await getAuthToken();
2072
+ const apiUrl = getBackendUrl();
2073
+ const {
2074
+ companyId,
2075
+ lineIds,
2076
+ date,
2077
+ shifts = [0, 1],
2078
+ includeSkus = true,
2079
+ includeActions = true
2080
+ } = params;
2081
+ const queryParams = new URLSearchParams({
2082
+ company_id: companyId,
2083
+ line_ids: lineIds.join(","),
2084
+ date,
2085
+ shifts: shifts.join(","),
2086
+ include_skus: includeSkus.toString(),
2087
+ include_actions: includeActions.toString()
2088
+ });
2089
+ const response = await fetch(
2090
+ `${apiUrl}/api/targets/bulk-load?${queryParams}`,
2091
+ {
2092
+ method: "GET",
2093
+ headers: {
2094
+ "Authorization": `Bearer ${token}`,
2095
+ "Content-Type": "application/json"
2096
+ }
2097
+ }
2098
+ );
2099
+ if (!response.ok) {
2100
+ const errorText = await response.text();
2101
+ throw new Error(`Backend API error (${response.status}): ${errorText}`);
2102
+ }
2103
+ const data = await response.json();
2104
+ console.log("[fetchBulkTargets] Success:", {
2105
+ lineCount: data.metadata?.line_count,
2106
+ totalWorkspaces: data.metadata?.total_workspaces,
2107
+ timestamp: data.metadata?.timestamp
2108
+ });
2109
+ return data;
2110
+ } catch (error) {
2111
+ console.error("Error fetching bulk targets:", error);
2112
+ throw error;
2113
+ }
2061
2114
  }
2062
2115
  };
2063
2116
  var WorkspaceHealthService = class _WorkspaceHealthService {
@@ -3216,7 +3269,7 @@ var SSEChatClient = class {
3216
3269
  user_id: userId,
3217
3270
  context
3218
3271
  });
3219
- const agnoApiUrl = this.baseUrl || "https://fastapi-production-111f9.up.railway.app";
3272
+ const agnoApiUrl = this.baseUrl || (process.env.NEXT_PUBLIC_AGNO_URL || "https://fastapi-production-111f9.up.railway.app");
3220
3273
  const endpoint = `${agnoApiUrl}/api/v2/chat`;
3221
3274
  console.log("[SSEClient] Posting directly to AGNO:", endpoint);
3222
3275
  const response = await fetch(endpoint, {
@@ -5792,60 +5845,32 @@ var UserManagementService = class {
5792
5845
  }
5793
5846
  }
5794
5847
  /**
5795
- * Deactivate a user
5796
- * @param input - Deactivation input
5848
+ * Permanently delete a user
5849
+ * @param userId - The ID of the user to delete
5850
+ * @returns Promise with deletion response
5797
5851
  */
5798
- async deactivateUser(input) {
5852
+ async deleteUser(userId) {
5799
5853
  try {
5800
5854
  const token = await this.getAuthToken();
5801
5855
  const backendUrl = this.getBackendUrl();
5802
5856
  const response = await fetch(
5803
- `${backendUrl}/api/users/deactivate`,
5804
- {
5805
- method: "POST",
5806
- headers: {
5807
- "Authorization": `Bearer ${token}`,
5808
- "Content-Type": "application/json"
5809
- },
5810
- body: JSON.stringify(input)
5811
- }
5812
- );
5813
- if (!response.ok) {
5814
- const errorData = await response.json();
5815
- throw new Error(`Failed to deactivate user: ${errorData.detail || response.statusText}`);
5816
- }
5817
- console.log("[UserManagementService] User deactivated successfully");
5818
- } catch (error) {
5819
- console.error("[UserManagementService] Error deactivating user:", error);
5820
- throw error;
5821
- }
5822
- }
5823
- /**
5824
- * Reactivate a user
5825
- * @param input - Reactivation input
5826
- */
5827
- async reactivateUser(input) {
5828
- try {
5829
- const token = await this.getAuthToken();
5830
- const backendUrl = this.getBackendUrl();
5831
- const response = await fetch(
5832
- `${backendUrl}/api/users/reactivate`,
5857
+ `${backendUrl}/api/users/${userId}`,
5833
5858
  {
5834
- method: "POST",
5859
+ method: "DELETE",
5835
5860
  headers: {
5836
- "Authorization": `Bearer ${token}`,
5837
- "Content-Type": "application/json"
5838
- },
5839
- body: JSON.stringify(input)
5861
+ "Authorization": `Bearer ${token}`
5862
+ }
5840
5863
  }
5841
5864
  );
5842
5865
  if (!response.ok) {
5843
5866
  const errorData = await response.json();
5844
- throw new Error(`Failed to reactivate user: ${errorData.detail || response.statusText}`);
5867
+ throw new Error(`Failed to delete user: ${errorData.detail || response.statusText}`);
5845
5868
  }
5846
- console.log("[UserManagementService] User reactivated successfully");
5869
+ const result = await response.json();
5870
+ console.log("[UserManagementService] User permanently deleted:", result);
5871
+ return result;
5847
5872
  } catch (error) {
5848
- console.error("[UserManagementService] Error reactivating user:", error);
5873
+ console.error("[UserManagementService] Error deleting user:", error);
5849
5874
  throw error;
5850
5875
  }
5851
5876
  }
@@ -7658,7 +7683,7 @@ var useLeaderboardMetrics = (date, shiftId, limit = 10, filter2 = "all") => {
7658
7683
  refetch: fetchLeaderboard
7659
7684
  };
7660
7685
  };
7661
- var useDashboardMetrics = ({ onLineMetricsUpdate, lineId }) => {
7686
+ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId, userAccessibleLineIds }) => {
7662
7687
  const { supabaseUrl, supabaseKey } = useDashboardConfig();
7663
7688
  const entityConfig = useEntityConfig();
7664
7689
  const databaseConfig = useDatabaseConfig();
@@ -7703,7 +7728,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId }) => {
7703
7728
  try {
7704
7729
  const currentShiftDetails = getCurrentShift(defaultTimezone, shiftConfig);
7705
7730
  const operationalDate = getOperationalDate(defaultTimezone);
7706
- const targetLineIds = currentLineIdToUse === (entityConfig.factoryViewId || "factory") ? getConfiguredLineIds(entityConfig) : [currentLineIdToUse];
7731
+ const targetLineIds = currentLineIdToUse === (entityConfig.factoryViewId || "factory") ? userAccessibleLineIds || getConfiguredLineIds(entityConfig) : [currentLineIdToUse];
7707
7732
  if (targetLineIds.length === 0 && currentLineIdToUse === (entityConfig.factoryViewId || "factory")) {
7708
7733
  throw new Error("Factory view selected, but no lines are configured in entityConfig.");
7709
7734
  }
@@ -7829,7 +7854,7 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId }) => {
7829
7854
  }
7830
7855
  const currentShiftDetails = getCurrentShift(defaultTimezone, shiftConfig);
7831
7856
  const operationalDateForSubscription = getOperationalDate(defaultTimezone);
7832
- const targetLineIds = currentLineIdToUse === (entityConfig.factoryViewId || "factory") ? getConfiguredLineIds(entityConfig) : [currentLineIdToUse];
7857
+ const targetLineIds = currentLineIdToUse === (entityConfig.factoryViewId || "factory") ? userAccessibleLineIds || getConfiguredLineIds(entityConfig) : [currentLineIdToUse];
7833
7858
  if (targetLineIds.length === 0) return;
7834
7859
  const wsMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
7835
7860
  const lineMetricsFilter = `line_id=in.(${targetLineIds.map((id3) => `"${id3}"`).join(",")})`;
@@ -7867,7 +7892,8 @@ var useDashboardMetrics = ({ onLineMetricsUpdate, lineId }) => {
7867
7892
  entityConfig?.companyId,
7868
7893
  entityConfig?.factoryViewId,
7869
7894
  defaultTimezone,
7870
- lineId
7895
+ lineId,
7896
+ userAccessibleLineIds
7871
7897
  ]);
7872
7898
  return {
7873
7899
  workspaceMetrics: metrics2?.workspaceMetrics || [],
@@ -9857,7 +9883,8 @@ var useAllWorkspaceMetrics = (options) => {
9857
9883
  }
9858
9884
  setError(null);
9859
9885
  try {
9860
- const configuredLineIds = getConfiguredLineIds(entityConfig);
9886
+ const allConfiguredLineIds = getConfiguredLineIds(entityConfig);
9887
+ const configuredLineIds = options?.allowedLineIds ? allConfiguredLineIds.filter((id3) => options.allowedLineIds.includes(id3)) : allConfiguredLineIds;
9861
9888
  let enabledWorkspaceIds = [];
9862
9889
  for (const lineId of configuredLineIds) {
9863
9890
  const workspaces2 = await workspaceService.getWorkspaces(lineId);
@@ -9920,7 +9947,7 @@ var useAllWorkspaceMetrics = (options) => {
9920
9947
  setLoading(false);
9921
9948
  isFetchingRef.current = false;
9922
9949
  }
9923
- }, [queryDate, queryShiftId, metricsTable, supabase, entityConfig.companyId]);
9950
+ }, [queryDate, queryShiftId, metricsTable, supabase, entityConfig.companyId, entityConfig, options?.allowedLineIds]);
9924
9951
  useEffect(() => {
9925
9952
  if (!initialized) {
9926
9953
  fetchWorkspaceMetrics();
@@ -10709,23 +10736,18 @@ function formatRelativeTime(timestamp) {
10709
10736
  const diffHours = Math.floor(diffMinutes / 60);
10710
10737
  const diffDays = Math.floor(diffHours / 24);
10711
10738
  if (diffSeconds < 60) {
10712
- return `${diffSeconds}s ago`;
10739
+ return "Less than a minute ago";
10713
10740
  }
10714
10741
  if (diffMinutes < 60) {
10715
- const remainingSeconds = diffSeconds % 60;
10716
- if (remainingSeconds === 0) {
10717
- return `${diffMinutes}m ago`;
10718
- }
10719
- return `${diffMinutes}m ${remainingSeconds}s ago`;
10742
+ const minuteLabel = diffMinutes === 1 ? "minute" : "minutes";
10743
+ return `${diffMinutes} ${minuteLabel} ago`;
10720
10744
  }
10721
10745
  if (diffHours < 24) {
10722
- const remainingMinutes = diffMinutes % 60;
10723
- if (remainingMinutes === 0) {
10724
- return `${diffHours}h ago`;
10725
- }
10726
- return `${diffHours}h ${remainingMinutes}m ago`;
10746
+ const hourLabel = diffHours === 1 ? "hour" : "hours";
10747
+ return `${diffHours} ${hourLabel} ago`;
10727
10748
  }
10728
- return `${diffDays}d ago`;
10749
+ const dayLabel = diffDays === 1 ? "day" : "days";
10750
+ return `${diffDays} ${dayLabel} ago`;
10729
10751
  } catch (error) {
10730
10752
  console.error("[formatRelativeTime] Error formatting timestamp:", error);
10731
10753
  return "Unknown";
@@ -10741,8 +10763,8 @@ function getNextUpdateInterval(timestamp) {
10741
10763
  const diffSeconds = Math.floor(diffMs / 1e3);
10742
10764
  const diffMinutes = Math.floor(diffSeconds / 60);
10743
10765
  const diffHours = Math.floor(diffMinutes / 60);
10744
- if (diffSeconds < 60) return 1e3;
10745
- if (diffMinutes < 60) return 1e3;
10766
+ if (diffSeconds < 60) return 1e4;
10767
+ if (diffMinutes < 60) return 6e4;
10746
10768
  if (diffHours < 24) return 6e4;
10747
10769
  return 36e5;
10748
10770
  } catch (error) {
@@ -10919,50 +10941,129 @@ function useAccessControl() {
10919
10941
  const userRole = useMemo(() => {
10920
10942
  if (!user?.role_level) return null;
10921
10943
  const roleLevel = user.role_level;
10922
- if (roleLevel === "owner" || roleLevel === "plant_head" || roleLevel === "supervisor") {
10944
+ if (roleLevel === "owner" || roleLevel === "plant_head" || roleLevel === "supervisor" || roleLevel === "optifye") {
10923
10945
  return roleLevel;
10924
10946
  }
10925
10947
  return "supervisor";
10926
10948
  }, [user?.role_level]);
10927
- const allPages = [
10928
- "/",
10929
- "/leaderboard",
10930
- "/kpis",
10931
- "/targets",
10932
- "/shifts",
10933
- "/supervisor-management",
10934
- "/skus",
10935
- "/ai-agent",
10936
- "/help",
10937
- "/health",
10938
- "/profile",
10939
- "/workspace",
10940
- "/factory-view"
10941
- ];
10949
+ const assignedLineIds = useMemo(() => {
10950
+ if (!user) return [];
10951
+ if (user.role_level === "supervisor") {
10952
+ const lines = user.properties?.line_id || user.properties?.line_ids || [];
10953
+ return Array.isArray(lines) ? lines : [];
10954
+ }
10955
+ return [];
10956
+ }, [user]);
10957
+ const assignedFactoryIds = useMemo(() => {
10958
+ if (!user) return [];
10959
+ if (user.role_level === "plant_head") {
10960
+ const factories = user.properties?.factory_id || user.properties?.factory_ids || [];
10961
+ return Array.isArray(factories) ? factories : [];
10962
+ }
10963
+ return [];
10964
+ }, [user]);
10965
+ const roleAccessMap = {
10966
+ optifye: [
10967
+ "/",
10968
+ "/leaderboard",
10969
+ "/kpis",
10970
+ "/targets",
10971
+ "/shifts",
10972
+ "/supervisor-management",
10973
+ "/skus",
10974
+ "/ai-agent",
10975
+ "/help",
10976
+ "/health",
10977
+ "/profile",
10978
+ "/workspace",
10979
+ "/factory-view",
10980
+ "/team-management"
10981
+ ],
10982
+ owner: [
10983
+ "/",
10984
+ "/leaderboard",
10985
+ "/kpis",
10986
+ "/targets",
10987
+ "/shifts",
10988
+ "/supervisor-management",
10989
+ "/skus",
10990
+ "/ai-agent",
10991
+ "/help",
10992
+ "/health",
10993
+ "/profile",
10994
+ "/workspace",
10995
+ "/factory-view",
10996
+ "/team-management"
10997
+ ],
10998
+ plant_head: [
10999
+ "/",
11000
+ "/leaderboard",
11001
+ "/kpis",
11002
+ "/targets",
11003
+ "/shifts",
11004
+ "/supervisor-management",
11005
+ "/skus",
11006
+ "/ai-agent",
11007
+ "/help",
11008
+ "/health",
11009
+ "/profile",
11010
+ "/workspace",
11011
+ "/factory-view",
11012
+ "/team-management"
11013
+ ],
11014
+ supervisor: [
11015
+ "/",
11016
+ "/leaderboard",
11017
+ "/kpis",
11018
+ "/targets",
11019
+ "/shifts",
11020
+ "/skus",
11021
+ "/ai-agent",
11022
+ "/help",
11023
+ "/health",
11024
+ "/profile",
11025
+ "/workspace"
11026
+ ]
11027
+ };
10942
11028
  const accessiblePages = useMemo(() => {
10943
- return allPages;
10944
- }, []);
11029
+ if (!userRole) return [];
11030
+ return roleAccessMap[userRole] || [];
11031
+ }, [userRole]);
10945
11032
  const hasAccess = useMemo(() => {
10946
11033
  return (path) => {
10947
- return true;
11034
+ if (!userRole) return false;
11035
+ const basePath = path.split("?")[0].split("/").slice(0, 2).join("/");
11036
+ const hasBaseAccess = accessiblePages.includes(basePath) || accessiblePages.includes("/");
11037
+ if (userRole === "supervisor" && assignedLineIds.length > 0) {
11038
+ if (path.includes("/kpis/") || path.includes("/workspace/")) {
11039
+ const lineIdMatch = path.match(/\/kpis\/([^/?]+)/);
11040
+ if (lineIdMatch) {
11041
+ const pathLineId = lineIdMatch[1];
11042
+ return assignedLineIds.includes(pathLineId);
11043
+ }
11044
+ }
11045
+ }
11046
+ return hasBaseAccess;
10948
11047
  };
10949
- }, []);
11048
+ }, [userRole, accessiblePages, assignedLineIds]);
10950
11049
  const isPageVisible = useMemo(() => {
10951
11050
  return (path) => {
10952
- return true;
11051
+ return hasAccess(path);
10953
11052
  };
10954
- }, []);
11053
+ }, [hasAccess]);
10955
11054
  const canAccessPage = useMemo(() => {
10956
11055
  return (path) => {
10957
- return true;
11056
+ return hasAccess(path);
10958
11057
  };
10959
- }, []);
11058
+ }, [hasAccess]);
10960
11059
  return {
10961
11060
  userRole,
10962
11061
  hasAccess,
10963
11062
  accessiblePages,
10964
11063
  isPageVisible,
10965
- canAccessPage
11064
+ canAccessPage,
11065
+ assignedLineIds,
11066
+ assignedFactoryIds
10966
11067
  };
10967
11068
  }
10968
11069
 
@@ -11126,45 +11227,51 @@ function useHasLineAccess(lineId, configLineIds) {
11126
11227
  function useLineSupervisor(lineId) {
11127
11228
  const supabase = useSupabase();
11128
11229
  const [supervisor, setSupervisor] = useState(null);
11230
+ const [supervisors, setSupervisors] = useState([]);
11129
11231
  const [isLoading, setIsLoading] = useState(true);
11130
11232
  const [error, setError] = useState(null);
11131
- console.log("[useLineSupervisor] Hook initialized for lineId:", lineId);
11132
11233
  const fetchSupervisor = useCallback(async () => {
11133
11234
  if (!lineId || !supabase) {
11134
- console.log("[useLineSupervisor] Skipping fetch - lineId or supabase missing:", { lineId, hasSupabase: !!supabase });
11135
11235
  setIsLoading(false);
11136
11236
  return;
11137
11237
  }
11138
11238
  try {
11139
11239
  setIsLoading(true);
11140
11240
  setError(null);
11141
- console.log("[useLineSupervisor] Fetching supervisor for lineId:", lineId);
11142
- const { data, error: fetchError } = await supabase.from("user_roles").select("user_id, email, properties").eq("role_level", "supervisor").filter("properties->line_id", "@>", `["${lineId}"]`).limit(1);
11143
- console.log("[useLineSupervisor] Query result:", { data, error: fetchError, lineId });
11241
+ const { data, error: fetchError } = await supabase.from("user_roles").select("user_id, email, properties").eq("role_level", "supervisor");
11144
11242
  if (fetchError) {
11145
11243
  console.error("[useLineSupervisor] Query error:", fetchError);
11146
11244
  throw fetchError;
11147
11245
  }
11148
11246
  if (data && data.length > 0) {
11149
- const supervisorData = data[0];
11150
- console.log("[useLineSupervisor] Found supervisor:", {
11151
- email: supervisorData.email,
11152
- properties: supervisorData.properties,
11153
- line_id_in_properties: supervisorData.properties?.line_id
11154
- });
11155
- const displayName = supervisorData.email.split("@")[0] || supervisorData.email;
11156
- setSupervisor({
11157
- userId: supervisorData.user_id,
11158
- email: supervisorData.email,
11159
- displayName
11247
+ const supervisorsForLine = data.filter((supervisorData) => {
11248
+ const lineIds = supervisorData.properties?.line_id || supervisorData.properties?.line_ids || [];
11249
+ const isAssigned = Array.isArray(lineIds) && lineIds.includes(lineId);
11250
+ return isAssigned;
11160
11251
  });
11252
+ if (supervisorsForLine.length > 0) {
11253
+ const allSupervisors = supervisorsForLine.map((supervisorData) => {
11254
+ const displayName = supervisorData.email.split("@")[0] || supervisorData.email;
11255
+ return {
11256
+ userId: supervisorData.user_id,
11257
+ email: supervisorData.email,
11258
+ displayName
11259
+ };
11260
+ });
11261
+ setSupervisors(allSupervisors);
11262
+ setSupervisor(allSupervisors[0]);
11263
+ } else {
11264
+ setSupervisors([]);
11265
+ setSupervisor(null);
11266
+ }
11161
11267
  } else {
11162
- console.log("[useLineSupervisor] No supervisor found for line:", lineId);
11268
+ setSupervisors([]);
11163
11269
  setSupervisor(null);
11164
11270
  }
11165
11271
  } catch (err) {
11166
- console.error("[useLineSupervisor] Error fetching supervisor:", err);
11167
- setError(err instanceof Error ? err : new Error("Failed to fetch supervisor"));
11272
+ console.error("[useLineSupervisor] Error fetching supervisors:", err);
11273
+ setError(err instanceof Error ? err : new Error("Failed to fetch supervisors"));
11274
+ setSupervisors([]);
11168
11275
  setSupervisor(null);
11169
11276
  } finally {
11170
11277
  setIsLoading(false);
@@ -11189,7 +11296,6 @@ function useLineSupervisor(lineId) {
11189
11296
  filter: `role_level=eq.supervisor`
11190
11297
  },
11191
11298
  (payload) => {
11192
- console.log("[useLineSupervisor] Real-time update received:", payload);
11193
11299
  fetchSupervisor();
11194
11300
  }
11195
11301
  ).subscribe();
@@ -11201,9 +11307,11 @@ function useLineSupervisor(lineId) {
11201
11307
  }
11202
11308
  };
11203
11309
  }, [lineId, supabase, fetchSupervisor]);
11310
+ const supervisorName = supervisors.length > 0 ? supervisors.map((s) => s.displayName).join(", ") : null;
11204
11311
  return {
11205
- supervisorName: supervisor?.displayName || null,
11312
+ supervisorName,
11206
11313
  supervisor,
11314
+ supervisors,
11207
11315
  isLoading,
11208
11316
  error
11209
11317
  };
@@ -34074,14 +34182,16 @@ var WorkspaceWhatsAppShareButton = ({
34074
34182
  };
34075
34183
  var WorkspacePdfGenerator = ({ workspace, className }) => {
34076
34184
  const [isGenerating, setIsGenerating] = useState(false);
34185
+ const entityConfig = useEntityConfig();
34077
34186
  const generatePDF = async () => {
34078
34187
  setIsGenerating(true);
34079
34188
  try {
34189
+ const lineName = workspace.line_name || getLineDisplayName(entityConfig, workspace.line_id);
34080
34190
  trackCoreEvent("Workspace PDF Export Clicked", {
34081
34191
  workspace_id: workspace.workspace_id,
34082
34192
  line_id: workspace.line_id,
34083
34193
  workspace_name: workspace.workspace_name,
34084
- line_name: workspace.line_name
34194
+ line_name: lineName
34085
34195
  });
34086
34196
  const doc = new jsPDF$1();
34087
34197
  doc.setFontSize(14);
@@ -34102,7 +34212,7 @@ var WorkspacePdfGenerator = ({ workspace, className }) => {
34102
34212
  doc.setFontSize(32);
34103
34213
  doc.setFont("helvetica", "bold");
34104
34214
  doc.setTextColor(0, 0, 0);
34105
- doc.text("Line 1", 20, 40);
34215
+ doc.text(lineName, 20, 40);
34106
34216
  doc.setFontSize(22);
34107
34217
  doc.setFont("helvetica", "normal");
34108
34218
  doc.setTextColor(40, 40, 40);
@@ -38802,7 +38912,7 @@ var AIAgentView = () => {
38802
38912
  const renderedContentCache = useRef(/* @__PURE__ */ new Map());
38803
38913
  const { createThread, mutate: mutateThreads } = useThreads();
38804
38914
  const { messages, addMessage, setMessages } = useMessages(activeThreadId);
38805
- const agnoApiUrl = config.endpoints?.agnoApiUrl || "https://optifye-agent-production.up.railway.app";
38915
+ const agnoApiUrl = config.endpoints?.agnoApiUrl || "https://fastapi-production-111f9.up.railway.app";
38806
38916
  const sseClient = useMemo(() => {
38807
38917
  console.log("[AIAgentView] Using AGNO API URL:", agnoApiUrl);
38808
38918
  return new SSEChatClient(agnoApiUrl);
@@ -41336,7 +41446,6 @@ function HomeView({
41336
41446
  factoryName = "Simba Beer - Line 1"
41337
41447
  }) {
41338
41448
  const [isHydrated, setIsHydrated] = useState(false);
41339
- const availableLineIds = useMemo(() => [factoryViewId, ...allLineIds], [factoryViewId, allLineIds]);
41340
41449
  const [selectedLineId, setSelectedLineId] = useState(factoryViewId);
41341
41450
  const [isChangingFilter, setIsChangingFilter] = useState(false);
41342
41451
  const [errorMessage, setErrorMessage] = useState(null);
@@ -41346,6 +41455,21 @@ function HomeView({
41346
41455
  const entityConfig = useEntityConfig();
41347
41456
  const supabaseClient = useSupabaseClient();
41348
41457
  const { user } = useAuth();
41458
+ const isSupervisor = user?.role_level === "supervisor";
41459
+ const hasMultipleLines = allLineIds.length > 1;
41460
+ const availableLineIds = useMemo(() => {
41461
+ if (isSupervisor && !hasMultipleLines) {
41462
+ return allLineIds;
41463
+ }
41464
+ return [factoryViewId, ...allLineIds];
41465
+ }, [factoryViewId, allLineIds, isSupervisor, hasMultipleLines]);
41466
+ useEffect(() => {
41467
+ if (user) {
41468
+ if (isSupervisor && allLineIds.length > 0) {
41469
+ setSelectedLineId(allLineIds[0]);
41470
+ }
41471
+ }
41472
+ }, [user, isSupervisor, allLineIds]);
41349
41473
  const userCompanyId = useMemo(() => {
41350
41474
  return user?.properties?.company_id || user?.company_id || entityConfig.companyId;
41351
41475
  }, [user, entityConfig.companyId]);
@@ -41406,7 +41530,9 @@ function HomeView({
41406
41530
  refetch: refetchMetrics
41407
41531
  } = useDashboardMetrics({
41408
41532
  lineId: selectedLineId,
41409
- onLineMetricsUpdate
41533
+ onLineMetricsUpdate,
41534
+ userAccessibleLineIds: allLineIds
41535
+ // Pass user's accessible lines for supervisor filtering
41410
41536
  });
41411
41537
  const {
41412
41538
  activeBreaks: allActiveBreaks,
@@ -43465,7 +43591,8 @@ var LeaderboardDetailView = memo(({
43465
43591
  line1Id = "",
43466
43592
  line2Id = "",
43467
43593
  lineNames = {},
43468
- className = ""
43594
+ className = "",
43595
+ userAccessibleLineIds
43469
43596
  }) => {
43470
43597
  const navigation = useNavigation();
43471
43598
  const entityConfig = useEntityConfig();
@@ -43505,7 +43632,9 @@ var LeaderboardDetailView = memo(({
43505
43632
  refreshWorkspaces
43506
43633
  } = useAllWorkspaceMetrics({
43507
43634
  initialDate: date,
43508
- initialShiftId: typeof shift === "number" ? shift : typeof shift === "string" ? parseInt(shift) : void 0
43635
+ initialShiftId: typeof shift === "number" ? shift : typeof shift === "string" ? parseInt(shift) : void 0,
43636
+ allowedLineIds: userAccessibleLineIds
43637
+ // Filter to user's accessible lines only
43509
43638
  });
43510
43639
  const getShiftName = useCallback((shiftId2) => {
43511
43640
  if (shiftId2 === void 0) return "Day";
@@ -44861,12 +44990,17 @@ var ACTION_NAMES = {
44861
44990
  var calculatePPH = (cycleTime, breaks = [], shiftHours = 0) => {
44862
44991
  if (cycleTime === "" || cycleTime === 0) return "";
44863
44992
  const pph = 3600 / cycleTime;
44864
- return Number(pph.toFixed(1));
44993
+ return Math.round(pph);
44865
44994
  };
44866
44995
  var calculateDayOutput = (pph, shiftHours, breaks = []) => {
44867
44996
  if (pph === "") return "";
44868
44997
  return Math.round(pph * shiftHours);
44869
44998
  };
44999
+ var calculateDayOutputFromCycleTime = (cycleTime, shiftHours) => {
45000
+ if (cycleTime === "" || cycleTime === 0 || shiftHours === 0) return "";
45001
+ const dayOutput = 3600 / cycleTime * shiftHours;
45002
+ return Math.round(dayOutput);
45003
+ };
44870
45004
  var formatWorkspaceName = (name, lineId) => {
44871
45005
  return getWorkspaceDisplayName(name, lineId);
44872
45006
  };
@@ -44878,20 +45012,6 @@ var getStoredLineState2 = (lineId) => {
44878
45012
  return false;
44879
45013
  }
44880
45014
  };
44881
- var calculateShiftHours2 = (startTime, endTime, breaks = []) => {
44882
- if (!startTime || !endTime) return 8;
44883
- const [startHour, startMinute] = startTime.split(":").map(Number);
44884
- const [endHour, endMinute] = endTime.split(":").map(Number);
44885
- let startMinutes = startHour * 60 + startMinute;
44886
- let endMinutes = endHour * 60 + endMinute;
44887
- if (endMinutes < startMinutes) {
44888
- endMinutes += 24 * 60;
44889
- }
44890
- const safeBreaks = Array.isArray(breaks) ? breaks : [];
44891
- const totalBreakMinutes = safeBreaks.reduce((total, breakItem) => total + breakItem.duration, 0);
44892
- const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
44893
- return Number(hoursDiff.toFixed(1));
44894
- };
44895
45015
  var BulkConfigureModal = ({
44896
45016
  isOpen,
44897
45017
  onClose,
@@ -46099,121 +46219,6 @@ var TargetsView = ({
46099
46219
  const dashboardConfig = useDashboardConfig();
46100
46220
  const { skus, isLoading: skusLoading } = useSKUs(companyId);
46101
46221
  const skuEnabled = dashboardConfig?.skuConfig?.enabled || false;
46102
- const loadOperatingHours = useCallback(async (lineId, shiftId) => {
46103
- try {
46104
- const { data: { session } } = await supabase.auth.getSession();
46105
- if (!session?.access_token) {
46106
- console.error("No authentication token available");
46107
- return {
46108
- startTime: "08:00",
46109
- // Default values
46110
- endTime: "19:00",
46111
- breaks: []
46112
- };
46113
- }
46114
- const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
46115
- if (!backendUrl) {
46116
- console.error("Backend URL is not configured");
46117
- return {
46118
- startTime: "08:00",
46119
- // Default values
46120
- endTime: "19:00",
46121
- breaks: []
46122
- };
46123
- }
46124
- const response = await fetch(`${backendUrl}/api/lines/${lineId}/operating-hours?shift_id=${shiftId}`, {
46125
- headers: {
46126
- "Authorization": `Bearer ${session.access_token}`,
46127
- "Content-Type": "application/json"
46128
- }
46129
- });
46130
- if (!response.ok) {
46131
- console.log(`No operating hours found for line ${lineId}, shift ${shiftId} - using defaults`);
46132
- return {
46133
- startTime: "08:00",
46134
- // Default values
46135
- endTime: "19:00",
46136
- breaks: []
46137
- };
46138
- }
46139
- const data = await response.json();
46140
- let breaks = [];
46141
- if (data?.breaks) {
46142
- if (Array.isArray(data.breaks)) {
46143
- breaks = data.breaks.map((breakItem) => {
46144
- const startTime = breakItem.start || breakItem.startTime || "00:00";
46145
- const endTime = breakItem.end || breakItem.endTime || "00:00";
46146
- const calculateDuration = (start, end) => {
46147
- const [startHour, startMinute] = start.split(":").map(Number);
46148
- const [endHour, endMinute] = end.split(":").map(Number);
46149
- let startMinutes = startHour * 60 + startMinute;
46150
- let endMinutes = endHour * 60 + endMinute;
46151
- if (endMinutes < startMinutes) {
46152
- endMinutes += 24 * 60;
46153
- }
46154
- return endMinutes - startMinutes;
46155
- };
46156
- return {
46157
- startTime,
46158
- endTime,
46159
- duration: breakItem.duration || calculateDuration(startTime, endTime)
46160
- };
46161
- });
46162
- } else if (typeof data.breaks === "object" && data.breaks.breaks) {
46163
- breaks = data.breaks.breaks.map((breakItem) => {
46164
- const startTime = breakItem.start || breakItem.startTime || "00:00";
46165
- const endTime = breakItem.end || breakItem.endTime || "00:00";
46166
- const calculateDuration = (start, end) => {
46167
- const [startHour, startMinute] = start.split(":").map(Number);
46168
- const [endHour, endMinute] = end.split(":").map(Number);
46169
- let startMinutes = startHour * 60 + startMinute;
46170
- let endMinutes = endHour * 60 + endMinute;
46171
- if (endMinutes < startMinutes) {
46172
- endMinutes += 24 * 60;
46173
- }
46174
- return endMinutes - startMinutes;
46175
- };
46176
- return {
46177
- startTime,
46178
- endTime,
46179
- duration: breakItem.duration || calculateDuration(startTime, endTime)
46180
- };
46181
- });
46182
- } else if (typeof data.breaks === "string") {
46183
- try {
46184
- const parsedBreaks = JSON.parse(data.breaks);
46185
- if (Array.isArray(parsedBreaks)) {
46186
- breaks = parsedBreaks.map((breakItem) => ({
46187
- startTime: breakItem.start || breakItem.startTime || "00:00",
46188
- endTime: breakItem.end || breakItem.endTime || "00:00",
46189
- duration: breakItem.duration || 0
46190
- }));
46191
- } else if (parsedBreaks.breaks && Array.isArray(parsedBreaks.breaks)) {
46192
- breaks = parsedBreaks.breaks.map((breakItem) => ({
46193
- startTime: breakItem.start || breakItem.startTime || "00:00",
46194
- endTime: breakItem.end || breakItem.endTime || "00:00",
46195
- duration: breakItem.duration || 0
46196
- }));
46197
- }
46198
- } catch (e) {
46199
- console.error("Error parsing breaks data:", e);
46200
- }
46201
- }
46202
- }
46203
- return {
46204
- startTime: data?.start_time || "08:00",
46205
- endTime: data?.end_time || "19:00",
46206
- breaks
46207
- };
46208
- } catch (e) {
46209
- console.error("Exception when loading operating hours:", e);
46210
- return {
46211
- startTime: "08:00",
46212
- endTime: "19:00",
46213
- breaks: []
46214
- };
46215
- }
46216
- }, [supabase]);
46217
46222
  useEffect(() => {
46218
46223
  console.log("[TargetsView] Component mounted/re-rendered", {
46219
46224
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -46225,221 +46230,108 @@ var TargetsView = ({
46225
46230
  };
46226
46231
  }, []);
46227
46232
  useEffect(() => {
46228
- let timeoutId;
46229
- let retryCount = 0;
46230
- const MAX_RETRIES2 = 2;
46231
- const LOADING_TIMEOUT = 15e3;
46232
46233
  const fetchInitialData = async () => {
46233
- if (lineIds.length === 0) return;
46234
- console.log("[TargetsView] Starting fetchInitialData");
46234
+ if (lineIds.length === 0 || !companyId) return;
46235
+ console.log("[TargetsView] Starting optimized fetchInitialData with bulk endpoint");
46235
46236
  setIsLoading(true);
46236
- timeoutId = setTimeout(() => {
46237
- console.error("Loading timeout reached");
46238
- if (retryCount < MAX_RETRIES2) {
46239
- retryCount++;
46240
- console.log(`Retrying... (attempt ${retryCount + 1}/${MAX_RETRIES2 + 1})`);
46241
- toast.warning("Loading is taking longer than expected. Retrying...");
46242
- fetchInitialData();
46243
- } else {
46244
- setIsLoading(false);
46245
- toast.error("Failed to load data. Please refresh the page.");
46246
- }
46247
- }, LOADING_TIMEOUT);
46248
46237
  try {
46249
- const { data: { session } } = await supabase.auth.getSession();
46250
- if (!session?.access_token) {
46251
- throw new Error("No authentication token available");
46252
- }
46253
- const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
46254
- if (!backendUrl) {
46255
- throw new Error("Backend URL is not configured");
46238
+ const currentDate = getOperationalDate(timezone);
46239
+ const bulkResponse = await workspaceService.fetchBulkTargets({
46240
+ companyId,
46241
+ lineIds,
46242
+ date: currentDate,
46243
+ shifts: [0, 1],
46244
+ // Fetch both shifts at once
46245
+ includeSkus: skuEnabled,
46246
+ includeActions: true
46247
+ });
46248
+ if (!bulkResponse.success) {
46249
+ throw new Error("Failed to fetch bulk targets data");
46256
46250
  }
46257
- const [linesResponse, actions] = await Promise.all([
46258
- // Fetch lines data (includes factory_id)
46259
- Promise.all(lineIds.map(async (lineId) => {
46260
- try {
46261
- const response = await fetch(`${backendUrl}/api/lines/${lineId}`, {
46262
- headers: {
46263
- "Authorization": `Bearer ${session.access_token}`,
46264
- "Content-Type": "application/json"
46265
- }
46266
- });
46267
- if (!response.ok) {
46268
- console.error(`Error fetching line data for ${lineId}:`, response.statusText);
46269
- return { lineId, factoryId: void 0 };
46270
- }
46271
- const lineData = await response.json();
46272
- return { lineId, factoryId: lineData.factory_id };
46273
- } catch (err) {
46274
- console.error(`Exception fetching line data for ${lineId}:`, err);
46275
- return { lineId, factoryId: void 0 };
46276
- }
46277
- })),
46278
- // Fetch action IDs
46279
- actionService.getActionsByName(
46280
- [ACTION_NAMES.ASSEMBLY, ACTION_NAMES.PACKAGING],
46281
- companyId
46282
- )
46283
- ]);
46284
- const factoryResults = linesResponse;
46285
- const assemblyAction = actions.find((a) => a.action_name === ACTION_NAMES.ASSEMBLY);
46286
- const packagingAction = actions.find((a) => a.action_name === ACTION_NAMES.PACKAGING);
46251
+ const { data } = bulkResponse;
46252
+ const assemblyAction = Object.values(data.actions).find((a) => a.action_name === ACTION_NAMES.ASSEMBLY);
46253
+ const packagingAction = Object.values(data.actions).find((a) => a.action_name === ACTION_NAMES.PACKAGING);
46287
46254
  if (!assemblyAction || !packagingAction) {
46288
- throw new Error("Could not find required actions");
46255
+ throw new Error("Could not find required actions in bulk response");
46289
46256
  }
46290
46257
  const actionIdsData = {
46291
46258
  assembly: assemblyAction.id,
46292
46259
  packaging: packagingAction.id
46293
46260
  };
46294
46261
  setActionIds(actionIdsData);
46295
- const updatedLineWorkspaces = { ...initialLineWorkspaces };
46296
- factoryResults.forEach((result) => {
46297
- if (result.factoryId && updatedLineWorkspaces[result.lineId]) {
46298
- updatedLineWorkspaces[result.lineId].factoryId = result.factoryId;
46299
- }
46300
- });
46301
- const currentDate = getOperationalDate(timezone);
46302
- for (const lineId of lineIds) {
46303
- if (!updatedLineWorkspaces[lineId]?.factoryId) {
46304
- console.warn(`Skipping workspace fetch for line ${lineId} - no factory ID`);
46305
- continue;
46306
- }
46307
- try {
46308
- const workspacesData = await workspaceService.getWorkspaces(lineId);
46309
- const enabledWorkspaces = workspacesData.filter((ws) => ws.enable === true);
46310
- const actionThresholds = await workspaceService.getActionThresholds(
46311
- lineId,
46312
- currentDate,
46313
- 0
46314
- // Always use day shift for initial load
46315
- );
46316
- const operatingHoursData = await loadOperatingHours(lineId, 0);
46317
- if (operatingHoursData) {
46318
- updatedLineWorkspaces[lineId].shiftStartTime = operatingHoursData.startTime;
46319
- updatedLineWorkspaces[lineId].shiftEndTime = operatingHoursData.endTime;
46320
- updatedLineWorkspaces[lineId].breaks = operatingHoursData.breaks;
46321
- updatedLineWorkspaces[lineId].shiftHours = calculateShiftHours2(
46322
- operatingHoursData.startTime,
46323
- operatingHoursData.endTime,
46324
- operatingHoursData.breaks
46325
- );
46262
+ const newAllShiftsData = { 0: {}, 1: {} };
46263
+ const newDbValues = { 0: {}, 1: {} };
46264
+ Object.entries(data.lines).forEach(([lineId, lineData]) => {
46265
+ [0, 1].forEach((shiftId) => {
46266
+ const shiftData = lineData.shifts[shiftId.toString()];
46267
+ if (!shiftData) {
46268
+ console.warn(`No shift ${shiftId} data for line ${lineId}`);
46269
+ return;
46326
46270
  }
46271
+ const operatingHours = shiftData.operating_hours || {
46272
+ start_time: "08:00",
46273
+ end_time: "19:00",
46274
+ breaks: [],
46275
+ total_hours: 11
46276
+ };
46277
+ newAllShiftsData[shiftId][lineId] = {
46278
+ productId: "",
46279
+ shiftStartTime: operatingHours.start_time,
46280
+ shiftEndTime: operatingHours.end_time,
46281
+ breaks: operatingHours.breaks,
46282
+ shiftHours: operatingHours.total_hours,
46283
+ workspaces: [],
46284
+ factoryId: lineData.line_info.factory_id
46285
+ };
46286
+ newDbValues[shiftId][lineId] = {};
46287
+ const enabledWorkspaces = lineData.workspaces.filter((ws) => ws.enable === true);
46327
46288
  const mappedWorkspaces = enabledWorkspaces.map((ws) => {
46328
- const threshold = actionThresholds.find((t) => t.workspace_id === ws.id);
46329
- if (!dbValues[0][lineId]) {
46330
- dbValues[0][lineId] = {};
46331
- }
46289
+ const threshold = shiftData.thresholds.find((t) => t.workspace_id === ws.id);
46332
46290
  if (threshold) {
46333
- dbValues[0][lineId][ws.id] = {
46334
- targetPPH: threshold.pph_threshold,
46335
- targetCycleTime: threshold.ideal_cycle_time,
46336
- targetDayOutput: threshold.total_day_output
46291
+ newDbValues[shiftId][lineId][ws.id] = {
46292
+ targetPPH: threshold.pph_threshold ? Math.round(threshold.pph_threshold) : "",
46293
+ targetCycleTime: threshold.ideal_cycle_time ?? "",
46294
+ targetDayOutput: threshold.total_day_output ?? ""
46337
46295
  };
46338
46296
  }
46297
+ let actionType = "assembly";
46298
+ let actionId = actionIdsData.assembly;
46299
+ if (ws.action_id === packagingAction.id || ws.action_type === "packaging") {
46300
+ actionType = "packaging";
46301
+ actionId = packagingAction.id;
46302
+ } else if (ws.action_id === assemblyAction.id || ws.action_type === "assembly") {
46303
+ actionType = "assembly";
46304
+ actionId = assemblyAction.id;
46305
+ }
46339
46306
  return {
46340
46307
  id: ws.id,
46341
46308
  name: ws.workspace_id,
46342
- targetPPH: threshold?.pph_threshold ?? (ws.action_pph_threshold === null ? "" : ws.action_pph_threshold),
46343
- targetCycleTime: threshold?.ideal_cycle_time ?? (ws.action_cycle_time === null ? "" : ws.action_cycle_time),
46344
- targetDayOutput: threshold?.total_day_output ?? (ws.action_total_day_output === null ? "" : ws.action_total_day_output),
46345
- actionType: ws.action_id === actionIdsData.assembly ? "assembly" : ws.action_id === actionIdsData.packaging ? "packaging" : "assembly",
46346
- actionId: ws.action_id === actionIdsData.assembly ? actionIdsData.assembly : ws.action_id === actionIdsData.packaging ? actionIdsData.packaging : actionIdsData.assembly
46309
+ targetPPH: threshold?.pph_threshold ? Math.round(threshold.pph_threshold) : "",
46310
+ targetCycleTime: threshold?.ideal_cycle_time ?? "",
46311
+ targetDayOutput: threshold?.total_day_output ?? "",
46312
+ actionType,
46313
+ actionId
46347
46314
  };
46348
46315
  }).sort((a, b) => a.name.localeCompare(b.name, void 0, { numeric: true }));
46349
- updatedLineWorkspaces[lineId].workspaces = mappedWorkspaces;
46350
- } catch (error) {
46351
- console.error(`Error fetching workspace data for line ${lineId}:`, error);
46352
- }
46353
- }
46354
- setLineWorkspaces(updatedLineWorkspaces);
46355
- await fetchAllShiftsData(updatedLineWorkspaces);
46316
+ newAllShiftsData[shiftId][lineId].workspaces = mappedWorkspaces;
46317
+ });
46318
+ });
46319
+ setAllShiftsData(newAllShiftsData);
46320
+ setDbValues(newDbValues);
46321
+ console.log("[TargetsView] Successfully loaded all data with bulk endpoint:", {
46322
+ lineCount: bulkResponse.metadata.line_count,
46323
+ totalWorkspaces: bulkResponse.metadata.total_workspaces,
46324
+ loadTime: "Fast!"
46325
+ });
46356
46326
  } catch (error) {
46357
- console.error("Error fetching initial data:", error);
46358
- clearTimeout(timeoutId);
46359
- if (retryCount < MAX_RETRIES2) {
46360
- retryCount++;
46361
- console.log(`Error occurred, retrying... (attempt ${retryCount + 1}/${MAX_RETRIES2 + 1})`);
46362
- toast.warning("Error loading data. Retrying...");
46363
- setTimeout(() => fetchInitialData(), 1e3);
46364
- } else {
46365
- toast.error("Failed to load initial data");
46366
- setIsLoading(false);
46367
- }
46327
+ console.error("[TargetsView] Error fetching bulk data:", error);
46328
+ toast.error("Failed to load targets data. Please refresh the page.");
46368
46329
  } finally {
46369
- clearTimeout(timeoutId);
46370
- if (retryCount === 0 || retryCount >= MAX_RETRIES2) {
46371
- setIsLoading(false);
46372
- }
46330
+ setIsLoading(false);
46373
46331
  }
46374
46332
  };
46375
46333
  fetchInitialData();
46376
- return () => {
46377
- clearTimeout(timeoutId);
46378
- };
46379
- }, [lineIds, companyId, loadOperatingHours]);
46380
- const fetchAllShiftsData = useCallback(async (currentWorkspaces) => {
46381
- if (!supabase) return;
46382
- const currentDate = getOperationalDate(timezone);
46383
- const newAllShiftsData = {
46384
- 0: JSON.parse(JSON.stringify(currentWorkspaces)),
46385
- // Deep clone for day shift
46386
- 1: JSON.parse(JSON.stringify(currentWorkspaces))
46387
- // Deep clone for night shift
46388
- };
46389
- const newDbValues = { 0: {}, 1: {} };
46390
- for (const shiftId of [0, 1]) {
46391
- for (const lineId of lineIds) {
46392
- try {
46393
- const operatingHoursData = await loadOperatingHours(lineId, shiftId);
46394
- if (!operatingHoursData) {
46395
- console.warn(`No operating hours for line ${lineId}, shift ${shiftId} - using defaults`);
46396
- continue;
46397
- }
46398
- const { startTime, endTime, breaks } = operatingHoursData;
46399
- const shiftHours = calculateShiftHours2(startTime, endTime, breaks);
46400
- const actionThresholds = await workspaceService.getActionThresholds(
46401
- lineId,
46402
- currentDate,
46403
- shiftId
46404
- );
46405
- if (!newDbValues[shiftId][lineId]) {
46406
- newDbValues[shiftId][lineId] = {};
46407
- }
46408
- const existingLine = newAllShiftsData[shiftId][lineId];
46409
- if (existingLine) {
46410
- newAllShiftsData[shiftId][lineId] = {
46411
- ...existingLine,
46412
- shiftStartTime: startTime,
46413
- shiftEndTime: endTime,
46414
- breaks,
46415
- shiftHours: Number(shiftHours),
46416
- workspaces: existingLine.workspaces.map((ws) => {
46417
- const threshold = actionThresholds.find((t) => t.workspace_id === ws.id);
46418
- if (threshold) {
46419
- newDbValues[shiftId][lineId][ws.id] = {
46420
- targetPPH: threshold.pph_threshold,
46421
- targetCycleTime: threshold.ideal_cycle_time,
46422
- targetDayOutput: threshold.total_day_output
46423
- };
46424
- return {
46425
- ...ws,
46426
- targetPPH: threshold.pph_threshold,
46427
- targetCycleTime: threshold.ideal_cycle_time,
46428
- targetDayOutput: threshold.total_day_output
46429
- };
46430
- }
46431
- return ws;
46432
- })
46433
- };
46434
- }
46435
- } catch (error) {
46436
- console.error(`Error fetching data for line ${lineId}, shift ${shiftId}:`, error);
46437
- }
46438
- }
46439
- }
46440
- setAllShiftsData(newAllShiftsData);
46441
- setDbValues(newDbValues);
46442
- }, [supabase, lineIds, loadOperatingHours]);
46334
+ }, [lineIds, companyId, timezone, skuEnabled]);
46443
46335
  const toggleLineDropdown = useCallback((lineId) => {
46444
46336
  setDropdownStates((prev) => {
46445
46337
  const newIsOpen = !prev[lineId];
@@ -46512,7 +46404,7 @@ var TargetsView = ({
46512
46404
  if (value !== "") {
46513
46405
  const pph = calculatePPH(value, prev[lineId].breaks, shiftHours);
46514
46406
  updates.targetPPH = pph;
46515
- updates.targetDayOutput = calculateDayOutput(pph, shiftHours, prev[lineId].breaks);
46407
+ updates.targetDayOutput = calculateDayOutputFromCycleTime(value, shiftHours);
46516
46408
  } else {
46517
46409
  updates.targetPPH = "";
46518
46410
  updates.targetDayOutput = "";
@@ -46630,7 +46522,8 @@ var TargetsView = ({
46630
46522
  action_id: ws.actionId,
46631
46523
  workspace_id: ws.id,
46632
46524
  date: currentDate,
46633
- pph_threshold: Number(ws.targetPPH) || 0,
46525
+ pph_threshold: ws.targetPPH ? Math.round(Number(ws.targetPPH)) : 0,
46526
+ // Round to whole number
46634
46527
  ideal_cycle_time: Number(ws.targetCycleTime) || 0,
46635
46528
  total_day_output: Number(ws.targetDayOutput) || 0,
46636
46529
  action_name: ws.actionType === "assembly" ? ACTION_NAMES.ASSEMBLY : ACTION_NAMES.PACKAGING,
@@ -46649,7 +46542,8 @@ var TargetsView = ({
46649
46542
  shift_id: selectedShift,
46650
46543
  product_code: lineDataToSave.productId,
46651
46544
  threshold_day_output: packagingWorkspaces.reduce((acc, ws) => acc + (Number(ws.targetDayOutput) || 0), 0),
46652
- threshold_pph: packagingWorkspaces.reduce((acc, ws) => acc + (Number(ws.targetPPH) || 0), 0),
46545
+ threshold_pph: packagingWorkspaces.reduce((acc, ws) => acc + (ws.targetPPH ? Math.round(Number(ws.targetPPH)) : 0), 0),
46546
+ // Round each PPH value
46653
46547
  ...skuEnabled && lineDataToSave.selectedSKU ? { sku_id: lineDataToSave.selectedSKU.id } : {}
46654
46548
  };
46655
46549
  console.log(`[handleSaveLine] lineThresholdData for upsert on ${lineId}:`, lineThresholdData);
@@ -46908,6 +46802,7 @@ var WorkspaceDetailView = ({
46908
46802
  const [showIdleTime, setShowIdleTime] = useState(false);
46909
46803
  const dashboardConfig = useDashboardConfig();
46910
46804
  const isClipsEnabled = dashboardConfig?.clipsConfig?.enabled ?? true;
46805
+ dashboardConfig?.supervisorConfig?.enabled || false;
46911
46806
  const {
46912
46807
  workspace: workspaceHealth,
46913
46808
  loading: healthLoading,
@@ -46916,6 +46811,36 @@ var WorkspaceDetailView = ({
46916
46811
  enableRealtime: true,
46917
46812
  refreshInterval: 3e4
46918
46813
  });
46814
+ const {
46815
+ isHealthy: isWorkspaceHealthy,
46816
+ timeSinceUpdate,
46817
+ lastHeartbeat,
46818
+ loading: healthStatusLoading,
46819
+ error: healthStatusError
46820
+ } = useWorkspaceHealthStatus(workspaceId);
46821
+ const isLive = useMemo(() => {
46822
+ if (!lastHeartbeat) return false;
46823
+ const now2 = /* @__PURE__ */ new Date();
46824
+ const heartbeat = new Date(lastHeartbeat);
46825
+ const minutesSince = (now2.getTime() - heartbeat.getTime()) / 6e4;
46826
+ return minutesSince < 5;
46827
+ }, [lastHeartbeat]);
46828
+ useEffect(() => {
46829
+ console.log("[WorkspaceDetailView] Workspace Health Status:", {
46830
+ workspaceId,
46831
+ // Old workspace_health table
46832
+ oldStatus: workspaceHealth?.status,
46833
+ oldLastHeartbeat: workspaceHealth?.last_heartbeat,
46834
+ oldTimeSinceLastUpdate: workspaceHealth?.timeSinceLastUpdate,
46835
+ // New workspace_health_status table
46836
+ isHealthy: isWorkspaceHealthy,
46837
+ isLive,
46838
+ lastHeartbeat,
46839
+ timeSinceUpdate,
46840
+ loading: healthLoading || healthStatusLoading,
46841
+ error: healthError || healthStatusError
46842
+ });
46843
+ }, [workspaceId, workspaceHealth, isWorkspaceHealthy, isLive, lastHeartbeat, timeSinceUpdate, healthLoading, healthStatusLoading, healthError, healthStatusError]);
46919
46844
  const {
46920
46845
  status: prefetchStatus,
46921
46846
  data: prefetchData,
@@ -46952,6 +46877,7 @@ var WorkspaceDetailView = ({
46952
46877
  const workspace = isHistoricView ? historicMetrics : liveMetrics;
46953
46878
  const loading = isHistoricView ? historicLoading : liveLoading;
46954
46879
  const error = isHistoricView ? historicError : liveError;
46880
+ const { supervisorName } = useLineSupervisor(workspace?.line_id || lineId);
46955
46881
  useEffect(() => {
46956
46882
  if (onTabChange) {
46957
46883
  onTabChange(activeTab);
@@ -47174,7 +47100,7 @@ var WorkspaceDetailView = ({
47174
47100
  initial: { opacity: 1 },
47175
47101
  animate: { opacity: 1 },
47176
47102
  children: /* @__PURE__ */ jsxs("div", { className: "min-h-screen w-full flex flex-col bg-slate-50", children: [
47177
- /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 px-3 sm:px-4 md:px-5 lg:px-6 py-2 sm:py-2.5 lg:py-3 flex flex-col shadow-sm bg-white", children: [
47103
+ /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-10 px-3 sm:px-4 md:px-5 lg:px-6 py-3 sm:py-3 lg:py-3.5 flex flex-col shadow-sm bg-white", children: [
47178
47104
  /* @__PURE__ */ jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
47179
47105
  /* @__PURE__ */ jsx(
47180
47106
  "button",
@@ -47188,15 +47114,15 @@ var WorkspaceDetailView = ({
47188
47114
  /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center justify-center", children: [
47189
47115
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
47190
47116
  /* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-gray-900 truncate max-w-[220px]", children: formattedWorkspaceName }),
47191
- workspaceHealth && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsxs("div", { className: "relative flex h-2 w-2", children: [
47192
- workspaceHealth.status === "healthy" && /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
47117
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsxs("div", { className: "relative flex h-2 w-2", children: [
47118
+ isLive && /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
47193
47119
  /* @__PURE__ */ jsx("span", { className: clsx(
47194
47120
  "relative inline-flex rounded-full h-2 w-2",
47195
- workspaceHealth.status === "healthy" ? "bg-green-500" : "bg-red-500"
47121
+ isLive ? "bg-green-500" : "bg-red-500"
47196
47122
  ) })
47197
47123
  ] }) })
47198
47124
  ] }),
47199
- activeTab !== "monthly_history" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
47125
+ activeTab !== "monthly_history" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5 mt-1", children: [
47200
47126
  workspaceHealth && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-500", children: workspaceHealth.timeSinceLastUpdate }),
47201
47127
  /* @__PURE__ */ jsx(
47202
47128
  WorkspaceHealthStatusBadge,
@@ -47223,14 +47149,11 @@ var WorkspaceDetailView = ({
47223
47149
  ) }),
47224
47150
  /* @__PURE__ */ jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2 max-w-[calc(100%-200px)]", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
47225
47151
  /* @__PURE__ */ jsx("h1", { className: "text-lg md:text-xl lg:text-2xl xl:text-3xl font-semibold text-gray-900 truncate", children: formattedWorkspaceName }),
47226
- workspaceHealth && /* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
47227
- /* @__PURE__ */ jsx("span", { className: clsx(
47228
- "animate-ping absolute inline-flex h-full w-full rounded-full opacity-75",
47229
- workspaceHealth.status === "healthy" ? "bg-green-400" : "bg-red-400"
47230
- ) }),
47152
+ /* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
47153
+ isLive && /* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" }),
47231
47154
  /* @__PURE__ */ jsx("span", { className: clsx(
47232
47155
  "relative inline-flex rounded-full h-2.5 w-2.5",
47233
- workspaceHealth.status === "healthy" ? "bg-green-500" : "bg-red-500"
47156
+ isLive ? "bg-green-500" : "bg-red-500"
47234
47157
  ) })
47235
47158
  ] })
47236
47159
  ] }) }),
@@ -48372,94 +48295,14 @@ var SupervisorManagementView = ({
48372
48295
  ] });
48373
48296
  };
48374
48297
  var SupervisorManagementView_default = SupervisorManagementView;
48375
- var roleChangeImpacts = {
48376
- supervisor_to_plant_head: {
48377
- title: "Supervisor \u2192 Plant Head",
48378
- warnings: [
48379
- "Will lose line-specific assignments",
48380
- "Will need factory assignment",
48381
- "Will gain ability to manage supervisors",
48382
- "Will see factory-wide data"
48383
- ],
48384
- colorClass: "text-blue-700 bg-blue-50 border-blue-200"
48385
- },
48386
- supervisor_to_owner: {
48387
- title: "Supervisor \u2192 Owner",
48388
- warnings: [
48389
- "Will gain full company access",
48390
- "Will lose line-specific restrictions",
48391
- "Will be able to manage all users",
48392
- "Will see all company data"
48393
- ],
48394
- colorClass: "text-red-700 bg-red-50 border-red-200"
48395
- },
48396
- plant_head_to_supervisor: {
48397
- title: "Plant Head \u2192 Supervisor",
48398
- warnings: [
48399
- "Will lose factory access",
48400
- "Will lose ability to manage others",
48401
- "Will need line assignment",
48402
- "Will only see assigned line data"
48403
- ],
48404
- colorClass: "text-orange-700 bg-orange-50 border-orange-200"
48405
- },
48406
- plant_head_to_owner: {
48407
- title: "Plant Head \u2192 Owner",
48408
- warnings: [
48409
- "Will gain full company access",
48410
- "Will lose factory-specific restrictions",
48411
- "Will be able to manage all users",
48412
- "Will see all company data"
48413
- ],
48414
- colorClass: "text-blue-700 bg-blue-50 border-blue-200"
48415
- },
48416
- owner_to_plant_head: {
48417
- title: "Owner \u2192 Plant Head",
48418
- warnings: [
48419
- "Will lose company-wide access",
48420
- "Will need factory assignment",
48421
- "Will only manage supervisors",
48422
- "Will only see factory data"
48423
- ],
48424
- colorClass: "text-orange-700 bg-orange-50 border-orange-200"
48425
- },
48426
- owner_to_supervisor: {
48427
- title: "Owner \u2192 Supervisor",
48428
- warnings: [
48429
- "Will lose company-wide access",
48430
- "Will lose ability to manage others",
48431
- "Will need line assignment",
48432
- "Will only see assigned line data"
48433
- ],
48434
- colorClass: "text-red-700 bg-red-50 border-red-200"
48435
- },
48436
- to_optifye: {
48437
- title: "Promoting to Optifye",
48438
- warnings: [
48439
- "Will gain access to ALL companies",
48440
- "Will be able to manage all users globally",
48441
- "Will see all data across the platform",
48442
- "This is the highest privilege level"
48443
- ],
48444
- colorClass: "text-purple-700 bg-purple-50 border-purple-200"
48445
- },
48446
- from_optifye: {
48447
- title: "Demoting from Optifye",
48448
- warnings: [
48449
- "Will lose access to other companies",
48450
- "Will only see assigned company data",
48451
- "Will lose global management capabilities",
48452
- "Will need appropriate assignments"
48453
- ],
48454
- colorClass: "text-red-700 bg-red-50 border-red-200"
48455
- }
48456
- };
48457
- var getRoleChangeKey = (currentRole, newRole) => {
48458
- if (currentRole === newRole) return null;
48459
- if (newRole === "optifye") return "to_optifye";
48460
- if (currentRole === "optifye") return "from_optifye";
48461
- const key = `${currentRole}_to_${newRole}`;
48462
- return roleChangeImpacts[key] ? key : null;
48298
+ var getRoleLabel = (role) => {
48299
+ const labels = {
48300
+ "supervisor": "Supervisor",
48301
+ "plant_head": "Plant Head",
48302
+ "owner": "Owner",
48303
+ "optifye": "Optifye Admin"
48304
+ };
48305
+ return labels[role] || role;
48463
48306
  };
48464
48307
  var ChangeRoleDialog = ({
48465
48308
  user,
@@ -48472,8 +48315,6 @@ var ChangeRoleDialog = ({
48472
48315
  const [confirmed, setConfirmed] = useState(false);
48473
48316
  const [isChanging, setIsChanging] = useState(false);
48474
48317
  const roleChanged = selectedRole !== user.role_level;
48475
- const changeKey = roleChanged ? getRoleChangeKey(user.role_level, selectedRole) : null;
48476
- const impactInfo = changeKey ? roleChangeImpacts[changeKey] : null;
48477
48318
  const handleNext = () => {
48478
48319
  if (roleChanged) {
48479
48320
  setStep("confirm");
@@ -48493,7 +48334,7 @@ var ChangeRoleDialog = ({
48493
48334
  setStep("select");
48494
48335
  setConfirmed(false);
48495
48336
  };
48496
- return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg shadow-xl max-w-md w-full mx-4", children: [
48337
+ return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm", children: /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-xl shadow-2xl max-w-md w-full mx-4 transform transition-all", children: [
48497
48338
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
48498
48339
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
48499
48340
  /* @__PURE__ */ jsx("div", { className: "p-2 bg-blue-100 rounded-lg", children: /* @__PURE__ */ jsx(UserCog, { className: "w-5 h-5 text-blue-600" }) }),
@@ -48503,20 +48344,14 @@ var ChangeRoleDialog = ({
48503
48344
  "button",
48504
48345
  {
48505
48346
  onClick: onClose,
48506
- className: "text-gray-400 hover:text-gray-600 transition-colors",
48347
+ className: "text-gray-400 hover:text-gray-600 transition-colors p-1 hover:bg-gray-100 rounded-lg",
48507
48348
  disabled: isChanging,
48349
+ "aria-label": "Close dialog",
48508
48350
  children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" })
48509
48351
  }
48510
48352
  )
48511
48353
  ] }),
48512
- /* @__PURE__ */ jsx("div", { className: "p-6 space-y-6", children: step === "select" ? /* @__PURE__ */ jsxs(Fragment, { children: [
48513
- roleChanged && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-4 bg-amber-50 border border-amber-200 rounded-lg", children: [
48514
- /* @__PURE__ */ jsx(AlertCircle, { className: "w-5 h-5 text-amber-600 mt-0.5 flex-shrink-0" }),
48515
- /* @__PURE__ */ jsxs("div", { children: [
48516
- /* @__PURE__ */ jsx("p", { className: "font-medium text-amber-900 text-sm", children: "Changing roles affects access" }),
48517
- /* @__PURE__ */ jsx("p", { className: "text-amber-700 text-sm mt-1", children: "Review the impact carefully before confirming." })
48518
- ] })
48519
- ] }),
48354
+ /* @__PURE__ */ jsx("div", { className: "p-6 space-y-5", children: step === "select" ? /* @__PURE__ */ jsxs(Fragment, { children: [
48520
48355
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48521
48356
  /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "User" }),
48522
48357
  /* @__PURE__ */ jsxs("div", { className: "p-3 bg-gray-50 rounded-lg", children: [
@@ -48527,7 +48362,7 @@ var ChangeRoleDialog = ({
48527
48362
  ] })
48528
48363
  ] })
48529
48364
  ] }),
48530
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
48365
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48531
48366
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "Select new role" }),
48532
48367
  /* @__PURE__ */ jsx("div", { className: "space-y-2", children: availableRoles.map((role) => /* @__PURE__ */ jsxs(
48533
48368
  "label",
@@ -48551,27 +48386,24 @@ var ChangeRoleDialog = ({
48551
48386
  role
48552
48387
  )) })
48553
48388
  ] }),
48554
- impactInfo && /* @__PURE__ */ jsxs("div", { className: `p-4 border rounded-lg ${impactInfo.colorClass}`, children: [
48555
- /* @__PURE__ */ jsx("p", { className: "font-medium text-sm mb-2", children: "\u2139\uFE0F Impact of this change:" }),
48556
- /* @__PURE__ */ jsx("ul", { className: "space-y-1.5 text-sm", children: impactInfo.warnings.map((warning6, idx) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
48557
- /* @__PURE__ */ jsx("span", { className: "mt-0.5", children: "\u2022" }),
48558
- /* @__PURE__ */ jsx("span", { children: warning6 })
48559
- ] }, idx)) })
48560
- ] })
48389
+ roleChanged && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-600", children: [
48390
+ "Changing from ",
48391
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: getRoleLabel(user.role_level) }),
48392
+ " to ",
48393
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: getRoleLabel(selectedRole) }),
48394
+ " will modify user permissions."
48395
+ ] }) })
48561
48396
  ] }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
48562
48397
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-3 p-4 bg-gray-50 rounded-lg", children: [
48563
48398
  /* @__PURE__ */ jsx(RoleBadge, { role: user.role_level }),
48564
48399
  /* @__PURE__ */ jsx(ChevronRight, { className: "w-5 h-5 text-gray-400" }),
48565
48400
  /* @__PURE__ */ jsx(RoleBadge, { role: selectedRole })
48566
48401
  ] }),
48567
- impactInfo && /* @__PURE__ */ jsxs("div", { className: `p-4 border rounded-lg ${impactInfo.colorClass}`, children: [
48568
- /* @__PURE__ */ jsx("p", { className: "font-medium text-sm mb-3", children: impactInfo.title }),
48569
- /* @__PURE__ */ jsx("ul", { className: "space-y-2 text-sm", children: impactInfo.warnings.map((warning6, idx) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
48570
- /* @__PURE__ */ jsx("span", { className: "mt-0.5", children: "\u2022" }),
48571
- /* @__PURE__ */ jsx("span", { children: warning6 })
48572
- ] }, idx)) })
48402
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48403
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "User" }),
48404
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900", children: user.email })
48573
48405
  ] }),
48574
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-4 border border-gray-200 rounded-lg", children: [
48406
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-3 border border-gray-200 rounded-lg bg-gray-50", children: [
48575
48407
  /* @__PURE__ */ jsx(
48576
48408
  "input",
48577
48409
  {
@@ -48582,7 +48414,7 @@ var ChangeRoleDialog = ({
48582
48414
  className: "mt-0.5 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
48583
48415
  }
48584
48416
  ),
48585
- /* @__PURE__ */ jsx("label", { htmlFor: "confirm-role-change", className: "text-sm text-gray-700 cursor-pointer", children: "I understand this will change the user's access and responsibilities" })
48417
+ /* @__PURE__ */ jsx("label", { htmlFor: "confirm-role-change", className: "text-sm text-gray-700 cursor-pointer", children: "I confirm this role change and understand the permissions will be updated" })
48586
48418
  ] })
48587
48419
  ] }) }) }),
48588
48420
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-3 p-6 border-t border-gray-200 bg-gray-50", children: step === "select" ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -48625,7 +48457,7 @@ var ChangeRoleDialog = ({
48625
48457
  /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })
48626
48458
  ] }),
48627
48459
  "Changing..."
48628
- ] }) : "Change Role"
48460
+ ] }) : "Confirm Change"
48629
48461
  }
48630
48462
  )
48631
48463
  ] }) })
@@ -48648,32 +48480,34 @@ var ConfirmRemoveUserDialog = ({
48648
48480
  setIsRemoving(false);
48649
48481
  }
48650
48482
  };
48651
- return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-lg shadow-xl max-w-md w-full mx-4", children: [
48483
+ return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm", children: /* @__PURE__ */ jsxs("div", { className: "bg-white rounded-xl shadow-2xl max-w-md w-full mx-4 transform transition-all", children: [
48652
48484
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
48653
48485
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
48654
48486
  /* @__PURE__ */ jsx("div", { className: "p-2 bg-red-100 rounded-lg", children: /* @__PURE__ */ jsx(Trash2, { className: "w-5 h-5 text-red-600" }) }),
48655
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Remove User" })
48487
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Permanently Delete User" })
48656
48488
  ] }),
48657
48489
  /* @__PURE__ */ jsx(
48658
48490
  "button",
48659
48491
  {
48660
48492
  onClick: onCancel,
48661
- className: "text-gray-400 hover:text-gray-600 transition-colors",
48493
+ className: "text-gray-400 hover:text-gray-600 transition-colors p-1 hover:bg-gray-100 rounded-lg",
48494
+ disabled: isRemoving,
48495
+ "aria-label": "Close dialog",
48662
48496
  children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" })
48663
48497
  }
48664
48498
  )
48665
48499
  ] }),
48666
- /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-6", children: [
48667
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-4 bg-red-50 border border-red-200 rounded-lg", children: [
48668
- /* @__PURE__ */ jsx(AlertTriangle, { className: "w-5 h-5 text-red-600 mt-0.5 flex-shrink-0" }),
48669
- /* @__PURE__ */ jsxs("div", { children: [
48670
- /* @__PURE__ */ jsx("p", { className: "font-medium text-red-900 text-sm", children: "This action cannot be undone" }),
48671
- /* @__PURE__ */ jsx("p", { className: "text-red-700 text-sm mt-1", children: "The user will immediately lose all access to the platform." })
48500
+ /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-5", children: [
48501
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-3 bg-red-50 border border-red-200 rounded-lg", children: [
48502
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "w-4 h-4 text-red-600 mt-0.5 flex-shrink-0" }),
48503
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-red-900", children: [
48504
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "This action cannot be undone." }),
48505
+ " This will permanently delete the user and all related data from the system."
48672
48506
  ] })
48673
48507
  ] }),
48674
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
48675
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "You are about to remove:" }),
48676
- /* @__PURE__ */ jsxs("div", { className: "p-4 bg-gray-50 rounded-lg space-y-2", children: [
48508
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48509
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "You are about to permanently delete:" }),
48510
+ /* @__PURE__ */ jsxs("div", { className: "p-3 bg-gray-50 rounded-lg space-y-2.5", children: [
48677
48511
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
48678
48512
  /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-600", children: "Email" }),
48679
48513
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-900", children: user.email })
@@ -48698,28 +48532,7 @@ var ConfirmRemoveUserDialog = ({
48698
48532
  ] })
48699
48533
  ] })
48700
48534
  ] }),
48701
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48702
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "This will:" }),
48703
- /* @__PURE__ */ jsxs("ul", { className: "space-y-2 text-sm text-gray-600", children: [
48704
- /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
48705
- /* @__PURE__ */ jsx("span", { className: "text-red-500 mt-0.5", children: "\u2022" }),
48706
- /* @__PURE__ */ jsx("span", { children: "Revoke all access immediately" })
48707
- ] }),
48708
- /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
48709
- /* @__PURE__ */ jsx("span", { className: "text-red-500 mt-0.5", children: "\u2022" }),
48710
- /* @__PURE__ */ jsx("span", { children: "Remove all line and factory assignments" })
48711
- ] }),
48712
- /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
48713
- /* @__PURE__ */ jsx("span", { className: "text-red-500 mt-0.5", children: "\u2022" }),
48714
- /* @__PURE__ */ jsx("span", { children: "Log the user out of all sessions" })
48715
- ] }),
48716
- /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
48717
- /* @__PURE__ */ jsx("span", { className: "text-red-500 mt-0.5", children: "\u2022" }),
48718
- /* @__PURE__ */ jsx("span", { children: "User will not be able to access the dashboard" })
48719
- ] })
48720
- ] })
48721
- ] }),
48722
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-4 border border-gray-200 rounded-lg", children: [
48535
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-3 border border-gray-200 rounded-lg bg-gray-50", children: [
48723
48536
  /* @__PURE__ */ jsx(
48724
48537
  "input",
48725
48538
  {
@@ -48730,7 +48543,7 @@ var ConfirmRemoveUserDialog = ({
48730
48543
  className: "mt-0.5 h-4 w-4 text-red-600 border-gray-300 rounded focus:ring-red-500"
48731
48544
  }
48732
48545
  ),
48733
- /* @__PURE__ */ jsx("label", { htmlFor: "confirm-remove", className: "text-sm text-gray-700 cursor-pointer", children: "I understand this action is permanent and cannot be undone" })
48546
+ /* @__PURE__ */ jsx("label", { htmlFor: "confirm-remove", className: "text-sm text-gray-700 cursor-pointer", children: "I understand this will permanently delete the user and all related data, and this action cannot be undone" })
48734
48547
  ] })
48735
48548
  ] }),
48736
48549
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3 p-6 border-t border-gray-200 bg-gray-50", children: [
@@ -48754,8 +48567,8 @@ var ConfirmRemoveUserDialog = ({
48754
48567
  /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4", fill: "none" }),
48755
48568
  /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })
48756
48569
  ] }),
48757
- "Removing..."
48758
- ] }) : "Remove User"
48570
+ "Deleting..."
48571
+ ] }) : "Delete User"
48759
48572
  }
48760
48573
  )
48761
48574
  ] })
@@ -49178,7 +48991,6 @@ var FactoryAssignmentDropdown = ({
49178
48991
  var UserManagementTable = ({
49179
48992
  users,
49180
48993
  isLoading = false,
49181
- onUserClick,
49182
48994
  onRoleChange,
49183
48995
  onRemoveUser,
49184
48996
  onLineAssignmentUpdate,
@@ -49204,9 +49016,48 @@ var UserManagementTable = ({
49204
49016
  const [sortField, setSortField] = useState("email");
49205
49017
  const [sortDirection, setSortDirection] = useState("asc");
49206
49018
  const [openActionMenuId, setOpenActionMenuId] = useState(null);
49019
+ const [dropdownPosition, setDropdownPosition] = useState(null);
49020
+ const actionButtonRefs = useRef({});
49207
49021
  const [selectedUser, setSelectedUser] = useState(null);
49208
49022
  const [showChangeRoleDialog, setShowChangeRoleDialog] = useState(false);
49209
49023
  const [showRemoveUserDialog, setShowRemoveUserDialog] = useState(false);
49024
+ const handleOpenActionMenu = (userId) => {
49025
+ const buttonRef = actionButtonRefs.current[userId];
49026
+ if (buttonRef) {
49027
+ const rect = buttonRef.getBoundingClientRect();
49028
+ setDropdownPosition({
49029
+ top: rect.bottom + 8,
49030
+ // 8px below the button
49031
+ left: rect.right - 192
49032
+ // 192px is the dropdown width (w-48 = 12rem = 192px)
49033
+ });
49034
+ setOpenActionMenuId(userId);
49035
+ }
49036
+ };
49037
+ const handleCloseActionMenu = () => {
49038
+ setOpenActionMenuId(null);
49039
+ setDropdownPosition(null);
49040
+ };
49041
+ useEffect(() => {
49042
+ if (openActionMenuId && actionButtonRefs.current[openActionMenuId]) {
49043
+ const updatePosition = () => {
49044
+ const buttonRef = actionButtonRefs.current[openActionMenuId];
49045
+ if (buttonRef) {
49046
+ const rect = buttonRef.getBoundingClientRect();
49047
+ setDropdownPosition({
49048
+ top: rect.bottom + 8,
49049
+ left: rect.right - 192
49050
+ });
49051
+ }
49052
+ };
49053
+ window.addEventListener("scroll", updatePosition, true);
49054
+ window.addEventListener("resize", updatePosition);
49055
+ return () => {
49056
+ window.removeEventListener("scroll", updatePosition, true);
49057
+ window.removeEventListener("resize", updatePosition);
49058
+ };
49059
+ }
49060
+ }, [openActionMenuId]);
49210
49061
  const filteredAndSortedUsers = useMemo(() => {
49211
49062
  let filtered = users;
49212
49063
  filtered = filtered.filter((user) => availableRoles.includes(user.role_level));
@@ -49397,7 +49248,7 @@ var UserManagementTable = ({
49397
49248
  LineAssignmentDropdown,
49398
49249
  {
49399
49250
  userId: user.user_id,
49400
- currentLineIds: user.properties?.line_id || [],
49251
+ currentLineIds: user.properties?.line_ids || [],
49401
49252
  availableLines: (
49402
49253
  // Filter lines to only show those from the target user's company
49403
49254
  (() => {
@@ -49435,69 +49286,24 @@ var UserManagementTable = ({
49435
49286
  }
49436
49287
  ) : /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) }) }),
49437
49288
  /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: formatDate(user.created_at) }) }),
49438
- /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right", children: hasActions && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
49439
- /* @__PURE__ */ jsx(
49440
- "button",
49441
- {
49442
- onClick: () => setOpenActionMenuId(openActionMenuId === user.user_id ? null : user.user_id),
49443
- className: "p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49444
- "aria-label": "User actions",
49445
- children: /* @__PURE__ */ jsx(MoreVertical, { className: "w-4 h-4" })
49446
- }
49447
- ),
49448
- openActionMenuId === user.user_id && /* @__PURE__ */ jsxs(Fragment, { children: [
49449
- /* @__PURE__ */ jsx(
49450
- "div",
49451
- {
49452
- className: "fixed inset-0 z-[9998]",
49453
- onClick: () => setOpenActionMenuId(null)
49289
+ /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right", children: hasActions && /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsx(
49290
+ "button",
49291
+ {
49292
+ ref: (el) => {
49293
+ actionButtonRefs.current[user.user_id] = el;
49294
+ },
49295
+ onClick: () => {
49296
+ if (openActionMenuId === user.user_id) {
49297
+ handleCloseActionMenu();
49298
+ } else {
49299
+ handleOpenActionMenu(user.user_id);
49454
49300
  }
49455
- ),
49456
- /* @__PURE__ */ jsx("div", { className: "absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-gray-200 z-[9999]", children: /* @__PURE__ */ jsxs("div", { className: "py-1", children: [
49457
- /* @__PURE__ */ jsxs(
49458
- "button",
49459
- {
49460
- onClick: () => {
49461
- onUserClick?.(user);
49462
- setOpenActionMenuId(null);
49463
- },
49464
- className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
49465
- children: [
49466
- /* @__PURE__ */ jsx(Eye, { className: "w-4 h-4" }),
49467
- "View Details"
49468
- ]
49469
- }
49470
- ),
49471
- canChangeRole && /* @__PURE__ */ jsxs(
49472
- "button",
49473
- {
49474
- onClick: () => handleChangeRole(user),
49475
- className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
49476
- disabled: isCurrentUser,
49477
- children: [
49478
- /* @__PURE__ */ jsx(UserCog, { className: "w-4 h-4" }),
49479
- "Change Role"
49480
- ]
49481
- }
49482
- ),
49483
- canRemove && /* @__PURE__ */ jsxs(Fragment, { children: [
49484
- /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200 my-1" }),
49485
- /* @__PURE__ */ jsxs(
49486
- "button",
49487
- {
49488
- onClick: () => handleRemoveUser(user),
49489
- className: "w-full px-4 py-2 text-sm text-left text-red-600 hover:bg-red-50 flex items-center gap-2",
49490
- disabled: isCurrentUser,
49491
- children: [
49492
- /* @__PURE__ */ jsx(Trash2, { className: "w-4 h-4" }),
49493
- "Remove User"
49494
- ]
49495
- }
49496
- )
49497
- ] })
49498
- ] }) })
49499
- ] })
49500
- ] }) })
49301
+ },
49302
+ className: "p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49303
+ "aria-label": "User actions",
49304
+ children: /* @__PURE__ */ jsx(MoreVertical, { className: "w-4 h-4" })
49305
+ }
49306
+ ) }) })
49501
49307
  ]
49502
49308
  },
49503
49309
  user.user_id
@@ -49505,6 +49311,75 @@ var UserManagementTable = ({
49505
49311
  }) })
49506
49312
  ] }) }) })
49507
49313
  ] }),
49314
+ openActionMenuId && dropdownPosition && typeof document !== "undefined" && createPortal(
49315
+ /* @__PURE__ */ jsxs(Fragment, { children: [
49316
+ /* @__PURE__ */ jsx(
49317
+ "div",
49318
+ {
49319
+ className: "fixed inset-0 z-[9998]",
49320
+ onClick: handleCloseActionMenu
49321
+ }
49322
+ ),
49323
+ /* @__PURE__ */ jsx(
49324
+ "div",
49325
+ {
49326
+ className: "fixed w-48 bg-white rounded-lg shadow-lg border border-gray-200 z-[9999]",
49327
+ style: {
49328
+ top: `${dropdownPosition.top}px`,
49329
+ left: `${dropdownPosition.left}px`
49330
+ },
49331
+ children: /* @__PURE__ */ jsxs("div", { className: "py-1", children: [
49332
+ (() => {
49333
+ const user = users.find((u) => u.user_id === openActionMenuId);
49334
+ const isCurrentUser = user?.user_id === currentUserId;
49335
+ const canChangeRole = user && permissions.canChangeRole(user);
49336
+ return canChangeRole && /* @__PURE__ */ jsxs(
49337
+ "button",
49338
+ {
49339
+ onClick: () => {
49340
+ if (user) {
49341
+ handleChangeRole(user);
49342
+ }
49343
+ },
49344
+ 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",
49345
+ disabled: isCurrentUser,
49346
+ children: [
49347
+ /* @__PURE__ */ jsx(UserCog, { className: "w-4 h-4" }),
49348
+ "Change Role"
49349
+ ]
49350
+ }
49351
+ );
49352
+ })(),
49353
+ (() => {
49354
+ const user = users.find((u) => u.user_id === openActionMenuId);
49355
+ const isCurrentUser = user?.user_id === currentUserId;
49356
+ const canRemove = user && permissions.canRemoveUser(user);
49357
+ return canRemove && /* @__PURE__ */ jsxs(Fragment, { children: [
49358
+ /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200 my-1" }),
49359
+ /* @__PURE__ */ jsxs(
49360
+ "button",
49361
+ {
49362
+ onClick: () => {
49363
+ if (user) {
49364
+ handleRemoveUser(user);
49365
+ }
49366
+ },
49367
+ className: "w-full px-4 py-2 text-sm text-left text-red-600 hover:bg-red-50 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
49368
+ disabled: isCurrentUser,
49369
+ children: [
49370
+ /* @__PURE__ */ jsx(Trash2, { className: "w-4 h-4" }),
49371
+ "Delete User"
49372
+ ]
49373
+ }
49374
+ )
49375
+ ] });
49376
+ })()
49377
+ ] })
49378
+ }
49379
+ )
49380
+ ] }),
49381
+ document.body
49382
+ ),
49508
49383
  showChangeRoleDialog && selectedUser && /* @__PURE__ */ jsx(
49509
49384
  ChangeRoleDialog,
49510
49385
  {
@@ -49618,6 +49493,34 @@ var InviteUserDialog = ({
49618
49493
  throw new Error(data.error);
49619
49494
  }
49620
49495
  toast.success(data?.message || "User added successfully!");
49496
+ try {
49497
+ const dashboardUrl = typeof window !== "undefined" ? window.location.origin : "";
49498
+ if (dashboardUrl) {
49499
+ console.log("Sending welcome email to:", email.trim());
49500
+ const { data: emailData, error: emailError } = await supabase.functions.invoke("hyper-service", {
49501
+ body: {
49502
+ action: "send-welcome-email",
49503
+ email: email.trim(),
49504
+ company_id: companyId,
49505
+ dashboard_url: dashboardUrl
49506
+ }
49507
+ });
49508
+ if (emailError) {
49509
+ console.error("Failed to send welcome email:", emailError);
49510
+ toast.warning("User added successfully, but welcome email could not be sent");
49511
+ } else if (emailData?.success) {
49512
+ console.log("Welcome email sent successfully:", emailData);
49513
+ toast.success("User added and welcome email sent!");
49514
+ } else {
49515
+ console.log("Welcome email response:", emailData);
49516
+ }
49517
+ } else {
49518
+ console.warn("Dashboard URL not available, skipping welcome email");
49519
+ }
49520
+ } catch (emailErr) {
49521
+ console.error("Error sending welcome email:", emailErr);
49522
+ toast.info("User added successfully (email service unavailable)");
49523
+ }
49621
49524
  onInviteSent?.();
49622
49525
  onClose();
49623
49526
  } catch (err) {
@@ -50128,15 +50031,12 @@ var TeamManagementView = ({
50128
50031
  if (!supabase || !user) return;
50129
50032
  try {
50130
50033
  const userManagementService = createUserManagementService(supabase);
50131
- await userManagementService.deactivateUser({
50132
- user_id: userId,
50133
- deactivated_by: user.id
50134
- });
50135
- toast.success("User removed successfully");
50034
+ const result = await userManagementService.deleteUser(userId);
50035
+ toast.success(`User ${result.deleted_user_email} permanently deleted`);
50136
50036
  loadData();
50137
50037
  } catch (err) {
50138
- console.error("Error removing user:", err);
50139
- toast.error("Failed to remove user");
50038
+ console.error("Error deleting user:", err);
50039
+ toast.error("Failed to delete user");
50140
50040
  }
50141
50041
  }, [supabase, user, loadData]);
50142
50042
  const handleLineAssignmentUpdate = useCallback(async (userId, lineIds) => {
@@ -50230,9 +50130,6 @@ var TeamManagementView = ({
50230
50130
  {
50231
50131
  users,
50232
50132
  currentUserId: user?.id,
50233
- onUserClick: (clickedUser) => {
50234
- console.log("View user details:", clickedUser);
50235
- },
50236
50133
  onRoleChange: handleRoleChange,
50237
50134
  onRemoveUser: handleRemoveUser,
50238
50135
  onLineAssignmentUpdate: handleLineAssignmentUpdate,
@@ -51410,7 +51307,7 @@ var S3Service = class {
51410
51307
  };
51411
51308
 
51412
51309
  // src/lib/api/optifye-agent.ts
51413
- var OPTIFYE_API_URL = "https://optifye-agent-production.up.railway.app";
51310
+ var OPTIFYE_API_URL = process.env.NEXT_PUBLIC_AGNO_URL || "https://fastapi-production-111f9.up.railway.app";
51414
51311
  var OptifyeAgentClient = class {
51415
51312
  constructor(apiUrl = OPTIFYE_API_URL) {
51416
51313
  this.apiUrl = apiUrl;