@optifye/dashboard-core 6.9.2 → 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
@@ -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 {
@@ -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();
@@ -41419,7 +41446,6 @@ function HomeView({
41419
41446
  factoryName = "Simba Beer - Line 1"
41420
41447
  }) {
41421
41448
  const [isHydrated, setIsHydrated] = useState(false);
41422
- const availableLineIds = useMemo(() => [factoryViewId, ...allLineIds], [factoryViewId, allLineIds]);
41423
41449
  const [selectedLineId, setSelectedLineId] = useState(factoryViewId);
41424
41450
  const [isChangingFilter, setIsChangingFilter] = useState(false);
41425
41451
  const [errorMessage, setErrorMessage] = useState(null);
@@ -41429,6 +41455,21 @@ function HomeView({
41429
41455
  const entityConfig = useEntityConfig();
41430
41456
  const supabaseClient = useSupabaseClient();
41431
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]);
41432
41473
  const userCompanyId = useMemo(() => {
41433
41474
  return user?.properties?.company_id || user?.company_id || entityConfig.companyId;
41434
41475
  }, [user, entityConfig.companyId]);
@@ -41489,7 +41530,9 @@ function HomeView({
41489
41530
  refetch: refetchMetrics
41490
41531
  } = useDashboardMetrics({
41491
41532
  lineId: selectedLineId,
41492
- onLineMetricsUpdate
41533
+ onLineMetricsUpdate,
41534
+ userAccessibleLineIds: allLineIds
41535
+ // Pass user's accessible lines for supervisor filtering
41493
41536
  });
41494
41537
  const {
41495
41538
  activeBreaks: allActiveBreaks,
@@ -43548,7 +43591,8 @@ var LeaderboardDetailView = memo(({
43548
43591
  line1Id = "",
43549
43592
  line2Id = "",
43550
43593
  lineNames = {},
43551
- className = ""
43594
+ className = "",
43595
+ userAccessibleLineIds
43552
43596
  }) => {
43553
43597
  const navigation = useNavigation();
43554
43598
  const entityConfig = useEntityConfig();
@@ -43588,7 +43632,9 @@ var LeaderboardDetailView = memo(({
43588
43632
  refreshWorkspaces
43589
43633
  } = useAllWorkspaceMetrics({
43590
43634
  initialDate: date,
43591
- 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
43592
43638
  });
43593
43639
  const getShiftName = useCallback((shiftId2) => {
43594
43640
  if (shiftId2 === void 0) return "Day";
@@ -44944,12 +44990,17 @@ var ACTION_NAMES = {
44944
44990
  var calculatePPH = (cycleTime, breaks = [], shiftHours = 0) => {
44945
44991
  if (cycleTime === "" || cycleTime === 0) return "";
44946
44992
  const pph = 3600 / cycleTime;
44947
- return Number(pph.toFixed(1));
44993
+ return Math.round(pph);
44948
44994
  };
44949
44995
  var calculateDayOutput = (pph, shiftHours, breaks = []) => {
44950
44996
  if (pph === "") return "";
44951
44997
  return Math.round(pph * shiftHours);
44952
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
+ };
44953
45004
  var formatWorkspaceName = (name, lineId) => {
44954
45005
  return getWorkspaceDisplayName(name, lineId);
44955
45006
  };
@@ -44961,20 +45012,6 @@ var getStoredLineState2 = (lineId) => {
44961
45012
  return false;
44962
45013
  }
44963
45014
  };
44964
- var calculateShiftHours2 = (startTime, endTime, breaks = []) => {
44965
- if (!startTime || !endTime) return 8;
44966
- const [startHour, startMinute] = startTime.split(":").map(Number);
44967
- const [endHour, endMinute] = endTime.split(":").map(Number);
44968
- let startMinutes = startHour * 60 + startMinute;
44969
- let endMinutes = endHour * 60 + endMinute;
44970
- if (endMinutes < startMinutes) {
44971
- endMinutes += 24 * 60;
44972
- }
44973
- const safeBreaks = Array.isArray(breaks) ? breaks : [];
44974
- const totalBreakMinutes = safeBreaks.reduce((total, breakItem) => total + breakItem.duration, 0);
44975
- const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
44976
- return Number(hoursDiff.toFixed(1));
44977
- };
44978
45015
  var BulkConfigureModal = ({
44979
45016
  isOpen,
44980
45017
  onClose,
@@ -46182,121 +46219,6 @@ var TargetsView = ({
46182
46219
  const dashboardConfig = useDashboardConfig();
46183
46220
  const { skus, isLoading: skusLoading } = useSKUs(companyId);
46184
46221
  const skuEnabled = dashboardConfig?.skuConfig?.enabled || false;
46185
- const loadOperatingHours = useCallback(async (lineId, shiftId) => {
46186
- try {
46187
- const { data: { session } } = await supabase.auth.getSession();
46188
- if (!session?.access_token) {
46189
- console.error("No authentication token available");
46190
- return {
46191
- startTime: "08:00",
46192
- // Default values
46193
- endTime: "19:00",
46194
- breaks: []
46195
- };
46196
- }
46197
- const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
46198
- if (!backendUrl) {
46199
- console.error("Backend URL is not configured");
46200
- return {
46201
- startTime: "08:00",
46202
- // Default values
46203
- endTime: "19:00",
46204
- breaks: []
46205
- };
46206
- }
46207
- const response = await fetch(`${backendUrl}/api/lines/${lineId}/operating-hours?shift_id=${shiftId}`, {
46208
- headers: {
46209
- "Authorization": `Bearer ${session.access_token}`,
46210
- "Content-Type": "application/json"
46211
- }
46212
- });
46213
- if (!response.ok) {
46214
- console.log(`No operating hours found for line ${lineId}, shift ${shiftId} - using defaults`);
46215
- return {
46216
- startTime: "08:00",
46217
- // Default values
46218
- endTime: "19:00",
46219
- breaks: []
46220
- };
46221
- }
46222
- const data = await response.json();
46223
- let breaks = [];
46224
- if (data?.breaks) {
46225
- if (Array.isArray(data.breaks)) {
46226
- breaks = data.breaks.map((breakItem) => {
46227
- const startTime = breakItem.start || breakItem.startTime || "00:00";
46228
- const endTime = breakItem.end || breakItem.endTime || "00:00";
46229
- const calculateDuration = (start, end) => {
46230
- const [startHour, startMinute] = start.split(":").map(Number);
46231
- const [endHour, endMinute] = end.split(":").map(Number);
46232
- let startMinutes = startHour * 60 + startMinute;
46233
- let endMinutes = endHour * 60 + endMinute;
46234
- if (endMinutes < startMinutes) {
46235
- endMinutes += 24 * 60;
46236
- }
46237
- return endMinutes - startMinutes;
46238
- };
46239
- return {
46240
- startTime,
46241
- endTime,
46242
- duration: breakItem.duration || calculateDuration(startTime, endTime)
46243
- };
46244
- });
46245
- } else if (typeof data.breaks === "object" && data.breaks.breaks) {
46246
- breaks = data.breaks.breaks.map((breakItem) => {
46247
- const startTime = breakItem.start || breakItem.startTime || "00:00";
46248
- const endTime = breakItem.end || breakItem.endTime || "00:00";
46249
- const calculateDuration = (start, end) => {
46250
- const [startHour, startMinute] = start.split(":").map(Number);
46251
- const [endHour, endMinute] = end.split(":").map(Number);
46252
- let startMinutes = startHour * 60 + startMinute;
46253
- let endMinutes = endHour * 60 + endMinute;
46254
- if (endMinutes < startMinutes) {
46255
- endMinutes += 24 * 60;
46256
- }
46257
- return endMinutes - startMinutes;
46258
- };
46259
- return {
46260
- startTime,
46261
- endTime,
46262
- duration: breakItem.duration || calculateDuration(startTime, endTime)
46263
- };
46264
- });
46265
- } else if (typeof data.breaks === "string") {
46266
- try {
46267
- const parsedBreaks = JSON.parse(data.breaks);
46268
- if (Array.isArray(parsedBreaks)) {
46269
- breaks = parsedBreaks.map((breakItem) => ({
46270
- startTime: breakItem.start || breakItem.startTime || "00:00",
46271
- endTime: breakItem.end || breakItem.endTime || "00:00",
46272
- duration: breakItem.duration || 0
46273
- }));
46274
- } else if (parsedBreaks.breaks && Array.isArray(parsedBreaks.breaks)) {
46275
- breaks = parsedBreaks.breaks.map((breakItem) => ({
46276
- startTime: breakItem.start || breakItem.startTime || "00:00",
46277
- endTime: breakItem.end || breakItem.endTime || "00:00",
46278
- duration: breakItem.duration || 0
46279
- }));
46280
- }
46281
- } catch (e) {
46282
- console.error("Error parsing breaks data:", e);
46283
- }
46284
- }
46285
- }
46286
- return {
46287
- startTime: data?.start_time || "08:00",
46288
- endTime: data?.end_time || "19:00",
46289
- breaks
46290
- };
46291
- } catch (e) {
46292
- console.error("Exception when loading operating hours:", e);
46293
- return {
46294
- startTime: "08:00",
46295
- endTime: "19:00",
46296
- breaks: []
46297
- };
46298
- }
46299
- }, [supabase]);
46300
46222
  useEffect(() => {
46301
46223
  console.log("[TargetsView] Component mounted/re-rendered", {
46302
46224
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -46308,221 +46230,108 @@ var TargetsView = ({
46308
46230
  };
46309
46231
  }, []);
46310
46232
  useEffect(() => {
46311
- let timeoutId;
46312
- let retryCount = 0;
46313
- const MAX_RETRIES2 = 2;
46314
- const LOADING_TIMEOUT = 15e3;
46315
46233
  const fetchInitialData = async () => {
46316
- if (lineIds.length === 0) return;
46317
- console.log("[TargetsView] Starting fetchInitialData");
46234
+ if (lineIds.length === 0 || !companyId) return;
46235
+ console.log("[TargetsView] Starting optimized fetchInitialData with bulk endpoint");
46318
46236
  setIsLoading(true);
46319
- timeoutId = setTimeout(() => {
46320
- console.error("Loading timeout reached");
46321
- if (retryCount < MAX_RETRIES2) {
46322
- retryCount++;
46323
- console.log(`Retrying... (attempt ${retryCount + 1}/${MAX_RETRIES2 + 1})`);
46324
- toast.warning("Loading is taking longer than expected. Retrying...");
46325
- fetchInitialData();
46326
- } else {
46327
- setIsLoading(false);
46328
- toast.error("Failed to load data. Please refresh the page.");
46329
- }
46330
- }, LOADING_TIMEOUT);
46331
46237
  try {
46332
- const { data: { session } } = await supabase.auth.getSession();
46333
- if (!session?.access_token) {
46334
- throw new Error("No authentication token available");
46335
- }
46336
- const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL;
46337
- if (!backendUrl) {
46338
- 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");
46339
46250
  }
46340
- const [linesResponse, actions] = await Promise.all([
46341
- // Fetch lines data (includes factory_id)
46342
- Promise.all(lineIds.map(async (lineId) => {
46343
- try {
46344
- const response = await fetch(`${backendUrl}/api/lines/${lineId}`, {
46345
- headers: {
46346
- "Authorization": `Bearer ${session.access_token}`,
46347
- "Content-Type": "application/json"
46348
- }
46349
- });
46350
- if (!response.ok) {
46351
- console.error(`Error fetching line data for ${lineId}:`, response.statusText);
46352
- return { lineId, factoryId: void 0 };
46353
- }
46354
- const lineData = await response.json();
46355
- return { lineId, factoryId: lineData.factory_id };
46356
- } catch (err) {
46357
- console.error(`Exception fetching line data for ${lineId}:`, err);
46358
- return { lineId, factoryId: void 0 };
46359
- }
46360
- })),
46361
- // Fetch action IDs
46362
- actionService.getActionsByName(
46363
- [ACTION_NAMES.ASSEMBLY, ACTION_NAMES.PACKAGING],
46364
- companyId
46365
- )
46366
- ]);
46367
- const factoryResults = linesResponse;
46368
- const assemblyAction = actions.find((a) => a.action_name === ACTION_NAMES.ASSEMBLY);
46369
- 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);
46370
46254
  if (!assemblyAction || !packagingAction) {
46371
- throw new Error("Could not find required actions");
46255
+ throw new Error("Could not find required actions in bulk response");
46372
46256
  }
46373
46257
  const actionIdsData = {
46374
46258
  assembly: assemblyAction.id,
46375
46259
  packaging: packagingAction.id
46376
46260
  };
46377
46261
  setActionIds(actionIdsData);
46378
- const updatedLineWorkspaces = { ...initialLineWorkspaces };
46379
- factoryResults.forEach((result) => {
46380
- if (result.factoryId && updatedLineWorkspaces[result.lineId]) {
46381
- updatedLineWorkspaces[result.lineId].factoryId = result.factoryId;
46382
- }
46383
- });
46384
- const currentDate = getOperationalDate(timezone);
46385
- for (const lineId of lineIds) {
46386
- if (!updatedLineWorkspaces[lineId]?.factoryId) {
46387
- console.warn(`Skipping workspace fetch for line ${lineId} - no factory ID`);
46388
- continue;
46389
- }
46390
- try {
46391
- const workspacesData = await workspaceService.getWorkspaces(lineId);
46392
- const enabledWorkspaces = workspacesData.filter((ws) => ws.enable === true);
46393
- const actionThresholds = await workspaceService.getActionThresholds(
46394
- lineId,
46395
- currentDate,
46396
- 0
46397
- // Always use day shift for initial load
46398
- );
46399
- const operatingHoursData = await loadOperatingHours(lineId, 0);
46400
- if (operatingHoursData) {
46401
- updatedLineWorkspaces[lineId].shiftStartTime = operatingHoursData.startTime;
46402
- updatedLineWorkspaces[lineId].shiftEndTime = operatingHoursData.endTime;
46403
- updatedLineWorkspaces[lineId].breaks = operatingHoursData.breaks;
46404
- updatedLineWorkspaces[lineId].shiftHours = calculateShiftHours2(
46405
- operatingHoursData.startTime,
46406
- operatingHoursData.endTime,
46407
- operatingHoursData.breaks
46408
- );
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;
46409
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);
46410
46288
  const mappedWorkspaces = enabledWorkspaces.map((ws) => {
46411
- const threshold = actionThresholds.find((t) => t.workspace_id === ws.id);
46412
- if (!dbValues[0][lineId]) {
46413
- dbValues[0][lineId] = {};
46414
- }
46289
+ const threshold = shiftData.thresholds.find((t) => t.workspace_id === ws.id);
46415
46290
  if (threshold) {
46416
- dbValues[0][lineId][ws.id] = {
46417
- targetPPH: threshold.pph_threshold,
46418
- targetCycleTime: threshold.ideal_cycle_time,
46419
- 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 ?? ""
46420
46295
  };
46421
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
+ }
46422
46306
  return {
46423
46307
  id: ws.id,
46424
46308
  name: ws.workspace_id,
46425
- targetPPH: threshold?.pph_threshold ?? (ws.action_pph_threshold === null ? "" : ws.action_pph_threshold),
46426
- targetCycleTime: threshold?.ideal_cycle_time ?? (ws.action_cycle_time === null ? "" : ws.action_cycle_time),
46427
- targetDayOutput: threshold?.total_day_output ?? (ws.action_total_day_output === null ? "" : ws.action_total_day_output),
46428
- actionType: ws.action_id === actionIdsData.assembly ? "assembly" : ws.action_id === actionIdsData.packaging ? "packaging" : "assembly",
46429
- 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
46430
46314
  };
46431
46315
  }).sort((a, b) => a.name.localeCompare(b.name, void 0, { numeric: true }));
46432
- updatedLineWorkspaces[lineId].workspaces = mappedWorkspaces;
46433
- } catch (error) {
46434
- console.error(`Error fetching workspace data for line ${lineId}:`, error);
46435
- }
46436
- }
46437
- setLineWorkspaces(updatedLineWorkspaces);
46438
- 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
+ });
46439
46326
  } catch (error) {
46440
- console.error("Error fetching initial data:", error);
46441
- clearTimeout(timeoutId);
46442
- if (retryCount < MAX_RETRIES2) {
46443
- retryCount++;
46444
- console.log(`Error occurred, retrying... (attempt ${retryCount + 1}/${MAX_RETRIES2 + 1})`);
46445
- toast.warning("Error loading data. Retrying...");
46446
- setTimeout(() => fetchInitialData(), 1e3);
46447
- } else {
46448
- toast.error("Failed to load initial data");
46449
- setIsLoading(false);
46450
- }
46327
+ console.error("[TargetsView] Error fetching bulk data:", error);
46328
+ toast.error("Failed to load targets data. Please refresh the page.");
46451
46329
  } finally {
46452
- clearTimeout(timeoutId);
46453
- if (retryCount === 0 || retryCount >= MAX_RETRIES2) {
46454
- setIsLoading(false);
46455
- }
46330
+ setIsLoading(false);
46456
46331
  }
46457
46332
  };
46458
46333
  fetchInitialData();
46459
- return () => {
46460
- clearTimeout(timeoutId);
46461
- };
46462
- }, [lineIds, companyId, loadOperatingHours]);
46463
- const fetchAllShiftsData = useCallback(async (currentWorkspaces) => {
46464
- if (!supabase) return;
46465
- const currentDate = getOperationalDate(timezone);
46466
- const newAllShiftsData = {
46467
- 0: JSON.parse(JSON.stringify(currentWorkspaces)),
46468
- // Deep clone for day shift
46469
- 1: JSON.parse(JSON.stringify(currentWorkspaces))
46470
- // Deep clone for night shift
46471
- };
46472
- const newDbValues = { 0: {}, 1: {} };
46473
- for (const shiftId of [0, 1]) {
46474
- for (const lineId of lineIds) {
46475
- try {
46476
- const operatingHoursData = await loadOperatingHours(lineId, shiftId);
46477
- if (!operatingHoursData) {
46478
- console.warn(`No operating hours for line ${lineId}, shift ${shiftId} - using defaults`);
46479
- continue;
46480
- }
46481
- const { startTime, endTime, breaks } = operatingHoursData;
46482
- const shiftHours = calculateShiftHours2(startTime, endTime, breaks);
46483
- const actionThresholds = await workspaceService.getActionThresholds(
46484
- lineId,
46485
- currentDate,
46486
- shiftId
46487
- );
46488
- if (!newDbValues[shiftId][lineId]) {
46489
- newDbValues[shiftId][lineId] = {};
46490
- }
46491
- const existingLine = newAllShiftsData[shiftId][lineId];
46492
- if (existingLine) {
46493
- newAllShiftsData[shiftId][lineId] = {
46494
- ...existingLine,
46495
- shiftStartTime: startTime,
46496
- shiftEndTime: endTime,
46497
- breaks,
46498
- shiftHours: Number(shiftHours),
46499
- workspaces: existingLine.workspaces.map((ws) => {
46500
- const threshold = actionThresholds.find((t) => t.workspace_id === ws.id);
46501
- if (threshold) {
46502
- newDbValues[shiftId][lineId][ws.id] = {
46503
- targetPPH: threshold.pph_threshold,
46504
- targetCycleTime: threshold.ideal_cycle_time,
46505
- targetDayOutput: threshold.total_day_output
46506
- };
46507
- return {
46508
- ...ws,
46509
- targetPPH: threshold.pph_threshold,
46510
- targetCycleTime: threshold.ideal_cycle_time,
46511
- targetDayOutput: threshold.total_day_output
46512
- };
46513
- }
46514
- return ws;
46515
- })
46516
- };
46517
- }
46518
- } catch (error) {
46519
- console.error(`Error fetching data for line ${lineId}, shift ${shiftId}:`, error);
46520
- }
46521
- }
46522
- }
46523
- setAllShiftsData(newAllShiftsData);
46524
- setDbValues(newDbValues);
46525
- }, [supabase, lineIds, loadOperatingHours]);
46334
+ }, [lineIds, companyId, timezone, skuEnabled]);
46526
46335
  const toggleLineDropdown = useCallback((lineId) => {
46527
46336
  setDropdownStates((prev) => {
46528
46337
  const newIsOpen = !prev[lineId];
@@ -46595,7 +46404,7 @@ var TargetsView = ({
46595
46404
  if (value !== "") {
46596
46405
  const pph = calculatePPH(value, prev[lineId].breaks, shiftHours);
46597
46406
  updates.targetPPH = pph;
46598
- updates.targetDayOutput = calculateDayOutput(pph, shiftHours, prev[lineId].breaks);
46407
+ updates.targetDayOutput = calculateDayOutputFromCycleTime(value, shiftHours);
46599
46408
  } else {
46600
46409
  updates.targetPPH = "";
46601
46410
  updates.targetDayOutput = "";
@@ -46713,7 +46522,8 @@ var TargetsView = ({
46713
46522
  action_id: ws.actionId,
46714
46523
  workspace_id: ws.id,
46715
46524
  date: currentDate,
46716
- pph_threshold: Number(ws.targetPPH) || 0,
46525
+ pph_threshold: ws.targetPPH ? Math.round(Number(ws.targetPPH)) : 0,
46526
+ // Round to whole number
46717
46527
  ideal_cycle_time: Number(ws.targetCycleTime) || 0,
46718
46528
  total_day_output: Number(ws.targetDayOutput) || 0,
46719
46529
  action_name: ws.actionType === "assembly" ? ACTION_NAMES.ASSEMBLY : ACTION_NAMES.PACKAGING,
@@ -46732,7 +46542,8 @@ var TargetsView = ({
46732
46542
  shift_id: selectedShift,
46733
46543
  product_code: lineDataToSave.productId,
46734
46544
  threshold_day_output: packagingWorkspaces.reduce((acc, ws) => acc + (Number(ws.targetDayOutput) || 0), 0),
46735
- 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
46736
46547
  ...skuEnabled && lineDataToSave.selectedSKU ? { sku_id: lineDataToSave.selectedSKU.id } : {}
46737
46548
  };
46738
46549
  console.log(`[handleSaveLine] lineThresholdData for upsert on ${lineId}:`, lineThresholdData);
@@ -48484,94 +48295,14 @@ var SupervisorManagementView = ({
48484
48295
  ] });
48485
48296
  };
48486
48297
  var SupervisorManagementView_default = SupervisorManagementView;
48487
- var roleChangeImpacts = {
48488
- supervisor_to_plant_head: {
48489
- title: "Supervisor \u2192 Plant Head",
48490
- warnings: [
48491
- "Will lose line-specific assignments",
48492
- "Will need factory assignment",
48493
- "Will gain ability to manage supervisors",
48494
- "Will see factory-wide data"
48495
- ],
48496
- colorClass: "text-blue-700 bg-blue-50 border-blue-200"
48497
- },
48498
- supervisor_to_owner: {
48499
- title: "Supervisor \u2192 Owner",
48500
- warnings: [
48501
- "Will gain full company access",
48502
- "Will lose line-specific restrictions",
48503
- "Will be able to manage all users",
48504
- "Will see all company data"
48505
- ],
48506
- colorClass: "text-red-700 bg-red-50 border-red-200"
48507
- },
48508
- plant_head_to_supervisor: {
48509
- title: "Plant Head \u2192 Supervisor",
48510
- warnings: [
48511
- "Will lose factory access",
48512
- "Will lose ability to manage others",
48513
- "Will need line assignment",
48514
- "Will only see assigned line data"
48515
- ],
48516
- colorClass: "text-orange-700 bg-orange-50 border-orange-200"
48517
- },
48518
- plant_head_to_owner: {
48519
- title: "Plant Head \u2192 Owner",
48520
- warnings: [
48521
- "Will gain full company access",
48522
- "Will lose factory-specific restrictions",
48523
- "Will be able to manage all users",
48524
- "Will see all company data"
48525
- ],
48526
- colorClass: "text-blue-700 bg-blue-50 border-blue-200"
48527
- },
48528
- owner_to_plant_head: {
48529
- title: "Owner \u2192 Plant Head",
48530
- warnings: [
48531
- "Will lose company-wide access",
48532
- "Will need factory assignment",
48533
- "Will only manage supervisors",
48534
- "Will only see factory data"
48535
- ],
48536
- colorClass: "text-orange-700 bg-orange-50 border-orange-200"
48537
- },
48538
- owner_to_supervisor: {
48539
- title: "Owner \u2192 Supervisor",
48540
- warnings: [
48541
- "Will lose company-wide access",
48542
- "Will lose ability to manage others",
48543
- "Will need line assignment",
48544
- "Will only see assigned line data"
48545
- ],
48546
- colorClass: "text-red-700 bg-red-50 border-red-200"
48547
- },
48548
- to_optifye: {
48549
- title: "Promoting to Optifye",
48550
- warnings: [
48551
- "Will gain access to ALL companies",
48552
- "Will be able to manage all users globally",
48553
- "Will see all data across the platform",
48554
- "This is the highest privilege level"
48555
- ],
48556
- colorClass: "text-purple-700 bg-purple-50 border-purple-200"
48557
- },
48558
- from_optifye: {
48559
- title: "Demoting from Optifye",
48560
- warnings: [
48561
- "Will lose access to other companies",
48562
- "Will only see assigned company data",
48563
- "Will lose global management capabilities",
48564
- "Will need appropriate assignments"
48565
- ],
48566
- colorClass: "text-red-700 bg-red-50 border-red-200"
48567
- }
48568
- };
48569
- var getRoleChangeKey = (currentRole, newRole) => {
48570
- if (currentRole === newRole) return null;
48571
- if (newRole === "optifye") return "to_optifye";
48572
- if (currentRole === "optifye") return "from_optifye";
48573
- const key = `${currentRole}_to_${newRole}`;
48574
- 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;
48575
48306
  };
48576
48307
  var ChangeRoleDialog = ({
48577
48308
  user,
@@ -48584,8 +48315,6 @@ var ChangeRoleDialog = ({
48584
48315
  const [confirmed, setConfirmed] = useState(false);
48585
48316
  const [isChanging, setIsChanging] = useState(false);
48586
48317
  const roleChanged = selectedRole !== user.role_level;
48587
- const changeKey = roleChanged ? getRoleChangeKey(user.role_level, selectedRole) : null;
48588
- const impactInfo = changeKey ? roleChangeImpacts[changeKey] : null;
48589
48318
  const handleNext = () => {
48590
48319
  if (roleChanged) {
48591
48320
  setStep("confirm");
@@ -48605,7 +48334,7 @@ var ChangeRoleDialog = ({
48605
48334
  setStep("select");
48606
48335
  setConfirmed(false);
48607
48336
  };
48608
- 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: [
48609
48338
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
48610
48339
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
48611
48340
  /* @__PURE__ */ jsx("div", { className: "p-2 bg-blue-100 rounded-lg", children: /* @__PURE__ */ jsx(UserCog, { className: "w-5 h-5 text-blue-600" }) }),
@@ -48615,20 +48344,14 @@ var ChangeRoleDialog = ({
48615
48344
  "button",
48616
48345
  {
48617
48346
  onClick: onClose,
48618
- 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",
48619
48348
  disabled: isChanging,
48349
+ "aria-label": "Close dialog",
48620
48350
  children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" })
48621
48351
  }
48622
48352
  )
48623
48353
  ] }),
48624
- /* @__PURE__ */ jsx("div", { className: "p-6 space-y-6", children: step === "select" ? /* @__PURE__ */ jsxs(Fragment, { children: [
48625
- roleChanged && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-4 bg-amber-50 border border-amber-200 rounded-lg", children: [
48626
- /* @__PURE__ */ jsx(AlertCircle, { className: "w-5 h-5 text-amber-600 mt-0.5 flex-shrink-0" }),
48627
- /* @__PURE__ */ jsxs("div", { children: [
48628
- /* @__PURE__ */ jsx("p", { className: "font-medium text-amber-900 text-sm", children: "Changing roles affects access" }),
48629
- /* @__PURE__ */ jsx("p", { className: "text-amber-700 text-sm mt-1", children: "Review the impact carefully before confirming." })
48630
- ] })
48631
- ] }),
48354
+ /* @__PURE__ */ jsx("div", { className: "p-6 space-y-5", children: step === "select" ? /* @__PURE__ */ jsxs(Fragment, { children: [
48632
48355
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48633
48356
  /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "User" }),
48634
48357
  /* @__PURE__ */ jsxs("div", { className: "p-3 bg-gray-50 rounded-lg", children: [
@@ -48639,7 +48362,7 @@ var ChangeRoleDialog = ({
48639
48362
  ] })
48640
48363
  ] })
48641
48364
  ] }),
48642
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
48365
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48643
48366
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "Select new role" }),
48644
48367
  /* @__PURE__ */ jsx("div", { className: "space-y-2", children: availableRoles.map((role) => /* @__PURE__ */ jsxs(
48645
48368
  "label",
@@ -48663,27 +48386,24 @@ var ChangeRoleDialog = ({
48663
48386
  role
48664
48387
  )) })
48665
48388
  ] }),
48666
- impactInfo && /* @__PURE__ */ jsxs("div", { className: `p-4 border rounded-lg ${impactInfo.colorClass}`, children: [
48667
- /* @__PURE__ */ jsx("p", { className: "font-medium text-sm mb-2", children: "\u2139\uFE0F Impact of this change:" }),
48668
- /* @__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: [
48669
- /* @__PURE__ */ jsx("span", { className: "mt-0.5", children: "\u2022" }),
48670
- /* @__PURE__ */ jsx("span", { children: warning6 })
48671
- ] }, idx)) })
48672
- ] })
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
+ ] }) })
48673
48396
  ] }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
48674
48397
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-3 p-4 bg-gray-50 rounded-lg", children: [
48675
48398
  /* @__PURE__ */ jsx(RoleBadge, { role: user.role_level }),
48676
48399
  /* @__PURE__ */ jsx(ChevronRight, { className: "w-5 h-5 text-gray-400" }),
48677
48400
  /* @__PURE__ */ jsx(RoleBadge, { role: selectedRole })
48678
48401
  ] }),
48679
- impactInfo && /* @__PURE__ */ jsxs("div", { className: `p-4 border rounded-lg ${impactInfo.colorClass}`, children: [
48680
- /* @__PURE__ */ jsx("p", { className: "font-medium text-sm mb-3", children: impactInfo.title }),
48681
- /* @__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: [
48682
- /* @__PURE__ */ jsx("span", { className: "mt-0.5", children: "\u2022" }),
48683
- /* @__PURE__ */ jsx("span", { children: warning6 })
48684
- ] }, 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 })
48685
48405
  ] }),
48686
- /* @__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: [
48687
48407
  /* @__PURE__ */ jsx(
48688
48408
  "input",
48689
48409
  {
@@ -48694,7 +48414,7 @@ var ChangeRoleDialog = ({
48694
48414
  className: "mt-0.5 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
48695
48415
  }
48696
48416
  ),
48697
- /* @__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" })
48698
48418
  ] })
48699
48419
  ] }) }) }),
48700
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: [
@@ -48737,7 +48457,7 @@ var ChangeRoleDialog = ({
48737
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" })
48738
48458
  ] }),
48739
48459
  "Changing..."
48740
- ] }) : "Change Role"
48460
+ ] }) : "Confirm Change"
48741
48461
  }
48742
48462
  )
48743
48463
  ] }) })
@@ -48760,32 +48480,34 @@ var ConfirmRemoveUserDialog = ({
48760
48480
  setIsRemoving(false);
48761
48481
  }
48762
48482
  };
48763
- 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: [
48764
48484
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
48765
48485
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
48766
48486
  /* @__PURE__ */ jsx("div", { className: "p-2 bg-red-100 rounded-lg", children: /* @__PURE__ */ jsx(Trash2, { className: "w-5 h-5 text-red-600" }) }),
48767
- /* @__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" })
48768
48488
  ] }),
48769
48489
  /* @__PURE__ */ jsx(
48770
48490
  "button",
48771
48491
  {
48772
48492
  onClick: onCancel,
48773
- 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",
48774
48496
  children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" })
48775
48497
  }
48776
48498
  )
48777
48499
  ] }),
48778
- /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-6", children: [
48779
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-4 bg-red-50 border border-red-200 rounded-lg", children: [
48780
- /* @__PURE__ */ jsx(AlertTriangle, { className: "w-5 h-5 text-red-600 mt-0.5 flex-shrink-0" }),
48781
- /* @__PURE__ */ jsxs("div", { children: [
48782
- /* @__PURE__ */ jsx("p", { className: "font-medium text-red-900 text-sm", children: "This action cannot be undone" }),
48783
- /* @__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."
48784
48506
  ] })
48785
48507
  ] }),
48786
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
48787
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "You are about to remove:" }),
48788
- /* @__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: [
48789
48511
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
48790
48512
  /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-600", children: "Email" }),
48791
48513
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-900", children: user.email })
@@ -48810,28 +48532,7 @@ var ConfirmRemoveUserDialog = ({
48810
48532
  ] })
48811
48533
  ] })
48812
48534
  ] }),
48813
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48814
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "This will:" }),
48815
- /* @__PURE__ */ jsxs("ul", { className: "space-y-2 text-sm text-gray-600", children: [
48816
- /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
48817
- /* @__PURE__ */ jsx("span", { className: "text-red-500 mt-0.5", children: "\u2022" }),
48818
- /* @__PURE__ */ jsx("span", { children: "Revoke all access immediately" })
48819
- ] }),
48820
- /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
48821
- /* @__PURE__ */ jsx("span", { className: "text-red-500 mt-0.5", children: "\u2022" }),
48822
- /* @__PURE__ */ jsx("span", { children: "Remove all line and factory assignments" })
48823
- ] }),
48824
- /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
48825
- /* @__PURE__ */ jsx("span", { className: "text-red-500 mt-0.5", children: "\u2022" }),
48826
- /* @__PURE__ */ jsx("span", { children: "Log the user out of all sessions" })
48827
- ] }),
48828
- /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
48829
- /* @__PURE__ */ jsx("span", { className: "text-red-500 mt-0.5", children: "\u2022" }),
48830
- /* @__PURE__ */ jsx("span", { children: "User will not be able to access the dashboard" })
48831
- ] })
48832
- ] })
48833
- ] }),
48834
- /* @__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: [
48835
48536
  /* @__PURE__ */ jsx(
48836
48537
  "input",
48837
48538
  {
@@ -48842,7 +48543,7 @@ var ConfirmRemoveUserDialog = ({
48842
48543
  className: "mt-0.5 h-4 w-4 text-red-600 border-gray-300 rounded focus:ring-red-500"
48843
48544
  }
48844
48545
  ),
48845
- /* @__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" })
48846
48547
  ] })
48847
48548
  ] }),
48848
48549
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3 p-6 border-t border-gray-200 bg-gray-50", children: [
@@ -48866,8 +48567,8 @@ var ConfirmRemoveUserDialog = ({
48866
48567
  /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4", fill: "none" }),
48867
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" })
48868
48569
  ] }),
48869
- "Removing..."
48870
- ] }) : "Remove User"
48570
+ "Deleting..."
48571
+ ] }) : "Delete User"
48871
48572
  }
48872
48573
  )
48873
48574
  ] })
@@ -49290,7 +48991,6 @@ var FactoryAssignmentDropdown = ({
49290
48991
  var UserManagementTable = ({
49291
48992
  users,
49292
48993
  isLoading = false,
49293
- onUserClick,
49294
48994
  onRoleChange,
49295
48995
  onRemoveUser,
49296
48996
  onLineAssignmentUpdate,
@@ -49316,9 +49016,48 @@ var UserManagementTable = ({
49316
49016
  const [sortField, setSortField] = useState("email");
49317
49017
  const [sortDirection, setSortDirection] = useState("asc");
49318
49018
  const [openActionMenuId, setOpenActionMenuId] = useState(null);
49019
+ const [dropdownPosition, setDropdownPosition] = useState(null);
49020
+ const actionButtonRefs = useRef({});
49319
49021
  const [selectedUser, setSelectedUser] = useState(null);
49320
49022
  const [showChangeRoleDialog, setShowChangeRoleDialog] = useState(false);
49321
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]);
49322
49061
  const filteredAndSortedUsers = useMemo(() => {
49323
49062
  let filtered = users;
49324
49063
  filtered = filtered.filter((user) => availableRoles.includes(user.role_level));
@@ -49509,7 +49248,7 @@ var UserManagementTable = ({
49509
49248
  LineAssignmentDropdown,
49510
49249
  {
49511
49250
  userId: user.user_id,
49512
- currentLineIds: user.properties?.line_id || [],
49251
+ currentLineIds: user.properties?.line_ids || [],
49513
49252
  availableLines: (
49514
49253
  // Filter lines to only show those from the target user's company
49515
49254
  (() => {
@@ -49547,69 +49286,24 @@ var UserManagementTable = ({
49547
49286
  }
49548
49287
  ) : /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) }) }),
49549
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) }) }),
49550
- /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right", children: hasActions && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
49551
- /* @__PURE__ */ jsx(
49552
- "button",
49553
- {
49554
- onClick: () => setOpenActionMenuId(openActionMenuId === user.user_id ? null : user.user_id),
49555
- className: "p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49556
- "aria-label": "User actions",
49557
- children: /* @__PURE__ */ jsx(MoreVertical, { className: "w-4 h-4" })
49558
- }
49559
- ),
49560
- openActionMenuId === user.user_id && /* @__PURE__ */ jsxs(Fragment, { children: [
49561
- /* @__PURE__ */ jsx(
49562
- "div",
49563
- {
49564
- className: "fixed inset-0 z-[9998]",
49565
- 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);
49566
49300
  }
49567
- ),
49568
- /* @__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: [
49569
- /* @__PURE__ */ jsxs(
49570
- "button",
49571
- {
49572
- onClick: () => {
49573
- onUserClick?.(user);
49574
- setOpenActionMenuId(null);
49575
- },
49576
- className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
49577
- children: [
49578
- /* @__PURE__ */ jsx(Eye, { className: "w-4 h-4" }),
49579
- "View Details"
49580
- ]
49581
- }
49582
- ),
49583
- canChangeRole && /* @__PURE__ */ jsxs(
49584
- "button",
49585
- {
49586
- onClick: () => handleChangeRole(user),
49587
- className: "w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 flex items-center gap-2",
49588
- disabled: isCurrentUser,
49589
- children: [
49590
- /* @__PURE__ */ jsx(UserCog, { className: "w-4 h-4" }),
49591
- "Change Role"
49592
- ]
49593
- }
49594
- ),
49595
- canRemove && /* @__PURE__ */ jsxs(Fragment, { children: [
49596
- /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200 my-1" }),
49597
- /* @__PURE__ */ jsxs(
49598
- "button",
49599
- {
49600
- onClick: () => handleRemoveUser(user),
49601
- className: "w-full px-4 py-2 text-sm text-left text-red-600 hover:bg-red-50 flex items-center gap-2",
49602
- disabled: isCurrentUser,
49603
- children: [
49604
- /* @__PURE__ */ jsx(Trash2, { className: "w-4 h-4" }),
49605
- "Remove User"
49606
- ]
49607
- }
49608
- )
49609
- ] })
49610
- ] }) })
49611
- ] })
49612
- ] }) })
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
+ ) }) })
49613
49307
  ]
49614
49308
  },
49615
49309
  user.user_id
@@ -49617,6 +49311,75 @@ var UserManagementTable = ({
49617
49311
  }) })
49618
49312
  ] }) }) })
49619
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
+ ),
49620
49383
  showChangeRoleDialog && selectedUser && /* @__PURE__ */ jsx(
49621
49384
  ChangeRoleDialog,
49622
49385
  {
@@ -50268,15 +50031,12 @@ var TeamManagementView = ({
50268
50031
  if (!supabase || !user) return;
50269
50032
  try {
50270
50033
  const userManagementService = createUserManagementService(supabase);
50271
- await userManagementService.deactivateUser({
50272
- user_id: userId,
50273
- deactivated_by: user.id
50274
- });
50275
- toast.success("User removed successfully");
50034
+ const result = await userManagementService.deleteUser(userId);
50035
+ toast.success(`User ${result.deleted_user_email} permanently deleted`);
50276
50036
  loadData();
50277
50037
  } catch (err) {
50278
- console.error("Error removing user:", err);
50279
- toast.error("Failed to remove user");
50038
+ console.error("Error deleting user:", err);
50039
+ toast.error("Failed to delete user");
50280
50040
  }
50281
50041
  }, [supabase, user, loadData]);
50282
50042
  const handleLineAssignmentUpdate = useCallback(async (userId, lineIds) => {
@@ -50370,9 +50130,6 @@ var TeamManagementView = ({
50370
50130
  {
50371
50131
  users,
50372
50132
  currentUserId: user?.id,
50373
- onUserClick: (clickedUser) => {
50374
- console.log("View user details:", clickedUser);
50375
- },
50376
50133
  onRoleChange: handleRoleChange,
50377
50134
  onRemoveUser: handleRemoveUser,
50378
50135
  onLineAssignmentUpdate: handleLineAssignmentUpdate,