@optifye/dashboard-core 6.9.2 → 6.9.5

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();
@@ -24305,6 +24332,7 @@ var VideoCard = React23__default.memo(({
24305
24332
  useRAF = true,
24306
24333
  className = "",
24307
24334
  compact = false,
24335
+ displayName,
24308
24336
  onMouseEnter,
24309
24337
  onMouseLeave
24310
24338
  }) => {
@@ -24326,7 +24354,7 @@ var VideoCard = React23__default.memo(({
24326
24354
  onFatalError: onFatalError ?? (() => throttledReloadDashboard())
24327
24355
  });
24328
24356
  }
24329
- const displayName = getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24357
+ const workspaceDisplayName = displayName || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24330
24358
  const getEfficiencyOverlayColor = (efficiency) => {
24331
24359
  if (efficiency >= 80) {
24332
24360
  return "bg-[#00D654]/25";
@@ -24419,7 +24447,7 @@ var VideoCard = React23__default.memo(({
24419
24447
  /* @__PURE__ */ jsxs("div", { className: `absolute bottom-0 left-0 right-0 bg-black bg-opacity-60 p-1.5 sm:${compact ? "p-1" : "p-1.5"} flex justify-between items-center z-10`, children: [
24420
24448
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
24421
24449
  /* @__PURE__ */ jsx(Camera, { size: compact ? 10 : 12, className: "text-white" }),
24422
- /* @__PURE__ */ jsx("p", { className: `text-white text-[11px] sm:${compact ? "text-[10px]" : "text-xs"} font-medium tracking-wide`, children: displayName })
24450
+ /* @__PURE__ */ jsx("p", { className: `text-white text-[11px] sm:${compact ? "text-[10px]" : "text-xs"} font-medium tracking-wide`, children: workspaceDisplayName })
24423
24451
  ] }),
24424
24452
  /* @__PURE__ */ jsxs("div", { className: `flex items-center ${compact ? "gap-1" : "gap-1.5"}`, children: [
24425
24453
  trendInfo && /* @__PURE__ */ jsx(
@@ -24456,6 +24484,7 @@ var VideoGridView = React23__default.memo(({
24456
24484
  selectedLine,
24457
24485
  className = "",
24458
24486
  videoSources = {},
24487
+ displayNames = {},
24459
24488
  onWorkspaceHover,
24460
24489
  onWorkspaceHoverEnd
24461
24490
  }) => {
@@ -24670,6 +24699,7 @@ var VideoGridView = React23__default.memo(({
24670
24699
  isVeryLowEfficiency,
24671
24700
  cropping: workspaceCropping,
24672
24701
  canvasFps: canvasConfig?.fps,
24702
+ displayName: displayNames[workspace.workspace_name] || workspace.workspace_name,
24673
24703
  useRAF: canvasConfig?.useRAF,
24674
24704
  compact: !selectedLine,
24675
24705
  onMouseEnter: onWorkspaceHover ? () => onWorkspaceHover(workspaceId) : void 0,
@@ -24687,6 +24717,7 @@ VideoGridView.displayName = "VideoGridView";
24687
24717
  var MapGridView = React23__default.memo(({
24688
24718
  workspaces,
24689
24719
  className = "",
24720
+ displayNames = {},
24690
24721
  onWorkspaceHover,
24691
24722
  onWorkspaceHoverEnd
24692
24723
  }) => {
@@ -24717,10 +24748,10 @@ var MapGridView = React23__default.memo(({
24717
24748
  efficiency: workspace.efficiency,
24718
24749
  action_count: workspace.action_count
24719
24750
  });
24720
- const displayName = getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24751
+ const displayName = displayNames[workspace.workspace_name] || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24721
24752
  const navParams = getWorkspaceNavigationParams(workspaceId, displayName, workspace.line_id);
24722
24753
  router.push(`/workspace/${workspaceId}${navParams}`);
24723
- }, [router]);
24754
+ }, [router, displayNames]);
24724
24755
  const activePositions = useMemo(() => {
24725
24756
  return workspacePositions.filter((pos) => {
24726
24757
  const wsKey = pos.id.toUpperCase();
@@ -24746,7 +24777,7 @@ var MapGridView = React23__default.memo(({
24746
24777
  if (!workspace) return null;
24747
24778
  const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
24748
24779
  getPerformanceColor(workspace.efficiency);
24749
- const displayName = getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24780
+ const workspaceDisplayName = displayNames[workspace.workspace_name] || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24750
24781
  return /* @__PURE__ */ jsx(
24751
24782
  motion.div,
24752
24783
  {
@@ -24772,7 +24803,7 @@ var MapGridView = React23__default.memo(({
24772
24803
  ${position.size === "conveyor" ? "w-32 h-24" : position.size === "large" ? "w-40 h-32" : "w-36 h-28"}
24773
24804
  `,
24774
24805
  children: [
24775
- /* @__PURE__ */ jsx("div", { className: "absolute top-0 left-0 right-0 px-2 py-1.5 border-b border-gray-200", children: /* @__PURE__ */ jsx("div", { className: "text-xs font-bold text-gray-800 truncate text-center leading-tight", children: displayName }) }),
24806
+ /* @__PURE__ */ jsx("div", { className: "absolute top-0 left-0 right-0 px-2 py-1.5 border-b border-gray-200", children: /* @__PURE__ */ jsx("div", { className: "text-xs font-bold text-gray-800 truncate text-center leading-tight", children: workspaceDisplayName }) }),
24776
24807
  /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 px-2 py-2", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center", children: /* @__PURE__ */ jsxs("div", { className: "text-2xl font-bold text-gray-900", children: [
24777
24808
  Math.round(workspace.efficiency),
24778
24809
  "%"
@@ -34800,6 +34831,7 @@ var WorkspaceGrid = React23__default.memo(({
34800
34831
  line2Uuid = "line-2",
34801
34832
  className = "",
34802
34833
  videoSources = {},
34834
+ displayNames = {},
34803
34835
  onWorkspaceHover,
34804
34836
  onWorkspaceHoverEnd
34805
34837
  }) => {
@@ -34865,6 +34897,7 @@ var WorkspaceGrid = React23__default.memo(({
34865
34897
  {
34866
34898
  workspaces,
34867
34899
  videoSources,
34900
+ displayNames,
34868
34901
  onWorkspaceHover,
34869
34902
  onWorkspaceHoverEnd
34870
34903
  }
@@ -34883,6 +34916,7 @@ var WorkspaceGrid = React23__default.memo(({
34883
34916
  MapGridViewComponent,
34884
34917
  {
34885
34918
  workspaces,
34919
+ displayNames,
34886
34920
  onWorkspaceHover,
34887
34921
  onWorkspaceHoverEnd
34888
34922
  }
@@ -41419,7 +41453,6 @@ function HomeView({
41419
41453
  factoryName = "Simba Beer - Line 1"
41420
41454
  }) {
41421
41455
  const [isHydrated, setIsHydrated] = useState(false);
41422
- const availableLineIds = useMemo(() => [factoryViewId, ...allLineIds], [factoryViewId, allLineIds]);
41423
41456
  const [selectedLineId, setSelectedLineId] = useState(factoryViewId);
41424
41457
  const [isChangingFilter, setIsChangingFilter] = useState(false);
41425
41458
  const [errorMessage, setErrorMessage] = useState(null);
@@ -41429,6 +41462,21 @@ function HomeView({
41429
41462
  const entityConfig = useEntityConfig();
41430
41463
  const supabaseClient = useSupabaseClient();
41431
41464
  const { user } = useAuth();
41465
+ const isSupervisor = user?.role_level === "supervisor";
41466
+ const hasMultipleLines = allLineIds.length > 1;
41467
+ const availableLineIds = useMemo(() => {
41468
+ if (isSupervisor && !hasMultipleLines) {
41469
+ return allLineIds;
41470
+ }
41471
+ return [factoryViewId, ...allLineIds];
41472
+ }, [factoryViewId, allLineIds, isSupervisor, hasMultipleLines]);
41473
+ useEffect(() => {
41474
+ if (user) {
41475
+ if (isSupervisor && allLineIds.length > 0) {
41476
+ setSelectedLineId(allLineIds[0]);
41477
+ }
41478
+ }
41479
+ }, [user, isSupervisor, allLineIds]);
41432
41480
  const userCompanyId = useMemo(() => {
41433
41481
  return user?.properties?.company_id || user?.company_id || entityConfig.companyId;
41434
41482
  }, [user, entityConfig.companyId]);
@@ -41463,7 +41511,7 @@ function HomeView({
41463
41511
  displayNames: workspaceDisplayNames,
41464
41512
  loading: displayNamesLoading,
41465
41513
  error: displayNamesError
41466
- } = useWorkspaceDisplayNames(void 0, selectedLineId);
41514
+ } = useWorkspaceDisplayNames(selectedLineId, void 0);
41467
41515
  useCallback(() => {
41468
41516
  console.log("Refetching KPIs after line metrics update");
41469
41517
  }, []);
@@ -41489,7 +41537,9 @@ function HomeView({
41489
41537
  refetch: refetchMetrics
41490
41538
  } = useDashboardMetrics({
41491
41539
  lineId: selectedLineId,
41492
- onLineMetricsUpdate
41540
+ onLineMetricsUpdate,
41541
+ userAccessibleLineIds: allLineIds
41542
+ // Pass user's accessible lines for supervisor filtering
41493
41543
  });
41494
41544
  const {
41495
41545
  activeBreaks: allActiveBreaks,
@@ -41825,7 +41875,7 @@ function HomeView({
41825
41875
  /* @__PURE__ */ jsx(SelectContent, { className: "z-50 bg-white shadow-lg border border-gray-200 rounded-md text-xs sm:text-sm", children: availableLineIds.map((id3) => /* @__PURE__ */ jsx(SelectItem, { value: id3, children: lineNames[id3] || (id3 === factoryViewId ? "All Lines" : `Line ${id3.substring(0, 4)}`) }, id3)) })
41826
41876
  ] });
41827
41877
  }, [availableLineIds, handleLineChange, selectedLineId, lineNames, factoryViewId, allLineIds.length]);
41828
- const isInitialLoading = !isHydrated || !displayNamesInitialized && displayNamesLoading;
41878
+ const isInitialLoading = !isHydrated || displayNamesLoading || !displayNamesInitialized && displayNamesLoading;
41829
41879
  const isDataLoading = metricsLoading || kpisLoading;
41830
41880
  if (isInitialLoading) {
41831
41881
  return /* @__PURE__ */ jsx(LoadingPageCmp, { message: "Loading Dashboard..." });
@@ -41870,6 +41920,7 @@ function HomeView({
41870
41920
  lineNames,
41871
41921
  factoryView: factoryViewId,
41872
41922
  videoSources,
41923
+ displayNames: workspaceDisplayNames,
41873
41924
  className: "h-full",
41874
41925
  onWorkspaceHover: handleWorkspaceHover,
41875
41926
  onWorkspaceHoverEnd: handleWorkspaceHoverEnd
@@ -41897,6 +41948,7 @@ function HomeView({
41897
41948
  lineNames,
41898
41949
  factoryView: factoryViewId,
41899
41950
  videoSources,
41951
+ displayNames: workspaceDisplayNames,
41900
41952
  className: "h-full",
41901
41953
  onWorkspaceHover: handleWorkspaceHover,
41902
41954
  onWorkspaceHoverEnd: handleWorkspaceHoverEnd
@@ -43548,7 +43600,8 @@ var LeaderboardDetailView = memo(({
43548
43600
  line1Id = "",
43549
43601
  line2Id = "",
43550
43602
  lineNames = {},
43551
- className = ""
43603
+ className = "",
43604
+ userAccessibleLineIds
43552
43605
  }) => {
43553
43606
  const navigation = useNavigation();
43554
43607
  const entityConfig = useEntityConfig();
@@ -43588,7 +43641,9 @@ var LeaderboardDetailView = memo(({
43588
43641
  refreshWorkspaces
43589
43642
  } = useAllWorkspaceMetrics({
43590
43643
  initialDate: date,
43591
- initialShiftId: typeof shift === "number" ? shift : typeof shift === "string" ? parseInt(shift) : void 0
43644
+ initialShiftId: typeof shift === "number" ? shift : typeof shift === "string" ? parseInt(shift) : void 0,
43645
+ allowedLineIds: userAccessibleLineIds
43646
+ // Filter to user's accessible lines only
43592
43647
  });
43593
43648
  const getShiftName = useCallback((shiftId2) => {
43594
43649
  if (shiftId2 === void 0) return "Day";
@@ -44944,12 +44999,17 @@ var ACTION_NAMES = {
44944
44999
  var calculatePPH = (cycleTime, breaks = [], shiftHours = 0) => {
44945
45000
  if (cycleTime === "" || cycleTime === 0) return "";
44946
45001
  const pph = 3600 / cycleTime;
44947
- return Number(pph.toFixed(1));
45002
+ return Math.round(pph);
44948
45003
  };
44949
45004
  var calculateDayOutput = (pph, shiftHours, breaks = []) => {
44950
45005
  if (pph === "") return "";
44951
45006
  return Math.round(pph * shiftHours);
44952
45007
  };
45008
+ var calculateDayOutputFromCycleTime = (cycleTime, shiftHours) => {
45009
+ if (cycleTime === "" || cycleTime === 0 || shiftHours === 0) return "";
45010
+ const dayOutput = 3600 / cycleTime * shiftHours;
45011
+ return Math.round(dayOutput);
45012
+ };
44953
45013
  var formatWorkspaceName = (name, lineId) => {
44954
45014
  return getWorkspaceDisplayName(name, lineId);
44955
45015
  };
@@ -44961,20 +45021,6 @@ var getStoredLineState2 = (lineId) => {
44961
45021
  return false;
44962
45022
  }
44963
45023
  };
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
45024
  var BulkConfigureModal = ({
44979
45025
  isOpen,
44980
45026
  onClose,
@@ -46182,121 +46228,6 @@ var TargetsView = ({
46182
46228
  const dashboardConfig = useDashboardConfig();
46183
46229
  const { skus, isLoading: skusLoading } = useSKUs(companyId);
46184
46230
  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
46231
  useEffect(() => {
46301
46232
  console.log("[TargetsView] Component mounted/re-rendered", {
46302
46233
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -46308,221 +46239,108 @@ var TargetsView = ({
46308
46239
  };
46309
46240
  }, []);
46310
46241
  useEffect(() => {
46311
- let timeoutId;
46312
- let retryCount = 0;
46313
- const MAX_RETRIES2 = 2;
46314
- const LOADING_TIMEOUT = 15e3;
46315
46242
  const fetchInitialData = async () => {
46316
- if (lineIds.length === 0) return;
46317
- console.log("[TargetsView] Starting fetchInitialData");
46243
+ if (lineIds.length === 0 || !companyId) return;
46244
+ console.log("[TargetsView] Starting optimized fetchInitialData with bulk endpoint");
46318
46245
  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
46246
  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");
46247
+ const currentDate = getOperationalDate(timezone);
46248
+ const bulkResponse = await workspaceService.fetchBulkTargets({
46249
+ companyId,
46250
+ lineIds,
46251
+ date: currentDate,
46252
+ shifts: [0, 1],
46253
+ // Fetch both shifts at once
46254
+ includeSkus: skuEnabled,
46255
+ includeActions: true
46256
+ });
46257
+ if (!bulkResponse.success) {
46258
+ throw new Error("Failed to fetch bulk targets data");
46339
46259
  }
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);
46260
+ const { data } = bulkResponse;
46261
+ const assemblyAction = Object.values(data.actions).find((a) => a.action_name === ACTION_NAMES.ASSEMBLY);
46262
+ const packagingAction = Object.values(data.actions).find((a) => a.action_name === ACTION_NAMES.PACKAGING);
46370
46263
  if (!assemblyAction || !packagingAction) {
46371
- throw new Error("Could not find required actions");
46264
+ throw new Error("Could not find required actions in bulk response");
46372
46265
  }
46373
46266
  const actionIdsData = {
46374
46267
  assembly: assemblyAction.id,
46375
46268
  packaging: packagingAction.id
46376
46269
  };
46377
46270
  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
- );
46271
+ const newAllShiftsData = { 0: {}, 1: {} };
46272
+ const newDbValues = { 0: {}, 1: {} };
46273
+ Object.entries(data.lines).forEach(([lineId, lineData]) => {
46274
+ [0, 1].forEach((shiftId) => {
46275
+ const shiftData = lineData.shifts[shiftId.toString()];
46276
+ if (!shiftData) {
46277
+ console.warn(`No shift ${shiftId} data for line ${lineId}`);
46278
+ return;
46409
46279
  }
46280
+ const operatingHours = shiftData.operating_hours || {
46281
+ start_time: "08:00",
46282
+ end_time: "19:00",
46283
+ breaks: [],
46284
+ total_hours: 11
46285
+ };
46286
+ newAllShiftsData[shiftId][lineId] = {
46287
+ productId: "",
46288
+ shiftStartTime: operatingHours.start_time,
46289
+ shiftEndTime: operatingHours.end_time,
46290
+ breaks: operatingHours.breaks,
46291
+ shiftHours: operatingHours.total_hours,
46292
+ workspaces: [],
46293
+ factoryId: lineData.line_info.factory_id
46294
+ };
46295
+ newDbValues[shiftId][lineId] = {};
46296
+ const enabledWorkspaces = lineData.workspaces.filter((ws) => ws.enable === true);
46410
46297
  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
- }
46298
+ const threshold = shiftData.thresholds.find((t) => t.workspace_id === ws.id);
46415
46299
  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
46300
+ newDbValues[shiftId][lineId][ws.id] = {
46301
+ targetPPH: threshold.pph_threshold ? Math.round(threshold.pph_threshold) : "",
46302
+ targetCycleTime: threshold.ideal_cycle_time ?? "",
46303
+ targetDayOutput: threshold.total_day_output ?? ""
46420
46304
  };
46421
46305
  }
46306
+ let actionType = "assembly";
46307
+ let actionId = actionIdsData.assembly;
46308
+ if (ws.action_id === packagingAction.id || ws.action_type === "packaging") {
46309
+ actionType = "packaging";
46310
+ actionId = packagingAction.id;
46311
+ } else if (ws.action_id === assemblyAction.id || ws.action_type === "assembly") {
46312
+ actionType = "assembly";
46313
+ actionId = assemblyAction.id;
46314
+ }
46422
46315
  return {
46423
46316
  id: ws.id,
46424
46317
  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
46318
+ targetPPH: threshold?.pph_threshold ? Math.round(threshold.pph_threshold) : "",
46319
+ targetCycleTime: threshold?.ideal_cycle_time ?? "",
46320
+ targetDayOutput: threshold?.total_day_output ?? "",
46321
+ actionType,
46322
+ actionId
46430
46323
  };
46431
46324
  }).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);
46325
+ newAllShiftsData[shiftId][lineId].workspaces = mappedWorkspaces;
46326
+ });
46327
+ });
46328
+ setAllShiftsData(newAllShiftsData);
46329
+ setDbValues(newDbValues);
46330
+ console.log("[TargetsView] Successfully loaded all data with bulk endpoint:", {
46331
+ lineCount: bulkResponse.metadata.line_count,
46332
+ totalWorkspaces: bulkResponse.metadata.total_workspaces,
46333
+ loadTime: "Fast!"
46334
+ });
46439
46335
  } 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
- }
46336
+ console.error("[TargetsView] Error fetching bulk data:", error);
46337
+ toast.error("Failed to load targets data. Please refresh the page.");
46451
46338
  } finally {
46452
- clearTimeout(timeoutId);
46453
- if (retryCount === 0 || retryCount >= MAX_RETRIES2) {
46454
- setIsLoading(false);
46455
- }
46339
+ setIsLoading(false);
46456
46340
  }
46457
46341
  };
46458
46342
  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]);
46343
+ }, [lineIds, companyId, timezone, skuEnabled]);
46526
46344
  const toggleLineDropdown = useCallback((lineId) => {
46527
46345
  setDropdownStates((prev) => {
46528
46346
  const newIsOpen = !prev[lineId];
@@ -46595,7 +46413,7 @@ var TargetsView = ({
46595
46413
  if (value !== "") {
46596
46414
  const pph = calculatePPH(value, prev[lineId].breaks, shiftHours);
46597
46415
  updates.targetPPH = pph;
46598
- updates.targetDayOutput = calculateDayOutput(pph, shiftHours, prev[lineId].breaks);
46416
+ updates.targetDayOutput = calculateDayOutputFromCycleTime(value, shiftHours);
46599
46417
  } else {
46600
46418
  updates.targetPPH = "";
46601
46419
  updates.targetDayOutput = "";
@@ -46713,7 +46531,8 @@ var TargetsView = ({
46713
46531
  action_id: ws.actionId,
46714
46532
  workspace_id: ws.id,
46715
46533
  date: currentDate,
46716
- pph_threshold: Number(ws.targetPPH) || 0,
46534
+ pph_threshold: ws.targetPPH ? Math.round(Number(ws.targetPPH)) : 0,
46535
+ // Round to whole number
46717
46536
  ideal_cycle_time: Number(ws.targetCycleTime) || 0,
46718
46537
  total_day_output: Number(ws.targetDayOutput) || 0,
46719
46538
  action_name: ws.actionType === "assembly" ? ACTION_NAMES.ASSEMBLY : ACTION_NAMES.PACKAGING,
@@ -46732,7 +46551,8 @@ var TargetsView = ({
46732
46551
  shift_id: selectedShift,
46733
46552
  product_code: lineDataToSave.productId,
46734
46553
  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),
46554
+ threshold_pph: packagingWorkspaces.reduce((acc, ws) => acc + (ws.targetPPH ? Math.round(Number(ws.targetPPH)) : 0), 0),
46555
+ // Round each PPH value
46736
46556
  ...skuEnabled && lineDataToSave.selectedSKU ? { sku_id: lineDataToSave.selectedSKU.id } : {}
46737
46557
  };
46738
46558
  console.log(`[handleSaveLine] lineThresholdData for upsert on ${lineId}:`, lineThresholdData);
@@ -48484,94 +48304,14 @@ var SupervisorManagementView = ({
48484
48304
  ] });
48485
48305
  };
48486
48306
  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;
48307
+ var getRoleLabel = (role) => {
48308
+ const labels = {
48309
+ "supervisor": "Supervisor",
48310
+ "plant_head": "Plant Head",
48311
+ "owner": "Owner",
48312
+ "optifye": "Optifye Admin"
48313
+ };
48314
+ return labels[role] || role;
48575
48315
  };
48576
48316
  var ChangeRoleDialog = ({
48577
48317
  user,
@@ -48584,8 +48324,6 @@ var ChangeRoleDialog = ({
48584
48324
  const [confirmed, setConfirmed] = useState(false);
48585
48325
  const [isChanging, setIsChanging] = useState(false);
48586
48326
  const roleChanged = selectedRole !== user.role_level;
48587
- const changeKey = roleChanged ? getRoleChangeKey(user.role_level, selectedRole) : null;
48588
- const impactInfo = changeKey ? roleChangeImpacts[changeKey] : null;
48589
48327
  const handleNext = () => {
48590
48328
  if (roleChanged) {
48591
48329
  setStep("confirm");
@@ -48605,7 +48343,7 @@ var ChangeRoleDialog = ({
48605
48343
  setStep("select");
48606
48344
  setConfirmed(false);
48607
48345
  };
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: [
48346
+ 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
48347
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
48610
48348
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
48611
48349
  /* @__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 +48353,14 @@ var ChangeRoleDialog = ({
48615
48353
  "button",
48616
48354
  {
48617
48355
  onClick: onClose,
48618
- className: "text-gray-400 hover:text-gray-600 transition-colors",
48356
+ className: "text-gray-400 hover:text-gray-600 transition-colors p-1 hover:bg-gray-100 rounded-lg",
48619
48357
  disabled: isChanging,
48358
+ "aria-label": "Close dialog",
48620
48359
  children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" })
48621
48360
  }
48622
48361
  )
48623
48362
  ] }),
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
- ] }),
48363
+ /* @__PURE__ */ jsx("div", { className: "p-6 space-y-5", children: step === "select" ? /* @__PURE__ */ jsxs(Fragment, { children: [
48632
48364
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48633
48365
  /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "User" }),
48634
48366
  /* @__PURE__ */ jsxs("div", { className: "p-3 bg-gray-50 rounded-lg", children: [
@@ -48639,7 +48371,7 @@ var ChangeRoleDialog = ({
48639
48371
  ] })
48640
48372
  ] })
48641
48373
  ] }),
48642
- /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
48374
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48643
48375
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "Select new role" }),
48644
48376
  /* @__PURE__ */ jsx("div", { className: "space-y-2", children: availableRoles.map((role) => /* @__PURE__ */ jsxs(
48645
48377
  "label",
@@ -48663,27 +48395,24 @@ var ChangeRoleDialog = ({
48663
48395
  role
48664
48396
  )) })
48665
48397
  ] }),
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
- ] })
48398
+ 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: [
48399
+ "Changing from ",
48400
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: getRoleLabel(user.role_level) }),
48401
+ " to ",
48402
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: getRoleLabel(selectedRole) }),
48403
+ " will modify user permissions."
48404
+ ] }) })
48673
48405
  ] }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
48674
48406
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-3 p-4 bg-gray-50 rounded-lg", children: [
48675
48407
  /* @__PURE__ */ jsx(RoleBadge, { role: user.role_level }),
48676
48408
  /* @__PURE__ */ jsx(ChevronRight, { className: "w-5 h-5 text-gray-400" }),
48677
48409
  /* @__PURE__ */ jsx(RoleBadge, { role: selectedRole })
48678
48410
  ] }),
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)) })
48411
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48412
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600", children: "User" }),
48413
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900", children: user.email })
48685
48414
  ] }),
48686
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-4 border border-gray-200 rounded-lg", children: [
48415
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-3 border border-gray-200 rounded-lg bg-gray-50", children: [
48687
48416
  /* @__PURE__ */ jsx(
48688
48417
  "input",
48689
48418
  {
@@ -48694,7 +48423,7 @@ var ChangeRoleDialog = ({
48694
48423
  className: "mt-0.5 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
48695
48424
  }
48696
48425
  ),
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" })
48426
+ /* @__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
48427
  ] })
48699
48428
  ] }) }) }),
48700
48429
  /* @__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 +48466,7 @@ var ChangeRoleDialog = ({
48737
48466
  /* @__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
48467
  ] }),
48739
48468
  "Changing..."
48740
- ] }) : "Change Role"
48469
+ ] }) : "Confirm Change"
48741
48470
  }
48742
48471
  )
48743
48472
  ] }) })
@@ -48760,32 +48489,34 @@ var ConfirmRemoveUserDialog = ({
48760
48489
  setIsRemoving(false);
48761
48490
  }
48762
48491
  };
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: [
48492
+ 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
48493
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
48765
48494
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
48766
48495
  /* @__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" })
48496
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Permanently Delete User" })
48768
48497
  ] }),
48769
48498
  /* @__PURE__ */ jsx(
48770
48499
  "button",
48771
48500
  {
48772
48501
  onClick: onCancel,
48773
- className: "text-gray-400 hover:text-gray-600 transition-colors",
48502
+ className: "text-gray-400 hover:text-gray-600 transition-colors p-1 hover:bg-gray-100 rounded-lg",
48503
+ disabled: isRemoving,
48504
+ "aria-label": "Close dialog",
48774
48505
  children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" })
48775
48506
  }
48776
48507
  )
48777
48508
  ] }),
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." })
48509
+ /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-5", children: [
48510
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-3 bg-red-50 border border-red-200 rounded-lg", children: [
48511
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "w-4 h-4 text-red-600 mt-0.5 flex-shrink-0" }),
48512
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-red-900", children: [
48513
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "This action cannot be undone." }),
48514
+ " This will permanently delete the user and all related data from the system."
48784
48515
  ] })
48785
48516
  ] }),
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: [
48517
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
48518
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "You are about to permanently delete:" }),
48519
+ /* @__PURE__ */ jsxs("div", { className: "p-3 bg-gray-50 rounded-lg space-y-2.5", children: [
48789
48520
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
48790
48521
  /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-600", children: "Email" }),
48791
48522
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-900", children: user.email })
@@ -48810,28 +48541,7 @@ var ConfirmRemoveUserDialog = ({
48810
48541
  ] })
48811
48542
  ] })
48812
48543
  ] }),
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: [
48544
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 p-3 border border-gray-200 rounded-lg bg-gray-50", children: [
48835
48545
  /* @__PURE__ */ jsx(
48836
48546
  "input",
48837
48547
  {
@@ -48842,7 +48552,7 @@ var ConfirmRemoveUserDialog = ({
48842
48552
  className: "mt-0.5 h-4 w-4 text-red-600 border-gray-300 rounded focus:ring-red-500"
48843
48553
  }
48844
48554
  ),
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" })
48555
+ /* @__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
48556
  ] })
48847
48557
  ] }),
48848
48558
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3 p-6 border-t border-gray-200 bg-gray-50", children: [
@@ -48866,8 +48576,8 @@ var ConfirmRemoveUserDialog = ({
48866
48576
  /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4", fill: "none" }),
48867
48577
  /* @__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
48578
  ] }),
48869
- "Removing..."
48870
- ] }) : "Remove User"
48579
+ "Deleting..."
48580
+ ] }) : "Delete User"
48871
48581
  }
48872
48582
  )
48873
48583
  ] })
@@ -49290,7 +49000,6 @@ var FactoryAssignmentDropdown = ({
49290
49000
  var UserManagementTable = ({
49291
49001
  users,
49292
49002
  isLoading = false,
49293
- onUserClick,
49294
49003
  onRoleChange,
49295
49004
  onRemoveUser,
49296
49005
  onLineAssignmentUpdate,
@@ -49316,9 +49025,48 @@ var UserManagementTable = ({
49316
49025
  const [sortField, setSortField] = useState("email");
49317
49026
  const [sortDirection, setSortDirection] = useState("asc");
49318
49027
  const [openActionMenuId, setOpenActionMenuId] = useState(null);
49028
+ const [dropdownPosition, setDropdownPosition] = useState(null);
49029
+ const actionButtonRefs = useRef({});
49319
49030
  const [selectedUser, setSelectedUser] = useState(null);
49320
49031
  const [showChangeRoleDialog, setShowChangeRoleDialog] = useState(false);
49321
49032
  const [showRemoveUserDialog, setShowRemoveUserDialog] = useState(false);
49033
+ const handleOpenActionMenu = (userId) => {
49034
+ const buttonRef = actionButtonRefs.current[userId];
49035
+ if (buttonRef) {
49036
+ const rect = buttonRef.getBoundingClientRect();
49037
+ setDropdownPosition({
49038
+ top: rect.bottom + 8,
49039
+ // 8px below the button
49040
+ left: rect.right - 192
49041
+ // 192px is the dropdown width (w-48 = 12rem = 192px)
49042
+ });
49043
+ setOpenActionMenuId(userId);
49044
+ }
49045
+ };
49046
+ const handleCloseActionMenu = () => {
49047
+ setOpenActionMenuId(null);
49048
+ setDropdownPosition(null);
49049
+ };
49050
+ useEffect(() => {
49051
+ if (openActionMenuId && actionButtonRefs.current[openActionMenuId]) {
49052
+ const updatePosition = () => {
49053
+ const buttonRef = actionButtonRefs.current[openActionMenuId];
49054
+ if (buttonRef) {
49055
+ const rect = buttonRef.getBoundingClientRect();
49056
+ setDropdownPosition({
49057
+ top: rect.bottom + 8,
49058
+ left: rect.right - 192
49059
+ });
49060
+ }
49061
+ };
49062
+ window.addEventListener("scroll", updatePosition, true);
49063
+ window.addEventListener("resize", updatePosition);
49064
+ return () => {
49065
+ window.removeEventListener("scroll", updatePosition, true);
49066
+ window.removeEventListener("resize", updatePosition);
49067
+ };
49068
+ }
49069
+ }, [openActionMenuId]);
49322
49070
  const filteredAndSortedUsers = useMemo(() => {
49323
49071
  let filtered = users;
49324
49072
  filtered = filtered.filter((user) => availableRoles.includes(user.role_level));
@@ -49509,7 +49257,7 @@ var UserManagementTable = ({
49509
49257
  LineAssignmentDropdown,
49510
49258
  {
49511
49259
  userId: user.user_id,
49512
- currentLineIds: user.properties?.line_id || [],
49260
+ currentLineIds: user.properties?.line_ids || [],
49513
49261
  availableLines: (
49514
49262
  // Filter lines to only show those from the target user's company
49515
49263
  (() => {
@@ -49547,69 +49295,24 @@ var UserManagementTable = ({
49547
49295
  }
49548
49296
  ) : /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-900", children: formatAssignments(user) }) }),
49549
49297
  /* @__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)
49298
+ /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right", children: hasActions && /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsx(
49299
+ "button",
49300
+ {
49301
+ ref: (el) => {
49302
+ actionButtonRefs.current[user.user_id] = el;
49303
+ },
49304
+ onClick: () => {
49305
+ if (openActionMenuId === user.user_id) {
49306
+ handleCloseActionMenu();
49307
+ } else {
49308
+ handleOpenActionMenu(user.user_id);
49566
49309
  }
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
- ] }) })
49310
+ },
49311
+ className: "p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
49312
+ "aria-label": "User actions",
49313
+ children: /* @__PURE__ */ jsx(MoreVertical, { className: "w-4 h-4" })
49314
+ }
49315
+ ) }) })
49613
49316
  ]
49614
49317
  },
49615
49318
  user.user_id
@@ -49617,6 +49320,75 @@ var UserManagementTable = ({
49617
49320
  }) })
49618
49321
  ] }) }) })
49619
49322
  ] }),
49323
+ openActionMenuId && dropdownPosition && typeof document !== "undefined" && createPortal(
49324
+ /* @__PURE__ */ jsxs(Fragment, { children: [
49325
+ /* @__PURE__ */ jsx(
49326
+ "div",
49327
+ {
49328
+ className: "fixed inset-0 z-[9998]",
49329
+ onClick: handleCloseActionMenu
49330
+ }
49331
+ ),
49332
+ /* @__PURE__ */ jsx(
49333
+ "div",
49334
+ {
49335
+ className: "fixed w-48 bg-white rounded-lg shadow-lg border border-gray-200 z-[9999]",
49336
+ style: {
49337
+ top: `${dropdownPosition.top}px`,
49338
+ left: `${dropdownPosition.left}px`
49339
+ },
49340
+ children: /* @__PURE__ */ jsxs("div", { className: "py-1", children: [
49341
+ (() => {
49342
+ const user = users.find((u) => u.user_id === openActionMenuId);
49343
+ const isCurrentUser = user?.user_id === currentUserId;
49344
+ const canChangeRole = user && permissions.canChangeRole(user);
49345
+ return canChangeRole && /* @__PURE__ */ jsxs(
49346
+ "button",
49347
+ {
49348
+ onClick: () => {
49349
+ if (user) {
49350
+ handleChangeRole(user);
49351
+ }
49352
+ },
49353
+ 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",
49354
+ disabled: isCurrentUser,
49355
+ children: [
49356
+ /* @__PURE__ */ jsx(UserCog, { className: "w-4 h-4" }),
49357
+ "Change Role"
49358
+ ]
49359
+ }
49360
+ );
49361
+ })(),
49362
+ (() => {
49363
+ const user = users.find((u) => u.user_id === openActionMenuId);
49364
+ const isCurrentUser = user?.user_id === currentUserId;
49365
+ const canRemove = user && permissions.canRemoveUser(user);
49366
+ return canRemove && /* @__PURE__ */ jsxs(Fragment, { children: [
49367
+ /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200 my-1" }),
49368
+ /* @__PURE__ */ jsxs(
49369
+ "button",
49370
+ {
49371
+ onClick: () => {
49372
+ if (user) {
49373
+ handleRemoveUser(user);
49374
+ }
49375
+ },
49376
+ 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",
49377
+ disabled: isCurrentUser,
49378
+ children: [
49379
+ /* @__PURE__ */ jsx(Trash2, { className: "w-4 h-4" }),
49380
+ "Delete User"
49381
+ ]
49382
+ }
49383
+ )
49384
+ ] });
49385
+ })()
49386
+ ] })
49387
+ }
49388
+ )
49389
+ ] }),
49390
+ document.body
49391
+ ),
49620
49392
  showChangeRoleDialog && selectedUser && /* @__PURE__ */ jsx(
49621
49393
  ChangeRoleDialog,
49622
49394
  {
@@ -50268,15 +50040,12 @@ var TeamManagementView = ({
50268
50040
  if (!supabase || !user) return;
50269
50041
  try {
50270
50042
  const userManagementService = createUserManagementService(supabase);
50271
- await userManagementService.deactivateUser({
50272
- user_id: userId,
50273
- deactivated_by: user.id
50274
- });
50275
- toast.success("User removed successfully");
50043
+ const result = await userManagementService.deleteUser(userId);
50044
+ toast.success(`User ${result.deleted_user_email} permanently deleted`);
50276
50045
  loadData();
50277
50046
  } catch (err) {
50278
- console.error("Error removing user:", err);
50279
- toast.error("Failed to remove user");
50047
+ console.error("Error deleting user:", err);
50048
+ toast.error("Failed to delete user");
50280
50049
  }
50281
50050
  }, [supabase, user, loadData]);
50282
50051
  const handleLineAssignmentUpdate = useCallback(async (userId, lineIds) => {
@@ -50370,9 +50139,6 @@ var TeamManagementView = ({
50370
50139
  {
50371
50140
  users,
50372
50141
  currentUserId: user?.id,
50373
- onUserClick: (clickedUser) => {
50374
- console.log("View user details:", clickedUser);
50375
- },
50376
50142
  onRoleChange: handleRoleChange,
50377
50143
  onRemoveUser: handleRemoveUser,
50378
50144
  onLineAssignmentUpdate: handleLineAssignmentUpdate,