@optifye/dashboard-core 6.10.46 → 6.10.47

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
@@ -51425,13 +51425,31 @@ var InviteUserDialog = ({
51425
51425
  const [selectedRole, setSelectedRole] = useState("supervisor");
51426
51426
  const [selectedLines, setSelectedLines] = useState([]);
51427
51427
  const [selectedFactories, setSelectedFactories] = useState([]);
51428
+ const [lineSearch, setLineSearch] = useState("");
51429
+ const [factorySearch, setFactorySearch] = useState("");
51428
51430
  const [isSubmitting, setIsSubmitting] = useState(false);
51429
51431
  const [error, setError] = useState(null);
51430
- const [isLinesDropdownOpen, setIsLinesDropdownOpen] = useState(false);
51431
- const [isFactoriesDropdownOpen, setIsFactoriesDropdownOpen] = useState(false);
51432
51432
  const canInviteIT = user?.role_level === "owner" || user?.role_level === "optifye";
51433
51433
  const canInvitePlantHead = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
51434
51434
  const canInviteSupervisor = ["owner", "it", "plant_head", "optifye"].includes(user?.role_level || "");
51435
+ const filteredLines = useMemo(() => {
51436
+ const search = lineSearch.trim().toLowerCase();
51437
+ if (!search) return availableLines;
51438
+ return availableLines.filter((line) => line.name.toLowerCase().includes(search));
51439
+ }, [availableLines, lineSearch]);
51440
+ const filteredFactories = useMemo(() => {
51441
+ const search = factorySearch.trim().toLowerCase();
51442
+ if (!search) return availableFactories;
51443
+ return availableFactories.filter((factory) => factory.name.toLowerCase().includes(search));
51444
+ }, [availableFactories, factorySearch]);
51445
+ const selectedLineItems = useMemo(
51446
+ () => availableLines.filter((line) => selectedLines.includes(line.id)),
51447
+ [availableLines, selectedLines]
51448
+ );
51449
+ const selectedFactoryItems = useMemo(
51450
+ () => availableFactories.filter((factory) => selectedFactories.includes(factory.id)),
51451
+ [availableFactories, selectedFactories]
51452
+ );
51435
51453
  useEffect(() => {
51436
51454
  if (!isOpen) {
51437
51455
  setEmail("");
@@ -51440,9 +51458,9 @@ var InviteUserDialog = ({
51440
51458
  setSelectedRole("supervisor");
51441
51459
  setSelectedLines([]);
51442
51460
  setSelectedFactories([]);
51461
+ setLineSearch("");
51462
+ setFactorySearch("");
51443
51463
  setError(null);
51444
- setIsLinesDropdownOpen(false);
51445
- setIsFactoriesDropdownOpen(false);
51446
51464
  }
51447
51465
  }, [isOpen]);
51448
51466
  const validateEmail = (email2) => {
@@ -51511,7 +51529,6 @@ var InviteUserDialog = ({
51511
51529
  try {
51512
51530
  const dashboardUrl = typeof window !== "undefined" ? window.location.origin : "";
51513
51531
  if (dashboardUrl) {
51514
- console.log("Sending welcome email to:", email.trim());
51515
51532
  const { data: emailData, error: emailError } = await supabase.functions.invoke("hyper-service", {
51516
51533
  body: {
51517
51534
  action: "send-welcome-email",
@@ -51524,13 +51541,8 @@ var InviteUserDialog = ({
51524
51541
  console.error("Failed to send welcome email:", emailError);
51525
51542
  toast.warning("User added successfully, but welcome email could not be sent");
51526
51543
  } else if (emailData?.success) {
51527
- console.log("Welcome email sent successfully:", emailData);
51528
51544
  toast.success("User added and welcome email sent!");
51529
- } else {
51530
- console.log("Welcome email response:", emailData);
51531
51545
  }
51532
- } else {
51533
- console.warn("Dashboard URL not available, skipping welcome email");
51534
51546
  }
51535
51547
  } catch (emailErr) {
51536
51548
  console.error("Error sending welcome email:", emailErr);
@@ -51556,7 +51568,7 @@ var InviteUserDialog = ({
51556
51568
  children: /* @__PURE__ */ jsxs(
51557
51569
  "div",
51558
51570
  {
51559
- className: "bg-white rounded-xl shadow-2xl max-w-lg w-full max-h-[90vh] overflow-y-auto transform transition-all animate-in fade-in duration-200",
51571
+ className: "bg-white rounded-xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto transform transition-all animate-in fade-in duration-200",
51560
51572
  onClick: (e) => e.stopPropagation(),
51561
51573
  children: [
51562
51574
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
@@ -51584,7 +51596,7 @@ var InviteUserDialog = ({
51584
51596
  /* @__PURE__ */ jsx("span", { children: error })
51585
51597
  ] }),
51586
51598
  /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
51587
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
51599
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [
51588
51600
  /* @__PURE__ */ jsxs("div", { children: [
51589
51601
  /* @__PURE__ */ jsxs("label", { htmlFor: "firstName", className: "block text-sm font-medium text-gray-700 mb-2", children: [
51590
51602
  "First Name ",
@@ -51743,124 +51755,192 @@ var InviteUserDialog = ({
51743
51755
  )
51744
51756
  ] })
51745
51757
  ] }),
51746
- selectedRole === "plant_head" && availableFactories.length > 0 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
51747
- /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: [
51748
- /* @__PURE__ */ jsx(Building2, { className: "w-4 h-4 inline mr-1" }),
51749
- "Assign to Factories"
51758
+ selectedRole === "plant_head" && /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 p-4 space-y-3", children: [
51759
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
51760
+ /* @__PURE__ */ jsxs("div", { children: [
51761
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-gray-800 flex items-center gap-1.5", children: [
51762
+ /* @__PURE__ */ jsx(Building2, { className: "w-4 h-4 text-blue-600" }),
51763
+ "Factory Access"
51764
+ ] }),
51765
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Choose the factories this plant head will manage." })
51766
+ ] }),
51767
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium bg-white border border-gray-200 text-gray-600 rounded-full px-2 py-1", children: [
51768
+ selectedFactories.length,
51769
+ " selected"
51770
+ ] })
51750
51771
  ] }),
51751
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Select one or multiple factories this plant head will manage" }),
51752
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
51753
- /* @__PURE__ */ jsx(
51754
- "button",
51772
+ availableFactories.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed border-gray-300 bg-white px-3 py-4 text-sm text-gray-500", children: "No factories available for assignment." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
51773
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
51774
+ /* @__PURE__ */ jsx(
51775
+ "button",
51776
+ {
51777
+ type: "button",
51778
+ onClick: () => setSelectedFactories(availableFactories.map((factory) => factory.id)),
51779
+ className: "px-2.5 py-1 text-xs font-medium text-blue-700 bg-blue-100 rounded-md hover:bg-blue-200 transition-colors",
51780
+ disabled: isSubmitting,
51781
+ children: "Select all"
51782
+ }
51783
+ ),
51784
+ /* @__PURE__ */ jsx(
51785
+ "button",
51786
+ {
51787
+ type: "button",
51788
+ onClick: () => setSelectedFactories([]),
51789
+ className: "px-2.5 py-1 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors",
51790
+ disabled: isSubmitting || selectedFactories.length === 0,
51791
+ children: "Clear"
51792
+ }
51793
+ )
51794
+ ] }),
51795
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
51796
+ /* @__PURE__ */ jsx(Search, { className: "w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" }),
51797
+ /* @__PURE__ */ jsx(
51798
+ "input",
51799
+ {
51800
+ type: "text",
51801
+ value: factorySearch,
51802
+ onChange: (e) => setFactorySearch(e.target.value),
51803
+ placeholder: "Search factories",
51804
+ className: "w-full pl-9 pr-3 py-2 text-sm border border-gray-300 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
51805
+ disabled: isSubmitting
51806
+ }
51807
+ )
51808
+ ] }),
51809
+ /* @__PURE__ */ jsx("div", { className: "max-h-56 overflow-y-auto rounded-lg border border-gray-200 bg-white divide-y divide-gray-100", children: filteredFactories.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-4 py-6 text-sm text-gray-500 text-center", children: "No factories match your search." }) : filteredFactories.map((factory) => /* @__PURE__ */ jsxs(
51810
+ "label",
51755
51811
  {
51756
- type: "button",
51757
- onClick: () => setIsFactoriesDropdownOpen(!isFactoriesDropdownOpen),
51758
- disabled: isSubmitting,
51759
- className: "w-full min-h-[42px] px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white text-left",
51760
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
51761
- selectedFactories.length === 0 ? /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Select factories..." }) : /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 flex-1", children: selectedFactories.map((factoryId) => {
51762
- const factory = availableFactories.find((f) => f.id === factoryId);
51763
- return factory ? /* @__PURE__ */ jsxs(
51764
- "span",
51812
+ className: "flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer",
51813
+ children: [
51814
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
51815
+ /* @__PURE__ */ jsx(
51816
+ "input",
51765
51817
  {
51766
- className: "inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51767
- children: [
51768
- factory.name,
51769
- /* @__PURE__ */ jsx(
51770
- "button",
51771
- {
51772
- type: "button",
51773
- onClick: (e) => {
51774
- e.stopPropagation();
51775
- toggleFactorySelection(factory.id);
51776
- },
51777
- className: "ml-0.5 hover:bg-blue-200 rounded-sm transition-colors",
51778
- disabled: isSubmitting,
51779
- children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
51780
- }
51781
- )
51782
- ]
51783
- },
51784
- factory.id
51785
- ) : null;
51786
- }) }),
51787
- /* @__PURE__ */ jsx(ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isFactoriesDropdownOpen && "rotate-180") })
51788
- ] })
51789
- }
51790
- ),
51791
- isFactoriesDropdownOpen && /* @__PURE__ */ jsx("div", { className: "absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto", children: availableFactories.map((factory) => /* @__PURE__ */ jsxs(
51792
- "div",
51818
+ type: "checkbox",
51819
+ checked: selectedFactories.includes(factory.id),
51820
+ onChange: () => toggleFactorySelection(factory.id),
51821
+ className: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500",
51822
+ disabled: isSubmitting
51823
+ }
51824
+ ),
51825
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-800 truncate", children: factory.name })
51826
+ ] }),
51827
+ selectedFactories.includes(factory.id) && /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-blue-600 flex-shrink-0" })
51828
+ ]
51829
+ },
51830
+ factory.id
51831
+ )) }),
51832
+ selectedFactoryItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedFactoryItems.map((factory) => /* @__PURE__ */ jsxs(
51833
+ "span",
51793
51834
  {
51794
- onClick: () => toggleFactorySelection(factory.id),
51795
- className: cn(
51796
- "px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
51797
- selectedFactories.includes(factory.id) && "bg-blue-50"
51798
- ),
51835
+ className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51799
51836
  children: [
51800
- /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700", children: factory.name }),
51801
- selectedFactories.includes(factory.id) && /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-blue-600" })
51837
+ factory.name,
51838
+ /* @__PURE__ */ jsx(
51839
+ "button",
51840
+ {
51841
+ type: "button",
51842
+ onClick: () => toggleFactorySelection(factory.id),
51843
+ className: "hover:bg-blue-200 rounded-sm transition-colors",
51844
+ disabled: isSubmitting,
51845
+ children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
51846
+ }
51847
+ )
51802
51848
  ]
51803
51849
  },
51804
51850
  factory.id
51805
51851
  )) })
51806
51852
  ] })
51807
51853
  ] }),
51808
- selectedRole === "supervisor" && availableLines.length > 0 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
51809
- /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: [
51810
- /* @__PURE__ */ jsx(Users, { className: "w-4 h-4 inline mr-1" }),
51811
- "Assign to Lines"
51854
+ selectedRole === "supervisor" && /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 p-4 space-y-3", children: [
51855
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
51856
+ /* @__PURE__ */ jsxs("div", { children: [
51857
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-gray-800 flex items-center gap-1.5", children: [
51858
+ /* @__PURE__ */ jsx(Users, { className: "w-4 h-4 text-blue-600" }),
51859
+ "Line Access"
51860
+ ] }),
51861
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Choose the lines this supervisor will monitor." })
51862
+ ] }),
51863
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium bg-white border border-gray-200 text-gray-600 rounded-full px-2 py-1", children: [
51864
+ selectedLines.length,
51865
+ " selected"
51866
+ ] })
51812
51867
  ] }),
51813
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Select one or multiple lines this supervisor will monitor" }),
51814
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
51815
- /* @__PURE__ */ jsx(
51816
- "button",
51868
+ availableLines.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed border-gray-300 bg-white px-3 py-4 text-sm text-gray-500", children: "No lines available for assignment." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
51869
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
51870
+ /* @__PURE__ */ jsx(
51871
+ "button",
51872
+ {
51873
+ type: "button",
51874
+ onClick: () => setSelectedLines(availableLines.map((line) => line.id)),
51875
+ className: "px-2.5 py-1 text-xs font-medium text-blue-700 bg-blue-100 rounded-md hover:bg-blue-200 transition-colors",
51876
+ disabled: isSubmitting,
51877
+ children: "Select all"
51878
+ }
51879
+ ),
51880
+ /* @__PURE__ */ jsx(
51881
+ "button",
51882
+ {
51883
+ type: "button",
51884
+ onClick: () => setSelectedLines([]),
51885
+ className: "px-2.5 py-1 text-xs font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors",
51886
+ disabled: isSubmitting || selectedLines.length === 0,
51887
+ children: "Clear"
51888
+ }
51889
+ )
51890
+ ] }),
51891
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
51892
+ /* @__PURE__ */ jsx(Search, { className: "w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" }),
51893
+ /* @__PURE__ */ jsx(
51894
+ "input",
51895
+ {
51896
+ type: "text",
51897
+ value: lineSearch,
51898
+ onChange: (e) => setLineSearch(e.target.value),
51899
+ placeholder: "Search lines",
51900
+ className: "w-full pl-9 pr-3 py-2 text-sm border border-gray-300 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
51901
+ disabled: isSubmitting
51902
+ }
51903
+ )
51904
+ ] }),
51905
+ /* @__PURE__ */ jsx("div", { className: "max-h-56 overflow-y-auto rounded-lg border border-gray-200 bg-white divide-y divide-gray-100", children: filteredLines.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-4 py-6 text-sm text-gray-500 text-center", children: "No lines match your search." }) : filteredLines.map((line) => /* @__PURE__ */ jsxs(
51906
+ "label",
51817
51907
  {
51818
- type: "button",
51819
- onClick: () => setIsLinesDropdownOpen(!isLinesDropdownOpen),
51820
- disabled: isSubmitting,
51821
- className: "w-full min-h-[42px] px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white text-left",
51822
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
51823
- selectedLines.length === 0 ? /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Select lines..." }) : /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 flex-1", children: selectedLines.map((lineId) => {
51824
- const line = availableLines.find((l) => l.id === lineId);
51825
- return line ? /* @__PURE__ */ jsxs(
51826
- "span",
51908
+ className: "flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer",
51909
+ children: [
51910
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
51911
+ /* @__PURE__ */ jsx(
51912
+ "input",
51827
51913
  {
51828
- className: "inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51829
- children: [
51830
- line.name,
51831
- /* @__PURE__ */ jsx(
51832
- "button",
51833
- {
51834
- type: "button",
51835
- onClick: (e) => {
51836
- e.stopPropagation();
51837
- toggleLineSelection(line.id);
51838
- },
51839
- className: "ml-0.5 hover:bg-blue-200 rounded-sm transition-colors",
51840
- disabled: isSubmitting,
51841
- children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
51842
- }
51843
- )
51844
- ]
51845
- },
51846
- line.id
51847
- ) : null;
51848
- }) }),
51849
- /* @__PURE__ */ jsx(ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isLinesDropdownOpen && "rotate-180") })
51850
- ] })
51851
- }
51852
- ),
51853
- isLinesDropdownOpen && /* @__PURE__ */ jsx("div", { className: "absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto", children: availableLines.map((line) => /* @__PURE__ */ jsxs(
51854
- "div",
51914
+ type: "checkbox",
51915
+ checked: selectedLines.includes(line.id),
51916
+ onChange: () => toggleLineSelection(line.id),
51917
+ className: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500",
51918
+ disabled: isSubmitting
51919
+ }
51920
+ ),
51921
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-800 truncate", children: line.name })
51922
+ ] }),
51923
+ selectedLines.includes(line.id) && /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-blue-600 flex-shrink-0" })
51924
+ ]
51925
+ },
51926
+ line.id
51927
+ )) }),
51928
+ selectedLineItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedLineItems.map((line) => /* @__PURE__ */ jsxs(
51929
+ "span",
51855
51930
  {
51856
- onClick: () => toggleLineSelection(line.id),
51857
- className: cn(
51858
- "px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
51859
- selectedLines.includes(line.id) && "bg-blue-50"
51860
- ),
51931
+ className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51861
51932
  children: [
51862
- /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700", children: line.name }),
51863
- selectedLines.includes(line.id) && /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-blue-600" })
51933
+ line.name,
51934
+ /* @__PURE__ */ jsx(
51935
+ "button",
51936
+ {
51937
+ type: "button",
51938
+ onClick: () => toggleLineSelection(line.id),
51939
+ className: "hover:bg-blue-200 rounded-sm transition-colors",
51940
+ disabled: isSubmitting,
51941
+ children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
51942
+ }
51943
+ )
51864
51944
  ]
51865
51945
  },
51866
51946
  line.id
@@ -52395,10 +52475,22 @@ var LineAssignmentDropdown = ({
52395
52475
  canEdit,
52396
52476
  onUpdate
52397
52477
  }) => {
52478
+ const VIEWPORT_MARGIN = 8;
52479
+ const DROPDOWN_GAP = 6;
52480
+ const MIN_DROPDOWN_WIDTH = 300;
52481
+ const MAX_DROPDOWN_WIDTH = 420;
52482
+ const PREFERRED_DROPDOWN_HEIGHT = 420;
52483
+ const MIN_DROPDOWN_HEIGHT = 180;
52398
52484
  const [isOpen, setIsOpen] = useState(false);
52399
52485
  const [selectedIds, setSelectedIds] = useState(currentLineIds);
52400
52486
  const [isSaving, setIsSaving] = useState(false);
52401
- const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });
52487
+ const [position, setPosition] = useState({
52488
+ top: 0,
52489
+ left: 0,
52490
+ width: MIN_DROPDOWN_WIDTH,
52491
+ maxHeight: PREFERRED_DROPDOWN_HEIGHT,
52492
+ openAbove: false
52493
+ });
52402
52494
  const buttonRef = useRef(null);
52403
52495
  const dropdownRef = useRef(null);
52404
52496
  useEffect(() => {
@@ -52408,10 +52500,31 @@ var LineAssignmentDropdown = ({
52408
52500
  const updatePosition = () => {
52409
52501
  if (isOpen && buttonRef.current) {
52410
52502
  const rect = buttonRef.current.getBoundingClientRect();
52503
+ const viewportWidth = window.innerWidth;
52504
+ const viewportHeight = window.innerHeight;
52505
+ const width = Math.min(
52506
+ Math.max(rect.width, MIN_DROPDOWN_WIDTH),
52507
+ Math.min(MAX_DROPDOWN_WIDTH, viewportWidth - VIEWPORT_MARGIN * 2)
52508
+ );
52509
+ const left = Math.min(
52510
+ Math.max(VIEWPORT_MARGIN, rect.left),
52511
+ viewportWidth - width - VIEWPORT_MARGIN
52512
+ );
52513
+ const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_MARGIN - DROPDOWN_GAP;
52514
+ const spaceAbove = rect.top - VIEWPORT_MARGIN - DROPDOWN_GAP;
52515
+ const openAbove = spaceBelow < 260 && spaceAbove > spaceBelow;
52516
+ const availableSpace = openAbove ? spaceAbove : spaceBelow;
52517
+ const maxHeight = Math.max(
52518
+ MIN_DROPDOWN_HEIGHT,
52519
+ Math.min(PREFERRED_DROPDOWN_HEIGHT, availableSpace > 0 ? availableSpace : viewportHeight - VIEWPORT_MARGIN * 2)
52520
+ );
52521
+ const top = openAbove ? rect.top - DROPDOWN_GAP : Math.min(rect.bottom + DROPDOWN_GAP, viewportHeight - VIEWPORT_MARGIN - maxHeight);
52411
52522
  setPosition({
52412
- top: rect.bottom,
52413
- left: rect.left,
52414
- width: rect.width
52523
+ top,
52524
+ left,
52525
+ width,
52526
+ maxHeight,
52527
+ openAbove
52415
52528
  });
52416
52529
  }
52417
52530
  };
@@ -52489,7 +52602,7 @@ var LineAssignmentDropdown = ({
52489
52602
  assignedLines.length - 2
52490
52603
  ] });
52491
52604
  };
52492
- const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentLineIds.sort());
52605
+ const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentLineIds].sort());
52493
52606
  if (!canEdit) {
52494
52607
  return /* @__PURE__ */ jsx("div", { className: "text-sm", children: getDisplayText() });
52495
52608
  }
@@ -52519,13 +52632,13 @@ var LineAssignmentDropdown = ({
52519
52632
  "div",
52520
52633
  {
52521
52634
  ref: dropdownRef,
52522
- className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200",
52635
+ className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col overflow-hidden",
52523
52636
  style: {
52524
- top: `${position.top + 4}px`,
52637
+ top: `${position.top}px`,
52525
52638
  left: `${position.left}px`,
52526
- minWidth: `${Math.max(position.width, 300)}px`,
52527
- maxWidth: "400px",
52528
- maxHeight: "calc(100vh - 100px)"
52639
+ width: `${position.width}px`,
52640
+ maxHeight: `${position.maxHeight}px`,
52641
+ transform: position.openAbove ? "translateY(-100%)" : void 0
52529
52642
  },
52530
52643
  children: [
52531
52644
  /* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b border-gray-200 bg-gray-50", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
@@ -52542,7 +52655,7 @@ var LineAssignmentDropdown = ({
52542
52655
  }
52543
52656
  )
52544
52657
  ] }) }),
52545
- /* @__PURE__ */ jsx("div", { className: "max-h-80 overflow-y-auto", children: availableLines.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "No lines available" }) }) : /* @__PURE__ */ jsx("div", { className: "py-1", children: availableLines.map((line) => /* @__PURE__ */ jsxs(
52658
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: availableLines.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "No lines available" }) }) : /* @__PURE__ */ jsx("div", { className: "py-1", children: availableLines.map((line) => /* @__PURE__ */ jsxs(
52546
52659
  "label",
52547
52660
  {
52548
52661
  className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
@@ -52602,10 +52715,22 @@ var FactoryAssignmentDropdown = ({
52602
52715
  canEdit,
52603
52716
  onUpdate
52604
52717
  }) => {
52718
+ const VIEWPORT_MARGIN = 8;
52719
+ const DROPDOWN_GAP = 6;
52720
+ const MIN_DROPDOWN_WIDTH = 300;
52721
+ const MAX_DROPDOWN_WIDTH = 420;
52722
+ const PREFERRED_DROPDOWN_HEIGHT = 420;
52723
+ const MIN_DROPDOWN_HEIGHT = 180;
52605
52724
  const [isOpen, setIsOpen] = useState(false);
52606
52725
  const [selectedIds, setSelectedIds] = useState(currentFactoryIds);
52607
52726
  const [isSaving, setIsSaving] = useState(false);
52608
- const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });
52727
+ const [position, setPosition] = useState({
52728
+ top: 0,
52729
+ left: 0,
52730
+ width: MIN_DROPDOWN_WIDTH,
52731
+ maxHeight: PREFERRED_DROPDOWN_HEIGHT,
52732
+ openAbove: false
52733
+ });
52609
52734
  const buttonRef = useRef(null);
52610
52735
  const dropdownRef = useRef(null);
52611
52736
  useEffect(() => {
@@ -52615,10 +52740,31 @@ var FactoryAssignmentDropdown = ({
52615
52740
  const updatePosition = () => {
52616
52741
  if (isOpen && buttonRef.current) {
52617
52742
  const rect = buttonRef.current.getBoundingClientRect();
52743
+ const viewportWidth = window.innerWidth;
52744
+ const viewportHeight = window.innerHeight;
52745
+ const width = Math.min(
52746
+ Math.max(rect.width, MIN_DROPDOWN_WIDTH),
52747
+ Math.min(MAX_DROPDOWN_WIDTH, viewportWidth - VIEWPORT_MARGIN * 2)
52748
+ );
52749
+ const left = Math.min(
52750
+ Math.max(VIEWPORT_MARGIN, rect.left),
52751
+ viewportWidth - width - VIEWPORT_MARGIN
52752
+ );
52753
+ const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_MARGIN - DROPDOWN_GAP;
52754
+ const spaceAbove = rect.top - VIEWPORT_MARGIN - DROPDOWN_GAP;
52755
+ const openAbove = spaceBelow < 260 && spaceAbove > spaceBelow;
52756
+ const availableSpace = openAbove ? spaceAbove : spaceBelow;
52757
+ const maxHeight = Math.max(
52758
+ MIN_DROPDOWN_HEIGHT,
52759
+ Math.min(PREFERRED_DROPDOWN_HEIGHT, availableSpace > 0 ? availableSpace : viewportHeight - VIEWPORT_MARGIN * 2)
52760
+ );
52761
+ const top = openAbove ? rect.top - DROPDOWN_GAP : Math.min(rect.bottom + DROPDOWN_GAP, viewportHeight - VIEWPORT_MARGIN - maxHeight);
52618
52762
  setPosition({
52619
- top: rect.bottom,
52620
- left: rect.left,
52621
- width: rect.width
52763
+ top,
52764
+ left,
52765
+ width,
52766
+ maxHeight,
52767
+ openAbove
52622
52768
  });
52623
52769
  }
52624
52770
  };
@@ -52696,7 +52842,7 @@ var FactoryAssignmentDropdown = ({
52696
52842
  assignedFactories.length - 2
52697
52843
  ] });
52698
52844
  };
52699
- const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentFactoryIds.sort());
52845
+ const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentFactoryIds].sort());
52700
52846
  if (!canEdit) {
52701
52847
  return /* @__PURE__ */ jsx("div", { className: "text-sm", children: getDisplayText() });
52702
52848
  }
@@ -52726,13 +52872,13 @@ var FactoryAssignmentDropdown = ({
52726
52872
  "div",
52727
52873
  {
52728
52874
  ref: dropdownRef,
52729
- className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200",
52875
+ className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col overflow-hidden",
52730
52876
  style: {
52731
- top: `${position.top + 4}px`,
52877
+ top: `${position.top}px`,
52732
52878
  left: `${position.left}px`,
52733
- minWidth: `${Math.max(position.width, 300)}px`,
52734
- maxWidth: "400px",
52735
- maxHeight: "calc(100vh - 100px)"
52879
+ width: `${position.width}px`,
52880
+ maxHeight: `${position.maxHeight}px`,
52881
+ transform: position.openAbove ? "translateY(-100%)" : void 0
52736
52882
  },
52737
52883
  children: [
52738
52884
  /* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b border-gray-200 bg-gray-50", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
@@ -52749,7 +52895,7 @@ var FactoryAssignmentDropdown = ({
52749
52895
  }
52750
52896
  )
52751
52897
  ] }) }),
52752
- /* @__PURE__ */ jsx("div", { className: "max-h-80 overflow-y-auto", children: availableFactories.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "No factories available" }) }) : /* @__PURE__ */ jsx("div", { className: "py-1", children: availableFactories.map((factory) => /* @__PURE__ */ jsxs(
52898
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: availableFactories.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "No factories available" }) }) : /* @__PURE__ */ jsx("div", { className: "py-1", children: availableFactories.map((factory) => /* @__PURE__ */ jsxs(
52753
52899
  "label",
52754
52900
  {
52755
52901
  className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
@@ -60882,6 +61028,20 @@ var ProfileView = () => {
60882
61028
  ] });
60883
61029
  };
60884
61030
  var ProfileView_default = ProfileView;
61031
+
61032
+ // src/lib/constants/actions.ts
61033
+ var ACTION_NAMES = {
61034
+ /** Assembly operations */
61035
+ ASSEMBLY: "Assembly",
61036
+ /** Packaging operations */
61037
+ PACKAGING: "Packaging",
61038
+ /** Inspection operations */
61039
+ INSPECTION: "Inspection",
61040
+ /** Testing operations */
61041
+ TESTING: "Testing",
61042
+ /** Quality control operations */
61043
+ QUALITY_CONTROL: "Quality Control"
61044
+ };
60885
61045
  var calculateShiftHours = (startTime, endTime, breaks = []) => {
60886
61046
  if (!startTime || !endTime) return 8;
60887
61047
  const [startHour, startMinute] = startTime.split(":").map(Number);
@@ -60895,6 +61055,43 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
60895
61055
  const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
60896
61056
  return Number(hoursDiff.toFixed(1));
60897
61057
  };
61058
+ var calculateBreakDuration2 = (startTime, endTime) => {
61059
+ const [startHour, startMinute] = startTime.split(":").map(Number);
61060
+ const [endHour, endMinute] = endTime.split(":").map(Number);
61061
+ let startMinutes = startHour * 60 + startMinute;
61062
+ let endMinutes = endHour * 60 + endMinute;
61063
+ if (endMinutes < startMinutes) {
61064
+ endMinutes += 24 * 60;
61065
+ }
61066
+ return endMinutes - startMinutes;
61067
+ };
61068
+ var parseBreaksFromDB = (dbBreaks) => {
61069
+ if (!dbBreaks) return [];
61070
+ if (Array.isArray(dbBreaks)) {
61071
+ return dbBreaks.map((breakItem) => ({
61072
+ startTime: breakItem.start || breakItem.startTime || "00:00",
61073
+ endTime: breakItem.end || breakItem.endTime || "00:00",
61074
+ duration: calculateBreakDuration2(
61075
+ breakItem.start || breakItem.startTime || "00:00",
61076
+ breakItem.end || breakItem.endTime || "00:00"
61077
+ ),
61078
+ remarks: breakItem.remarks || breakItem.name || ""
61079
+ }));
61080
+ } else if (dbBreaks.breaks && Array.isArray(dbBreaks.breaks)) {
61081
+ return dbBreaks.breaks.map((breakItem) => ({
61082
+ startTime: breakItem.start || breakItem.startTime || "00:00",
61083
+ endTime: breakItem.end || breakItem.endTime || "00:00",
61084
+ duration: calculateBreakDuration2(
61085
+ breakItem.start || breakItem.startTime || "00:00",
61086
+ breakItem.end || breakItem.endTime || "00:00"
61087
+ ),
61088
+ remarks: breakItem.remarks || breakItem.name || ""
61089
+ }));
61090
+ } else {
61091
+ console.warn("Unexpected breaks format:", dbBreaks);
61092
+ return [];
61093
+ }
61094
+ };
60898
61095
  var getStoredLineState = (lineId) => {
60899
61096
  try {
60900
61097
  return JSON.parse(localStorage.getItem(`line_${lineId}_open`) || "false");
@@ -60911,6 +61108,7 @@ var formatBreaks = (breaks) => {
60911
61108
  }))
60912
61109
  };
60913
61110
  };
61111
+ var SHIFT_HOURS_EPSILON = 1e-3;
60914
61112
  var BreakRow = memo$1(({
60915
61113
  break: breakItem,
60916
61114
  onUpdate,
@@ -61206,16 +61404,22 @@ var ShiftsView = ({
61206
61404
  );
61207
61405
  const [loading, setLoading] = useState(true);
61208
61406
  const [error, setError] = useState(null);
61407
+ const [saveStatusMessages, setSaveStatusMessages] = useState(
61408
+ () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: null }), {})
61409
+ );
61209
61410
  const showToast = useCallback((type, message) => {
61210
61411
  if (onToast) {
61211
- onToast(type, message);
61212
- } else {
61213
- if (type === "success") {
61214
- toast.success(message);
61215
- } else {
61216
- toast.error(message);
61412
+ try {
61413
+ onToast(type, message);
61414
+ } catch (error2) {
61415
+ console.warn("[ShiftsView] onToast callback failed, falling back to sonner toast:", error2);
61217
61416
  }
61218
61417
  }
61418
+ if (type === "success") {
61419
+ toast.success(message);
61420
+ } else {
61421
+ toast.error(message);
61422
+ }
61219
61423
  }, [onToast]);
61220
61424
  useEffect(() => {
61221
61425
  const fetchShiftConfigs = async () => {
@@ -61366,15 +61570,196 @@ var ShiftsView = ({
61366
61570
  return config;
61367
61571
  }));
61368
61572
  }, []);
61573
+ const getOperationalDateForLine = useCallback((lineConfig) => {
61574
+ const sortedShifts = [...lineConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
61575
+ const dayBoundaryStart = sortedShifts[0]?.startTime || "06:00";
61576
+ return getOperationalDate(lineConfig.timezone || "UTC", /* @__PURE__ */ new Date(), dayBoundaryStart);
61577
+ }, []);
61578
+ const recalculateTargetsForShiftHourChanges = useCallback(
61579
+ async ({
61580
+ lineId,
61581
+ lineConfig,
61582
+ previousShiftHours,
61583
+ updatedBy
61584
+ }) => {
61585
+ const primaryOperationalDate = getOperationalDate(lineConfig.timezone || "UTC");
61586
+ const alternateOperationalDate = getOperationalDateForLine(lineConfig);
61587
+ const candidateOperationalDates = primaryOperationalDate === alternateOperationalDate ? [primaryOperationalDate] : [primaryOperationalDate, alternateOperationalDate];
61588
+ let recalculatedCount = 0;
61589
+ const { data: lineRow, error: lineRowError } = await supabase.from("lines").select("factory_id").eq("id", lineId).maybeSingle();
61590
+ if (lineRowError) {
61591
+ throw new Error(`Failed to resolve line factory for target recalculation: ${lineRowError.message}`);
61592
+ }
61593
+ const lineFactoryId = lineRow?.factory_id || null;
61594
+ for (const shift of lineConfig.shifts || []) {
61595
+ const oldShiftHours = previousShiftHours[shift.shiftId];
61596
+ const newShiftHours = calculateShiftHours(shift.startTime, shift.endTime, shift.breaks || []);
61597
+ if (oldShiftHours === void 0) continue;
61598
+ if (!Number.isFinite(newShiftHours) || newShiftHours <= 0) {
61599
+ console.warn(
61600
+ `[ShiftsView] Skipping target recalculation for line ${lineId}, shift ${shift.shiftId} due to invalid new shift hours: ${newShiftHours}`
61601
+ );
61602
+ continue;
61603
+ }
61604
+ if (Math.abs(newShiftHours - oldShiftHours) <= SHIFT_HOURS_EPSILON) continue;
61605
+ let thresholdDateForShift = candidateOperationalDates[0];
61606
+ let currentThresholds = [];
61607
+ for (const candidateDate of candidateOperationalDates) {
61608
+ const { data: thresholdRows, error: thresholdsFetchError } = await supabase.from("action_thresholds").select("line_id, shift_id, action_id, workspace_id, date, pph_threshold, ideal_cycle_time, total_day_output, action_name, sku_id").eq("line_id", lineId).eq("date", candidateDate).eq("shift_id", shift.shiftId);
61609
+ if (thresholdsFetchError) {
61610
+ throw new Error(
61611
+ `Failed to fetch action thresholds for line ${lineId}, shift ${shift.shiftId}: ${thresholdsFetchError.message}`
61612
+ );
61613
+ }
61614
+ if ((thresholdRows || []).length > 0) {
61615
+ thresholdDateForShift = candidateDate;
61616
+ currentThresholds = thresholdRows || [];
61617
+ break;
61618
+ }
61619
+ }
61620
+ if (currentThresholds.length === 0) {
61621
+ continue;
61622
+ }
61623
+ const actionIds = Array.from(
61624
+ new Set(currentThresholds.map((threshold) => threshold.action_id).filter(Boolean))
61625
+ );
61626
+ const actionNameById = /* @__PURE__ */ new Map();
61627
+ if (actionIds.length > 0) {
61628
+ const { data: actionRows, error: actionsError } = await supabase.from("actions").select("id, action_name").in("id", actionIds);
61629
+ if (actionsError) {
61630
+ console.warn(
61631
+ `[ShiftsView] Failed to resolve action names for line ${lineId}, shift ${shift.shiftId}: ${actionsError.message}`
61632
+ );
61633
+ } else {
61634
+ (actionRows || []).forEach((actionRow) => {
61635
+ if (actionRow.id && actionRow.action_name) {
61636
+ actionNameById.set(actionRow.id, actionRow.action_name);
61637
+ }
61638
+ });
61639
+ }
61640
+ }
61641
+ const expandedHours = newShiftHours > oldShiftHours;
61642
+ const recalculatedThresholds = currentThresholds.map((threshold) => {
61643
+ const existingPPH = Number(threshold.pph_threshold) || 0;
61644
+ const existingDayOutput = Number(threshold.total_day_output) || 0;
61645
+ const baselineDayOutput = existingDayOutput > 0 ? existingDayOutput : Math.round(existingPPH * Math.max(oldShiftHours, 0));
61646
+ let nextPPH = existingPPH;
61647
+ let nextDayOutput = existingDayOutput;
61648
+ if (expandedHours) {
61649
+ const pphToKeep = existingPPH > 0 ? existingPPH : oldShiftHours > 0 ? Math.round(baselineDayOutput / oldShiftHours) : 0;
61650
+ nextPPH = pphToKeep;
61651
+ nextDayOutput = Math.round(pphToKeep * newShiftHours);
61652
+ } else {
61653
+ const dayOutputToKeep = baselineDayOutput;
61654
+ nextDayOutput = dayOutputToKeep;
61655
+ nextPPH = newShiftHours > 0 ? Math.round(dayOutputToKeep / newShiftHours) : 0;
61656
+ }
61657
+ const resolvedActionName = (typeof threshold.action_name === "string" && threshold.action_name.trim().length > 0 ? threshold.action_name : actionNameById.get(threshold.action_id)) || ACTION_NAMES.ASSEMBLY;
61658
+ return {
61659
+ line_id: threshold.line_id || lineId,
61660
+ shift_id: shift.shiftId,
61661
+ action_id: threshold.action_id,
61662
+ workspace_id: threshold.workspace_id,
61663
+ date: threshold.date || thresholdDateForShift,
61664
+ pph_threshold: Math.max(0, Math.round(Number.isFinite(nextPPH) ? nextPPH : 0)),
61665
+ ideal_cycle_time: Number(threshold.ideal_cycle_time) || 0,
61666
+ total_day_output: Math.max(0, Math.round(Number.isFinite(nextDayOutput) ? nextDayOutput : 0)),
61667
+ action_name: resolvedActionName,
61668
+ updated_by: updatedBy,
61669
+ ...threshold.sku_id ? { sku_id: threshold.sku_id } : {}
61670
+ };
61671
+ });
61672
+ const { error: thresholdsUpsertError } = await supabase.from("action_thresholds").upsert(recalculatedThresholds);
61673
+ if (thresholdsUpsertError) {
61674
+ throw new Error(
61675
+ `Failed to update action thresholds for line ${lineId}, shift ${shift.shiftId}: ${thresholdsUpsertError.message}`
61676
+ );
61677
+ }
61678
+ const packagingActionIds = new Set(
61679
+ Array.from(actionNameById.entries()).filter(([, actionName]) => actionName.toLowerCase() === ACTION_NAMES.PACKAGING.toLowerCase()).map(([actionId]) => actionId)
61680
+ );
61681
+ const packagingThresholds = recalculatedThresholds.filter((threshold) => {
61682
+ if (packagingActionIds.has(threshold.action_id)) return true;
61683
+ return typeof threshold.action_name === "string" && threshold.action_name.toLowerCase() === ACTION_NAMES.PACKAGING.toLowerCase();
61684
+ });
61685
+ const thresholdDayOutput = packagingThresholds.reduce(
61686
+ (sum, threshold) => sum + (Number(threshold.total_day_output) || 0),
61687
+ 0
61688
+ );
61689
+ const thresholdPPH = packagingThresholds.reduce(
61690
+ (sum, threshold) => sum + (Number(threshold.pph_threshold) || 0),
61691
+ 0
61692
+ );
61693
+ const { data: existingLineThreshold, error: existingLineThresholdError } = await supabase.from("line_thresholds").select("factory_id, product_code, sku_id").eq("line_id", lineId).eq("date", thresholdDateForShift).eq("shift_id", shift.shiftId).maybeSingle();
61694
+ if (existingLineThresholdError) {
61695
+ console.warn(
61696
+ `[ShiftsView] Failed to read existing line threshold for line ${lineId}, shift ${shift.shiftId}: ${existingLineThresholdError.message}`
61697
+ );
61698
+ }
61699
+ const factoryId = existingLineThreshold?.factory_id || lineFactoryId;
61700
+ if (factoryId) {
61701
+ const lineThresholdPayload = {
61702
+ factory_id: factoryId,
61703
+ line_id: lineId,
61704
+ date: thresholdDateForShift,
61705
+ shift_id: shift.shiftId,
61706
+ product_code: existingLineThreshold?.product_code || "",
61707
+ threshold_day_output: thresholdDayOutput,
61708
+ threshold_pph: thresholdPPH
61709
+ };
61710
+ if (existingLineThreshold?.sku_id) {
61711
+ lineThresholdPayload.sku_id = existingLineThreshold.sku_id;
61712
+ }
61713
+ const { error: lineThresholdUpsertError } = await supabase.from("line_thresholds").upsert(lineThresholdPayload, { onConflict: "factory_id,line_id,date,shift_id" });
61714
+ if (lineThresholdUpsertError) {
61715
+ throw new Error(
61716
+ `Failed to update line thresholds for line ${lineId}, shift ${shift.shiftId}: ${lineThresholdUpsertError.message}`
61717
+ );
61718
+ }
61719
+ } else {
61720
+ console.warn(
61721
+ `[ShiftsView] Missing factory_id while updating line thresholds for line ${lineId}, shift ${shift.shiftId}`
61722
+ );
61723
+ }
61724
+ recalculatedCount += recalculatedThresholds.length;
61725
+ }
61726
+ return recalculatedCount;
61727
+ },
61728
+ [getOperationalDateForLine, supabase]
61729
+ );
61369
61730
  const handleSaveShifts = useCallback(async (lineId) => {
61731
+ const userId = "6bf6f271-1e55-4a95-9b89-1c3820b58739";
61370
61732
  setLineConfigs((prev) => prev.map(
61371
61733
  (config) => config.id === lineId ? { ...config, isSaving: true, saveSuccess: false } : config
61372
61734
  ));
61735
+ setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
61373
61736
  try {
61374
61737
  const lineConfig = lineConfigs.find((config) => config.id === lineId);
61375
61738
  if (!lineConfig) {
61376
61739
  throw new Error("Line configuration not found");
61377
61740
  }
61741
+ const { data: existingRows, error: existingRowsError } = await supabase.from("line_operating_hours").select("shift_id, start_time, end_time, breaks").eq("line_id", lineId);
61742
+ if (existingRowsError) {
61743
+ throw new Error(`Failed to read existing shift timings: ${existingRowsError.message}`);
61744
+ }
61745
+ const previousShiftHours = (existingRows || []).reduce(
61746
+ (acc, row) => {
61747
+ acc[row.shift_id] = calculateShiftHours(
61748
+ row.start_time,
61749
+ row.end_time,
61750
+ parseBreaksFromDB(row.breaks)
61751
+ );
61752
+ return acc;
61753
+ },
61754
+ {}
61755
+ );
61756
+ const changedShiftCount = (lineConfig.shifts || []).reduce((count, shift) => {
61757
+ const oldShiftHours = previousShiftHours[shift.shiftId];
61758
+ if (oldShiftHours === void 0) return count;
61759
+ const newShiftHours = calculateShiftHours(shift.startTime, shift.endTime, shift.breaks || []);
61760
+ if (!Number.isFinite(newShiftHours)) return count;
61761
+ return Math.abs(newShiftHours - oldShiftHours) > SHIFT_HOURS_EPSILON ? count + 1 : count;
61762
+ }, 0);
61378
61763
  const allSavedRows = [];
61379
61764
  for (const shift of lineConfig.shifts || []) {
61380
61765
  const shiftData = {
@@ -61396,23 +61781,65 @@ var ShiftsView = ({
61396
61781
  if (allSavedRows.length > 0) {
61397
61782
  shiftConfigStore.setFromOperatingHoursRows(lineId, allSavedRows, shiftConfigStore.get(lineId));
61398
61783
  }
61784
+ let recalculatedTargetsCount = 0;
61785
+ try {
61786
+ recalculatedTargetsCount = await recalculateTargetsForShiftHourChanges({
61787
+ lineId,
61788
+ lineConfig,
61789
+ previousShiftHours,
61790
+ updatedBy: userId
61791
+ });
61792
+ } catch (recalcError) {
61793
+ console.error("[ShiftsView] Shift timings were saved but target recalculation failed:", recalcError);
61794
+ showToast("error", "Shift timings saved, but target recalculation failed. Please review targets.");
61795
+ setSaveStatusMessages((prev) => ({
61796
+ ...prev,
61797
+ [lineId]: {
61798
+ message: "Shift timings saved, but target recalculation failed. Please review targets.",
61799
+ tone: "error"
61800
+ }
61801
+ }));
61802
+ }
61399
61803
  setLineConfigs((prev) => prev.map(
61400
61804
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
61401
61805
  ));
61402
- showToast("success", "Shift configurations saved successfully");
61806
+ let successMessage = "Shift configurations saved successfully.";
61807
+ if (changedShiftCount > 0 && recalculatedTargetsCount > 0) {
61808
+ successMessage = `Shift configurations saved. Targets updated successfully for ${recalculatedTargetsCount} workspace${recalculatedTargetsCount === 1 ? "" : "s"}.`;
61809
+ } else if (changedShiftCount > 0) {
61810
+ successMessage = "Shift configurations saved. Target recalculation completed successfully.";
61811
+ }
61812
+ showToast("success", successMessage);
61813
+ setSaveStatusMessages((prev) => ({
61814
+ ...prev,
61815
+ [lineId]: {
61816
+ message: successMessage,
61817
+ tone: "success"
61818
+ }
61819
+ }));
61403
61820
  setTimeout(() => {
61404
61821
  setLineConfigs((prev) => prev.map(
61405
61822
  (config) => config.id === lineId ? { ...config, saveSuccess: false } : config
61406
61823
  ));
61407
61824
  }, 3e3);
61825
+ setTimeout(() => {
61826
+ setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
61827
+ }, 7e3);
61408
61828
  } catch (error2) {
61409
61829
  console.error("Error saving shift configurations:", error2);
61410
61830
  showToast("error", "Failed to save shift configurations");
61831
+ setSaveStatusMessages((prev) => ({
61832
+ ...prev,
61833
+ [lineId]: {
61834
+ message: "Failed to save shift configurations",
61835
+ tone: "error"
61836
+ }
61837
+ }));
61411
61838
  setLineConfigs((prev) => prev.map(
61412
61839
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: false } : config
61413
61840
  ));
61414
61841
  }
61415
- }, [lineConfigs, supabase, showToast]);
61842
+ }, [lineConfigs, recalculateTargetsForShiftHourChanges, supabase, showToast]);
61416
61843
  return /* @__PURE__ */ jsxs("div", { className: `min-h-screen bg-slate-50 ${className}`, children: [
61417
61844
  /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200/80 shadow-sm", children: /* @__PURE__ */ jsxs("div", { className: "px-3 sm:px-4 md:px-6 lg:px-8 py-3 sm:py-4", children: [
61418
61845
  /* @__PURE__ */ jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
@@ -61467,7 +61894,14 @@ var ShiftsView = ({
61467
61894
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center gap-2 sm:gap-4 w-full sm:w-auto", children: [
61468
61895
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
61469
61896
  config.isSaving && /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-sm font-medium text-blue-600", children: "Saving..." }),
61470
- config.saveSuccess && /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-sm font-medium text-green-600", children: "Saved!" })
61897
+ config.saveSuccess && /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-sm font-medium text-green-600", children: "Saved!" }),
61898
+ saveStatusMessages[config.id]?.message && /* @__PURE__ */ jsx(
61899
+ "span",
61900
+ {
61901
+ className: `text-xs sm:text-sm font-medium ${saveStatusMessages[config.id]?.tone === "error" ? "text-red-600" : saveStatusMessages[config.id]?.tone === "info" ? "text-blue-600" : "text-green-600"}`,
61902
+ children: saveStatusMessages[config.id]?.message
61903
+ }
61904
+ )
61471
61905
  ] }),
61472
61906
  /* @__PURE__ */ jsxs(
61473
61907
  "button",
@@ -61524,20 +61958,6 @@ var ShiftsView = ({
61524
61958
  var AuthenticatedShiftsView = withAuth(React26__default.memo(ShiftsView));
61525
61959
  var ShiftsView_default = ShiftsView;
61526
61960
 
61527
- // src/lib/constants/actions.ts
61528
- var ACTION_NAMES = {
61529
- /** Assembly operations */
61530
- ASSEMBLY: "Assembly",
61531
- /** Packaging operations */
61532
- PACKAGING: "Packaging",
61533
- /** Inspection operations */
61534
- INSPECTION: "Inspection",
61535
- /** Testing operations */
61536
- TESTING: "Testing",
61537
- /** Quality control operations */
61538
- QUALITY_CONTROL: "Quality Control"
61539
- };
61540
-
61541
61961
  // src/views/TargetsView.utils.ts
61542
61962
  var calculatePPH = (cycleTime, breaks = [], shiftHours = 0) => {
61543
61963
  if (cycleTime === "" || cycleTime === 0) return "";
@@ -65954,7 +66374,7 @@ var TeamManagementView = ({
65954
66374
  optifye: 0
65955
66375
  });
65956
66376
  const [isAddUserDialogOpen, setIsAddUserDialogOpen] = useState(false);
65957
- const canAddUsers = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
66377
+ const canAddUsers = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "plant_head" || user?.role_level === "optifye";
65958
66378
  const canViewUsageStats = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
65959
66379
  const companyIdForUsage = entityConfig?.companyId || user?.properties?.company_id;
65960
66380
  const usageDateRange = useMemo(() => {