@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.js CHANGED
@@ -51454,13 +51454,31 @@ var InviteUserDialog = ({
51454
51454
  const [selectedRole, setSelectedRole] = React26.useState("supervisor");
51455
51455
  const [selectedLines, setSelectedLines] = React26.useState([]);
51456
51456
  const [selectedFactories, setSelectedFactories] = React26.useState([]);
51457
+ const [lineSearch, setLineSearch] = React26.useState("");
51458
+ const [factorySearch, setFactorySearch] = React26.useState("");
51457
51459
  const [isSubmitting, setIsSubmitting] = React26.useState(false);
51458
51460
  const [error, setError] = React26.useState(null);
51459
- const [isLinesDropdownOpen, setIsLinesDropdownOpen] = React26.useState(false);
51460
- const [isFactoriesDropdownOpen, setIsFactoriesDropdownOpen] = React26.useState(false);
51461
51461
  const canInviteIT = user?.role_level === "owner" || user?.role_level === "optifye";
51462
51462
  const canInvitePlantHead = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
51463
51463
  const canInviteSupervisor = ["owner", "it", "plant_head", "optifye"].includes(user?.role_level || "");
51464
+ const filteredLines = React26.useMemo(() => {
51465
+ const search = lineSearch.trim().toLowerCase();
51466
+ if (!search) return availableLines;
51467
+ return availableLines.filter((line) => line.name.toLowerCase().includes(search));
51468
+ }, [availableLines, lineSearch]);
51469
+ const filteredFactories = React26.useMemo(() => {
51470
+ const search = factorySearch.trim().toLowerCase();
51471
+ if (!search) return availableFactories;
51472
+ return availableFactories.filter((factory) => factory.name.toLowerCase().includes(search));
51473
+ }, [availableFactories, factorySearch]);
51474
+ const selectedLineItems = React26.useMemo(
51475
+ () => availableLines.filter((line) => selectedLines.includes(line.id)),
51476
+ [availableLines, selectedLines]
51477
+ );
51478
+ const selectedFactoryItems = React26.useMemo(
51479
+ () => availableFactories.filter((factory) => selectedFactories.includes(factory.id)),
51480
+ [availableFactories, selectedFactories]
51481
+ );
51464
51482
  React26.useEffect(() => {
51465
51483
  if (!isOpen) {
51466
51484
  setEmail("");
@@ -51469,9 +51487,9 @@ var InviteUserDialog = ({
51469
51487
  setSelectedRole("supervisor");
51470
51488
  setSelectedLines([]);
51471
51489
  setSelectedFactories([]);
51490
+ setLineSearch("");
51491
+ setFactorySearch("");
51472
51492
  setError(null);
51473
- setIsLinesDropdownOpen(false);
51474
- setIsFactoriesDropdownOpen(false);
51475
51493
  }
51476
51494
  }, [isOpen]);
51477
51495
  const validateEmail = (email2) => {
@@ -51540,7 +51558,6 @@ var InviteUserDialog = ({
51540
51558
  try {
51541
51559
  const dashboardUrl = typeof window !== "undefined" ? window.location.origin : "";
51542
51560
  if (dashboardUrl) {
51543
- console.log("Sending welcome email to:", email.trim());
51544
51561
  const { data: emailData, error: emailError } = await supabase.functions.invoke("hyper-service", {
51545
51562
  body: {
51546
51563
  action: "send-welcome-email",
@@ -51553,13 +51570,8 @@ var InviteUserDialog = ({
51553
51570
  console.error("Failed to send welcome email:", emailError);
51554
51571
  sonner.toast.warning("User added successfully, but welcome email could not be sent");
51555
51572
  } else if (emailData?.success) {
51556
- console.log("Welcome email sent successfully:", emailData);
51557
51573
  sonner.toast.success("User added and welcome email sent!");
51558
- } else {
51559
- console.log("Welcome email response:", emailData);
51560
51574
  }
51561
- } else {
51562
- console.warn("Dashboard URL not available, skipping welcome email");
51563
51575
  }
51564
51576
  } catch (emailErr) {
51565
51577
  console.error("Error sending welcome email:", emailErr);
@@ -51585,7 +51597,7 @@ var InviteUserDialog = ({
51585
51597
  children: /* @__PURE__ */ jsxRuntime.jsxs(
51586
51598
  "div",
51587
51599
  {
51588
- 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",
51600
+ 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",
51589
51601
  onClick: (e) => e.stopPropagation(),
51590
51602
  children: [
51591
51603
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
@@ -51613,7 +51625,7 @@ var InviteUserDialog = ({
51613
51625
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
51614
51626
  ] }),
51615
51627
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-5", children: [
51616
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
51628
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [
51617
51629
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
51618
51630
  /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: "firstName", className: "block text-sm font-medium text-gray-700 mb-2", children: [
51619
51631
  "First Name ",
@@ -51772,124 +51784,192 @@ var InviteUserDialog = ({
51772
51784
  )
51773
51785
  ] })
51774
51786
  ] }),
51775
- selectedRole === "plant_head" && availableFactories.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51776
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: [
51777
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, { className: "w-4 h-4 inline mr-1" }),
51778
- "Assign to Factories"
51787
+ selectedRole === "plant_head" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 p-4 space-y-3", children: [
51788
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
51789
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
51790
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium text-gray-800 flex items-center gap-1.5", children: [
51791
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, { className: "w-4 h-4 text-blue-600" }),
51792
+ "Factory Access"
51793
+ ] }),
51794
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Choose the factories this plant head will manage." })
51795
+ ] }),
51796
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium bg-white border border-gray-200 text-gray-600 rounded-full px-2 py-1", children: [
51797
+ selectedFactories.length,
51798
+ " selected"
51799
+ ] })
51779
51800
  ] }),
51780
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Select one or multiple factories this plant head will manage" }),
51781
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51782
- /* @__PURE__ */ jsxRuntime.jsx(
51783
- "button",
51801
+ availableFactories.length === 0 ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
51802
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
51803
+ /* @__PURE__ */ jsxRuntime.jsx(
51804
+ "button",
51805
+ {
51806
+ type: "button",
51807
+ onClick: () => setSelectedFactories(availableFactories.map((factory) => factory.id)),
51808
+ className: "px-2.5 py-1 text-xs font-medium text-blue-700 bg-blue-100 rounded-md hover:bg-blue-200 transition-colors",
51809
+ disabled: isSubmitting,
51810
+ children: "Select all"
51811
+ }
51812
+ ),
51813
+ /* @__PURE__ */ jsxRuntime.jsx(
51814
+ "button",
51815
+ {
51816
+ type: "button",
51817
+ onClick: () => setSelectedFactories([]),
51818
+ 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",
51819
+ disabled: isSubmitting || selectedFactories.length === 0,
51820
+ children: "Clear"
51821
+ }
51822
+ )
51823
+ ] }),
51824
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51825
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" }),
51826
+ /* @__PURE__ */ jsxRuntime.jsx(
51827
+ "input",
51828
+ {
51829
+ type: "text",
51830
+ value: factorySearch,
51831
+ onChange: (e) => setFactorySearch(e.target.value),
51832
+ placeholder: "Search factories",
51833
+ 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",
51834
+ disabled: isSubmitting
51835
+ }
51836
+ )
51837
+ ] }),
51838
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("p", { className: "px-4 py-6 text-sm text-gray-500 text-center", children: "No factories match your search." }) : filteredFactories.map((factory) => /* @__PURE__ */ jsxRuntime.jsxs(
51839
+ "label",
51784
51840
  {
51785
- type: "button",
51786
- onClick: () => setIsFactoriesDropdownOpen(!isFactoriesDropdownOpen),
51787
- disabled: isSubmitting,
51788
- 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",
51789
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
51790
- selectedFactories.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: "Select factories..." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 flex-1", children: selectedFactories.map((factoryId) => {
51791
- const factory = availableFactories.find((f) => f.id === factoryId);
51792
- return factory ? /* @__PURE__ */ jsxRuntime.jsxs(
51793
- "span",
51841
+ className: "flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer",
51842
+ children: [
51843
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
51844
+ /* @__PURE__ */ jsxRuntime.jsx(
51845
+ "input",
51794
51846
  {
51795
- className: "inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51796
- children: [
51797
- factory.name,
51798
- /* @__PURE__ */ jsxRuntime.jsx(
51799
- "button",
51800
- {
51801
- type: "button",
51802
- onClick: (e) => {
51803
- e.stopPropagation();
51804
- toggleFactorySelection(factory.id);
51805
- },
51806
- className: "ml-0.5 hover:bg-blue-200 rounded-sm transition-colors",
51807
- disabled: isSubmitting,
51808
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
51809
- }
51810
- )
51811
- ]
51812
- },
51813
- factory.id
51814
- ) : null;
51815
- }) }),
51816
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isFactoriesDropdownOpen && "rotate-180") })
51817
- ] })
51818
- }
51819
- ),
51820
- isFactoriesDropdownOpen && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs(
51821
- "div",
51847
+ type: "checkbox",
51848
+ checked: selectedFactories.includes(factory.id),
51849
+ onChange: () => toggleFactorySelection(factory.id),
51850
+ className: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500",
51851
+ disabled: isSubmitting
51852
+ }
51853
+ ),
51854
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-800 truncate", children: factory.name })
51855
+ ] }),
51856
+ selectedFactories.includes(factory.id) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-blue-600 flex-shrink-0" })
51857
+ ]
51858
+ },
51859
+ factory.id
51860
+ )) }),
51861
+ selectedFactoryItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedFactoryItems.map((factory) => /* @__PURE__ */ jsxRuntime.jsxs(
51862
+ "span",
51822
51863
  {
51823
- onClick: () => toggleFactorySelection(factory.id),
51824
- className: cn(
51825
- "px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
51826
- selectedFactories.includes(factory.id) && "bg-blue-50"
51827
- ),
51864
+ className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51828
51865
  children: [
51829
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-700", children: factory.name }),
51830
- selectedFactories.includes(factory.id) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-blue-600" })
51866
+ factory.name,
51867
+ /* @__PURE__ */ jsxRuntime.jsx(
51868
+ "button",
51869
+ {
51870
+ type: "button",
51871
+ onClick: () => toggleFactorySelection(factory.id),
51872
+ className: "hover:bg-blue-200 rounded-sm transition-colors",
51873
+ disabled: isSubmitting,
51874
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
51875
+ }
51876
+ )
51831
51877
  ]
51832
51878
  },
51833
51879
  factory.id
51834
51880
  )) })
51835
51881
  ] })
51836
51882
  ] }),
51837
- selectedRole === "supervisor" && availableLines.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51838
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: [
51839
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { className: "w-4 h-4 inline mr-1" }),
51840
- "Assign to Lines"
51883
+ selectedRole === "supervisor" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-slate-200 bg-slate-50 p-4 space-y-3", children: [
51884
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
51885
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
51886
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium text-gray-800 flex items-center gap-1.5", children: [
51887
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { className: "w-4 h-4 text-blue-600" }),
51888
+ "Line Access"
51889
+ ] }),
51890
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Choose the lines this supervisor will monitor." })
51891
+ ] }),
51892
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium bg-white border border-gray-200 text-gray-600 rounded-full px-2 py-1", children: [
51893
+ selectedLines.length,
51894
+ " selected"
51895
+ ] })
51841
51896
  ] }),
51842
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Select one or multiple lines this supervisor will monitor" }),
51843
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51844
- /* @__PURE__ */ jsxRuntime.jsx(
51845
- "button",
51897
+ availableLines.length === 0 ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
51898
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
51899
+ /* @__PURE__ */ jsxRuntime.jsx(
51900
+ "button",
51901
+ {
51902
+ type: "button",
51903
+ onClick: () => setSelectedLines(availableLines.map((line) => line.id)),
51904
+ className: "px-2.5 py-1 text-xs font-medium text-blue-700 bg-blue-100 rounded-md hover:bg-blue-200 transition-colors",
51905
+ disabled: isSubmitting,
51906
+ children: "Select all"
51907
+ }
51908
+ ),
51909
+ /* @__PURE__ */ jsxRuntime.jsx(
51910
+ "button",
51911
+ {
51912
+ type: "button",
51913
+ onClick: () => setSelectedLines([]),
51914
+ 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",
51915
+ disabled: isSubmitting || selectedLines.length === 0,
51916
+ children: "Clear"
51917
+ }
51918
+ )
51919
+ ] }),
51920
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51921
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" }),
51922
+ /* @__PURE__ */ jsxRuntime.jsx(
51923
+ "input",
51924
+ {
51925
+ type: "text",
51926
+ value: lineSearch,
51927
+ onChange: (e) => setLineSearch(e.target.value),
51928
+ placeholder: "Search lines",
51929
+ 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",
51930
+ disabled: isSubmitting
51931
+ }
51932
+ )
51933
+ ] }),
51934
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("p", { className: "px-4 py-6 text-sm text-gray-500 text-center", children: "No lines match your search." }) : filteredLines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(
51935
+ "label",
51846
51936
  {
51847
- type: "button",
51848
- onClick: () => setIsLinesDropdownOpen(!isLinesDropdownOpen),
51849
- disabled: isSubmitting,
51850
- 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",
51851
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
51852
- selectedLines.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: "Select lines..." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 flex-1", children: selectedLines.map((lineId) => {
51853
- const line = availableLines.find((l) => l.id === lineId);
51854
- return line ? /* @__PURE__ */ jsxRuntime.jsxs(
51855
- "span",
51937
+ className: "flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer",
51938
+ children: [
51939
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
51940
+ /* @__PURE__ */ jsxRuntime.jsx(
51941
+ "input",
51856
51942
  {
51857
- className: "inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51858
- children: [
51859
- line.name,
51860
- /* @__PURE__ */ jsxRuntime.jsx(
51861
- "button",
51862
- {
51863
- type: "button",
51864
- onClick: (e) => {
51865
- e.stopPropagation();
51866
- toggleLineSelection(line.id);
51867
- },
51868
- className: "ml-0.5 hover:bg-blue-200 rounded-sm transition-colors",
51869
- disabled: isSubmitting,
51870
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
51871
- }
51872
- )
51873
- ]
51874
- },
51875
- line.id
51876
- ) : null;
51877
- }) }),
51878
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isLinesDropdownOpen && "rotate-180") })
51879
- ] })
51880
- }
51881
- ),
51882
- isLinesDropdownOpen && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs(
51883
- "div",
51943
+ type: "checkbox",
51944
+ checked: selectedLines.includes(line.id),
51945
+ onChange: () => toggleLineSelection(line.id),
51946
+ className: "h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500",
51947
+ disabled: isSubmitting
51948
+ }
51949
+ ),
51950
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-800 truncate", children: line.name })
51951
+ ] }),
51952
+ selectedLines.includes(line.id) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-blue-600 flex-shrink-0" })
51953
+ ]
51954
+ },
51955
+ line.id
51956
+ )) }),
51957
+ selectedLineItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", children: selectedLineItems.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(
51958
+ "span",
51884
51959
  {
51885
- onClick: () => toggleLineSelection(line.id),
51886
- className: cn(
51887
- "px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
51888
- selectedLines.includes(line.id) && "bg-blue-50"
51889
- ),
51960
+ className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51890
51961
  children: [
51891
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-700", children: line.name }),
51892
- selectedLines.includes(line.id) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-4 h-4 text-blue-600" })
51962
+ line.name,
51963
+ /* @__PURE__ */ jsxRuntime.jsx(
51964
+ "button",
51965
+ {
51966
+ type: "button",
51967
+ onClick: () => toggleLineSelection(line.id),
51968
+ className: "hover:bg-blue-200 rounded-sm transition-colors",
51969
+ disabled: isSubmitting,
51970
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
51971
+ }
51972
+ )
51893
51973
  ]
51894
51974
  },
51895
51975
  line.id
@@ -52424,10 +52504,22 @@ var LineAssignmentDropdown = ({
52424
52504
  canEdit,
52425
52505
  onUpdate
52426
52506
  }) => {
52507
+ const VIEWPORT_MARGIN = 8;
52508
+ const DROPDOWN_GAP = 6;
52509
+ const MIN_DROPDOWN_WIDTH = 300;
52510
+ const MAX_DROPDOWN_WIDTH = 420;
52511
+ const PREFERRED_DROPDOWN_HEIGHT = 420;
52512
+ const MIN_DROPDOWN_HEIGHT = 180;
52427
52513
  const [isOpen, setIsOpen] = React26.useState(false);
52428
52514
  const [selectedIds, setSelectedIds] = React26.useState(currentLineIds);
52429
52515
  const [isSaving, setIsSaving] = React26.useState(false);
52430
- const [position, setPosition] = React26.useState({ top: 0, left: 0, width: 0 });
52516
+ const [position, setPosition] = React26.useState({
52517
+ top: 0,
52518
+ left: 0,
52519
+ width: MIN_DROPDOWN_WIDTH,
52520
+ maxHeight: PREFERRED_DROPDOWN_HEIGHT,
52521
+ openAbove: false
52522
+ });
52431
52523
  const buttonRef = React26.useRef(null);
52432
52524
  const dropdownRef = React26.useRef(null);
52433
52525
  React26.useEffect(() => {
@@ -52437,10 +52529,31 @@ var LineAssignmentDropdown = ({
52437
52529
  const updatePosition = () => {
52438
52530
  if (isOpen && buttonRef.current) {
52439
52531
  const rect = buttonRef.current.getBoundingClientRect();
52532
+ const viewportWidth = window.innerWidth;
52533
+ const viewportHeight = window.innerHeight;
52534
+ const width = Math.min(
52535
+ Math.max(rect.width, MIN_DROPDOWN_WIDTH),
52536
+ Math.min(MAX_DROPDOWN_WIDTH, viewportWidth - VIEWPORT_MARGIN * 2)
52537
+ );
52538
+ const left = Math.min(
52539
+ Math.max(VIEWPORT_MARGIN, rect.left),
52540
+ viewportWidth - width - VIEWPORT_MARGIN
52541
+ );
52542
+ const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_MARGIN - DROPDOWN_GAP;
52543
+ const spaceAbove = rect.top - VIEWPORT_MARGIN - DROPDOWN_GAP;
52544
+ const openAbove = spaceBelow < 260 && spaceAbove > spaceBelow;
52545
+ const availableSpace = openAbove ? spaceAbove : spaceBelow;
52546
+ const maxHeight = Math.max(
52547
+ MIN_DROPDOWN_HEIGHT,
52548
+ Math.min(PREFERRED_DROPDOWN_HEIGHT, availableSpace > 0 ? availableSpace : viewportHeight - VIEWPORT_MARGIN * 2)
52549
+ );
52550
+ const top = openAbove ? rect.top - DROPDOWN_GAP : Math.min(rect.bottom + DROPDOWN_GAP, viewportHeight - VIEWPORT_MARGIN - maxHeight);
52440
52551
  setPosition({
52441
- top: rect.bottom,
52442
- left: rect.left,
52443
- width: rect.width
52552
+ top,
52553
+ left,
52554
+ width,
52555
+ maxHeight,
52556
+ openAbove
52444
52557
  });
52445
52558
  }
52446
52559
  };
@@ -52518,7 +52631,7 @@ var LineAssignmentDropdown = ({
52518
52631
  assignedLines.length - 2
52519
52632
  ] });
52520
52633
  };
52521
- const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentLineIds.sort());
52634
+ const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentLineIds].sort());
52522
52635
  if (!canEdit) {
52523
52636
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm", children: getDisplayText() });
52524
52637
  }
@@ -52548,13 +52661,13 @@ var LineAssignmentDropdown = ({
52548
52661
  "div",
52549
52662
  {
52550
52663
  ref: dropdownRef,
52551
- className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200",
52664
+ className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col overflow-hidden",
52552
52665
  style: {
52553
- top: `${position.top + 4}px`,
52666
+ top: `${position.top}px`,
52554
52667
  left: `${position.left}px`,
52555
- minWidth: `${Math.max(position.width, 300)}px`,
52556
- maxWidth: "400px",
52557
- maxHeight: "calc(100vh - 100px)"
52668
+ width: `${position.width}px`,
52669
+ maxHeight: `${position.maxHeight}px`,
52670
+ transform: position.openAbove ? "translateY(-100%)" : void 0
52558
52671
  },
52559
52672
  children: [
52560
52673
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 border-b border-gray-200 bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
@@ -52571,7 +52684,7 @@ var LineAssignmentDropdown = ({
52571
52684
  }
52572
52685
  )
52573
52686
  ] }) }),
52574
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-80 overflow-y-auto", children: availableLines.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "No lines available" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: availableLines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(
52687
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: availableLines.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "No lines available" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: availableLines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs(
52575
52688
  "label",
52576
52689
  {
52577
52690
  className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
@@ -52631,10 +52744,22 @@ var FactoryAssignmentDropdown = ({
52631
52744
  canEdit,
52632
52745
  onUpdate
52633
52746
  }) => {
52747
+ const VIEWPORT_MARGIN = 8;
52748
+ const DROPDOWN_GAP = 6;
52749
+ const MIN_DROPDOWN_WIDTH = 300;
52750
+ const MAX_DROPDOWN_WIDTH = 420;
52751
+ const PREFERRED_DROPDOWN_HEIGHT = 420;
52752
+ const MIN_DROPDOWN_HEIGHT = 180;
52634
52753
  const [isOpen, setIsOpen] = React26.useState(false);
52635
52754
  const [selectedIds, setSelectedIds] = React26.useState(currentFactoryIds);
52636
52755
  const [isSaving, setIsSaving] = React26.useState(false);
52637
- const [position, setPosition] = React26.useState({ top: 0, left: 0, width: 0 });
52756
+ const [position, setPosition] = React26.useState({
52757
+ top: 0,
52758
+ left: 0,
52759
+ width: MIN_DROPDOWN_WIDTH,
52760
+ maxHeight: PREFERRED_DROPDOWN_HEIGHT,
52761
+ openAbove: false
52762
+ });
52638
52763
  const buttonRef = React26.useRef(null);
52639
52764
  const dropdownRef = React26.useRef(null);
52640
52765
  React26.useEffect(() => {
@@ -52644,10 +52769,31 @@ var FactoryAssignmentDropdown = ({
52644
52769
  const updatePosition = () => {
52645
52770
  if (isOpen && buttonRef.current) {
52646
52771
  const rect = buttonRef.current.getBoundingClientRect();
52772
+ const viewportWidth = window.innerWidth;
52773
+ const viewportHeight = window.innerHeight;
52774
+ const width = Math.min(
52775
+ Math.max(rect.width, MIN_DROPDOWN_WIDTH),
52776
+ Math.min(MAX_DROPDOWN_WIDTH, viewportWidth - VIEWPORT_MARGIN * 2)
52777
+ );
52778
+ const left = Math.min(
52779
+ Math.max(VIEWPORT_MARGIN, rect.left),
52780
+ viewportWidth - width - VIEWPORT_MARGIN
52781
+ );
52782
+ const spaceBelow = viewportHeight - rect.bottom - VIEWPORT_MARGIN - DROPDOWN_GAP;
52783
+ const spaceAbove = rect.top - VIEWPORT_MARGIN - DROPDOWN_GAP;
52784
+ const openAbove = spaceBelow < 260 && spaceAbove > spaceBelow;
52785
+ const availableSpace = openAbove ? spaceAbove : spaceBelow;
52786
+ const maxHeight = Math.max(
52787
+ MIN_DROPDOWN_HEIGHT,
52788
+ Math.min(PREFERRED_DROPDOWN_HEIGHT, availableSpace > 0 ? availableSpace : viewportHeight - VIEWPORT_MARGIN * 2)
52789
+ );
52790
+ const top = openAbove ? rect.top - DROPDOWN_GAP : Math.min(rect.bottom + DROPDOWN_GAP, viewportHeight - VIEWPORT_MARGIN - maxHeight);
52647
52791
  setPosition({
52648
- top: rect.bottom,
52649
- left: rect.left,
52650
- width: rect.width
52792
+ top,
52793
+ left,
52794
+ width,
52795
+ maxHeight,
52796
+ openAbove
52651
52797
  });
52652
52798
  }
52653
52799
  };
@@ -52725,7 +52871,7 @@ var FactoryAssignmentDropdown = ({
52725
52871
  assignedFactories.length - 2
52726
52872
  ] });
52727
52873
  };
52728
- const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentFactoryIds.sort());
52874
+ const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentFactoryIds].sort());
52729
52875
  if (!canEdit) {
52730
52876
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm", children: getDisplayText() });
52731
52877
  }
@@ -52755,13 +52901,13 @@ var FactoryAssignmentDropdown = ({
52755
52901
  "div",
52756
52902
  {
52757
52903
  ref: dropdownRef,
52758
- className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200",
52904
+ className: "fixed z-[9999] bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col overflow-hidden",
52759
52905
  style: {
52760
- top: `${position.top + 4}px`,
52906
+ top: `${position.top}px`,
52761
52907
  left: `${position.left}px`,
52762
- minWidth: `${Math.max(position.width, 300)}px`,
52763
- maxWidth: "400px",
52764
- maxHeight: "calc(100vh - 100px)"
52908
+ width: `${position.width}px`,
52909
+ maxHeight: `${position.maxHeight}px`,
52910
+ transform: position.openAbove ? "translateY(-100%)" : void 0
52765
52911
  },
52766
52912
  children: [
52767
52913
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 border-b border-gray-200 bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
@@ -52778,7 +52924,7 @@ var FactoryAssignmentDropdown = ({
52778
52924
  }
52779
52925
  )
52780
52926
  ] }) }),
52781
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-80 overflow-y-auto", children: availableFactories.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "No factories available" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: availableFactories.map((factory) => /* @__PURE__ */ jsxRuntime.jsxs(
52927
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: availableFactories.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "No factories available" }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: availableFactories.map((factory) => /* @__PURE__ */ jsxRuntime.jsxs(
52782
52928
  "label",
52783
52929
  {
52784
52930
  className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
@@ -60911,6 +61057,20 @@ var ProfileView = () => {
60911
61057
  ] });
60912
61058
  };
60913
61059
  var ProfileView_default = ProfileView;
61060
+
61061
+ // src/lib/constants/actions.ts
61062
+ var ACTION_NAMES = {
61063
+ /** Assembly operations */
61064
+ ASSEMBLY: "Assembly",
61065
+ /** Packaging operations */
61066
+ PACKAGING: "Packaging",
61067
+ /** Inspection operations */
61068
+ INSPECTION: "Inspection",
61069
+ /** Testing operations */
61070
+ TESTING: "Testing",
61071
+ /** Quality control operations */
61072
+ QUALITY_CONTROL: "Quality Control"
61073
+ };
60914
61074
  var calculateShiftHours = (startTime, endTime, breaks = []) => {
60915
61075
  if (!startTime || !endTime) return 8;
60916
61076
  const [startHour, startMinute] = startTime.split(":").map(Number);
@@ -60924,6 +61084,43 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
60924
61084
  const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
60925
61085
  return Number(hoursDiff.toFixed(1));
60926
61086
  };
61087
+ var calculateBreakDuration2 = (startTime, endTime) => {
61088
+ const [startHour, startMinute] = startTime.split(":").map(Number);
61089
+ const [endHour, endMinute] = endTime.split(":").map(Number);
61090
+ let startMinutes = startHour * 60 + startMinute;
61091
+ let endMinutes = endHour * 60 + endMinute;
61092
+ if (endMinutes < startMinutes) {
61093
+ endMinutes += 24 * 60;
61094
+ }
61095
+ return endMinutes - startMinutes;
61096
+ };
61097
+ var parseBreaksFromDB = (dbBreaks) => {
61098
+ if (!dbBreaks) return [];
61099
+ if (Array.isArray(dbBreaks)) {
61100
+ return dbBreaks.map((breakItem) => ({
61101
+ startTime: breakItem.start || breakItem.startTime || "00:00",
61102
+ endTime: breakItem.end || breakItem.endTime || "00:00",
61103
+ duration: calculateBreakDuration2(
61104
+ breakItem.start || breakItem.startTime || "00:00",
61105
+ breakItem.end || breakItem.endTime || "00:00"
61106
+ ),
61107
+ remarks: breakItem.remarks || breakItem.name || ""
61108
+ }));
61109
+ } else if (dbBreaks.breaks && Array.isArray(dbBreaks.breaks)) {
61110
+ return dbBreaks.breaks.map((breakItem) => ({
61111
+ startTime: breakItem.start || breakItem.startTime || "00:00",
61112
+ endTime: breakItem.end || breakItem.endTime || "00:00",
61113
+ duration: calculateBreakDuration2(
61114
+ breakItem.start || breakItem.startTime || "00:00",
61115
+ breakItem.end || breakItem.endTime || "00:00"
61116
+ ),
61117
+ remarks: breakItem.remarks || breakItem.name || ""
61118
+ }));
61119
+ } else {
61120
+ console.warn("Unexpected breaks format:", dbBreaks);
61121
+ return [];
61122
+ }
61123
+ };
60927
61124
  var getStoredLineState = (lineId) => {
60928
61125
  try {
60929
61126
  return JSON.parse(localStorage.getItem(`line_${lineId}_open`) || "false");
@@ -60940,6 +61137,7 @@ var formatBreaks = (breaks) => {
60940
61137
  }))
60941
61138
  };
60942
61139
  };
61140
+ var SHIFT_HOURS_EPSILON = 1e-3;
60943
61141
  var BreakRow = React26.memo(({
60944
61142
  break: breakItem,
60945
61143
  onUpdate,
@@ -61235,16 +61433,22 @@ var ShiftsView = ({
61235
61433
  );
61236
61434
  const [loading, setLoading] = React26.useState(true);
61237
61435
  const [error, setError] = React26.useState(null);
61436
+ const [saveStatusMessages, setSaveStatusMessages] = React26.useState(
61437
+ () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: null }), {})
61438
+ );
61238
61439
  const showToast = React26.useCallback((type, message) => {
61239
61440
  if (onToast) {
61240
- onToast(type, message);
61241
- } else {
61242
- if (type === "success") {
61243
- sonner.toast.success(message);
61244
- } else {
61245
- sonner.toast.error(message);
61441
+ try {
61442
+ onToast(type, message);
61443
+ } catch (error2) {
61444
+ console.warn("[ShiftsView] onToast callback failed, falling back to sonner toast:", error2);
61246
61445
  }
61247
61446
  }
61447
+ if (type === "success") {
61448
+ sonner.toast.success(message);
61449
+ } else {
61450
+ sonner.toast.error(message);
61451
+ }
61248
61452
  }, [onToast]);
61249
61453
  React26.useEffect(() => {
61250
61454
  const fetchShiftConfigs = async () => {
@@ -61395,15 +61599,196 @@ var ShiftsView = ({
61395
61599
  return config;
61396
61600
  }));
61397
61601
  }, []);
61602
+ const getOperationalDateForLine = React26.useCallback((lineConfig) => {
61603
+ const sortedShifts = [...lineConfig.shifts || []].sort((a, b) => a.shiftId - b.shiftId);
61604
+ const dayBoundaryStart = sortedShifts[0]?.startTime || "06:00";
61605
+ return getOperationalDate(lineConfig.timezone || "UTC", /* @__PURE__ */ new Date(), dayBoundaryStart);
61606
+ }, []);
61607
+ const recalculateTargetsForShiftHourChanges = React26.useCallback(
61608
+ async ({
61609
+ lineId,
61610
+ lineConfig,
61611
+ previousShiftHours,
61612
+ updatedBy
61613
+ }) => {
61614
+ const primaryOperationalDate = getOperationalDate(lineConfig.timezone || "UTC");
61615
+ const alternateOperationalDate = getOperationalDateForLine(lineConfig);
61616
+ const candidateOperationalDates = primaryOperationalDate === alternateOperationalDate ? [primaryOperationalDate] : [primaryOperationalDate, alternateOperationalDate];
61617
+ let recalculatedCount = 0;
61618
+ const { data: lineRow, error: lineRowError } = await supabase.from("lines").select("factory_id").eq("id", lineId).maybeSingle();
61619
+ if (lineRowError) {
61620
+ throw new Error(`Failed to resolve line factory for target recalculation: ${lineRowError.message}`);
61621
+ }
61622
+ const lineFactoryId = lineRow?.factory_id || null;
61623
+ for (const shift of lineConfig.shifts || []) {
61624
+ const oldShiftHours = previousShiftHours[shift.shiftId];
61625
+ const newShiftHours = calculateShiftHours(shift.startTime, shift.endTime, shift.breaks || []);
61626
+ if (oldShiftHours === void 0) continue;
61627
+ if (!Number.isFinite(newShiftHours) || newShiftHours <= 0) {
61628
+ console.warn(
61629
+ `[ShiftsView] Skipping target recalculation for line ${lineId}, shift ${shift.shiftId} due to invalid new shift hours: ${newShiftHours}`
61630
+ );
61631
+ continue;
61632
+ }
61633
+ if (Math.abs(newShiftHours - oldShiftHours) <= SHIFT_HOURS_EPSILON) continue;
61634
+ let thresholdDateForShift = candidateOperationalDates[0];
61635
+ let currentThresholds = [];
61636
+ for (const candidateDate of candidateOperationalDates) {
61637
+ 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);
61638
+ if (thresholdsFetchError) {
61639
+ throw new Error(
61640
+ `Failed to fetch action thresholds for line ${lineId}, shift ${shift.shiftId}: ${thresholdsFetchError.message}`
61641
+ );
61642
+ }
61643
+ if ((thresholdRows || []).length > 0) {
61644
+ thresholdDateForShift = candidateDate;
61645
+ currentThresholds = thresholdRows || [];
61646
+ break;
61647
+ }
61648
+ }
61649
+ if (currentThresholds.length === 0) {
61650
+ continue;
61651
+ }
61652
+ const actionIds = Array.from(
61653
+ new Set(currentThresholds.map((threshold) => threshold.action_id).filter(Boolean))
61654
+ );
61655
+ const actionNameById = /* @__PURE__ */ new Map();
61656
+ if (actionIds.length > 0) {
61657
+ const { data: actionRows, error: actionsError } = await supabase.from("actions").select("id, action_name").in("id", actionIds);
61658
+ if (actionsError) {
61659
+ console.warn(
61660
+ `[ShiftsView] Failed to resolve action names for line ${lineId}, shift ${shift.shiftId}: ${actionsError.message}`
61661
+ );
61662
+ } else {
61663
+ (actionRows || []).forEach((actionRow) => {
61664
+ if (actionRow.id && actionRow.action_name) {
61665
+ actionNameById.set(actionRow.id, actionRow.action_name);
61666
+ }
61667
+ });
61668
+ }
61669
+ }
61670
+ const expandedHours = newShiftHours > oldShiftHours;
61671
+ const recalculatedThresholds = currentThresholds.map((threshold) => {
61672
+ const existingPPH = Number(threshold.pph_threshold) || 0;
61673
+ const existingDayOutput = Number(threshold.total_day_output) || 0;
61674
+ const baselineDayOutput = existingDayOutput > 0 ? existingDayOutput : Math.round(existingPPH * Math.max(oldShiftHours, 0));
61675
+ let nextPPH = existingPPH;
61676
+ let nextDayOutput = existingDayOutput;
61677
+ if (expandedHours) {
61678
+ const pphToKeep = existingPPH > 0 ? existingPPH : oldShiftHours > 0 ? Math.round(baselineDayOutput / oldShiftHours) : 0;
61679
+ nextPPH = pphToKeep;
61680
+ nextDayOutput = Math.round(pphToKeep * newShiftHours);
61681
+ } else {
61682
+ const dayOutputToKeep = baselineDayOutput;
61683
+ nextDayOutput = dayOutputToKeep;
61684
+ nextPPH = newShiftHours > 0 ? Math.round(dayOutputToKeep / newShiftHours) : 0;
61685
+ }
61686
+ const resolvedActionName = (typeof threshold.action_name === "string" && threshold.action_name.trim().length > 0 ? threshold.action_name : actionNameById.get(threshold.action_id)) || ACTION_NAMES.ASSEMBLY;
61687
+ return {
61688
+ line_id: threshold.line_id || lineId,
61689
+ shift_id: shift.shiftId,
61690
+ action_id: threshold.action_id,
61691
+ workspace_id: threshold.workspace_id,
61692
+ date: threshold.date || thresholdDateForShift,
61693
+ pph_threshold: Math.max(0, Math.round(Number.isFinite(nextPPH) ? nextPPH : 0)),
61694
+ ideal_cycle_time: Number(threshold.ideal_cycle_time) || 0,
61695
+ total_day_output: Math.max(0, Math.round(Number.isFinite(nextDayOutput) ? nextDayOutput : 0)),
61696
+ action_name: resolvedActionName,
61697
+ updated_by: updatedBy,
61698
+ ...threshold.sku_id ? { sku_id: threshold.sku_id } : {}
61699
+ };
61700
+ });
61701
+ const { error: thresholdsUpsertError } = await supabase.from("action_thresholds").upsert(recalculatedThresholds);
61702
+ if (thresholdsUpsertError) {
61703
+ throw new Error(
61704
+ `Failed to update action thresholds for line ${lineId}, shift ${shift.shiftId}: ${thresholdsUpsertError.message}`
61705
+ );
61706
+ }
61707
+ const packagingActionIds = new Set(
61708
+ Array.from(actionNameById.entries()).filter(([, actionName]) => actionName.toLowerCase() === ACTION_NAMES.PACKAGING.toLowerCase()).map(([actionId]) => actionId)
61709
+ );
61710
+ const packagingThresholds = recalculatedThresholds.filter((threshold) => {
61711
+ if (packagingActionIds.has(threshold.action_id)) return true;
61712
+ return typeof threshold.action_name === "string" && threshold.action_name.toLowerCase() === ACTION_NAMES.PACKAGING.toLowerCase();
61713
+ });
61714
+ const thresholdDayOutput = packagingThresholds.reduce(
61715
+ (sum, threshold) => sum + (Number(threshold.total_day_output) || 0),
61716
+ 0
61717
+ );
61718
+ const thresholdPPH = packagingThresholds.reduce(
61719
+ (sum, threshold) => sum + (Number(threshold.pph_threshold) || 0),
61720
+ 0
61721
+ );
61722
+ 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();
61723
+ if (existingLineThresholdError) {
61724
+ console.warn(
61725
+ `[ShiftsView] Failed to read existing line threshold for line ${lineId}, shift ${shift.shiftId}: ${existingLineThresholdError.message}`
61726
+ );
61727
+ }
61728
+ const factoryId = existingLineThreshold?.factory_id || lineFactoryId;
61729
+ if (factoryId) {
61730
+ const lineThresholdPayload = {
61731
+ factory_id: factoryId,
61732
+ line_id: lineId,
61733
+ date: thresholdDateForShift,
61734
+ shift_id: shift.shiftId,
61735
+ product_code: existingLineThreshold?.product_code || "",
61736
+ threshold_day_output: thresholdDayOutput,
61737
+ threshold_pph: thresholdPPH
61738
+ };
61739
+ if (existingLineThreshold?.sku_id) {
61740
+ lineThresholdPayload.sku_id = existingLineThreshold.sku_id;
61741
+ }
61742
+ const { error: lineThresholdUpsertError } = await supabase.from("line_thresholds").upsert(lineThresholdPayload, { onConflict: "factory_id,line_id,date,shift_id" });
61743
+ if (lineThresholdUpsertError) {
61744
+ throw new Error(
61745
+ `Failed to update line thresholds for line ${lineId}, shift ${shift.shiftId}: ${lineThresholdUpsertError.message}`
61746
+ );
61747
+ }
61748
+ } else {
61749
+ console.warn(
61750
+ `[ShiftsView] Missing factory_id while updating line thresholds for line ${lineId}, shift ${shift.shiftId}`
61751
+ );
61752
+ }
61753
+ recalculatedCount += recalculatedThresholds.length;
61754
+ }
61755
+ return recalculatedCount;
61756
+ },
61757
+ [getOperationalDateForLine, supabase]
61758
+ );
61398
61759
  const handleSaveShifts = React26.useCallback(async (lineId) => {
61760
+ const userId = "6bf6f271-1e55-4a95-9b89-1c3820b58739";
61399
61761
  setLineConfigs((prev) => prev.map(
61400
61762
  (config) => config.id === lineId ? { ...config, isSaving: true, saveSuccess: false } : config
61401
61763
  ));
61764
+ setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
61402
61765
  try {
61403
61766
  const lineConfig = lineConfigs.find((config) => config.id === lineId);
61404
61767
  if (!lineConfig) {
61405
61768
  throw new Error("Line configuration not found");
61406
61769
  }
61770
+ const { data: existingRows, error: existingRowsError } = await supabase.from("line_operating_hours").select("shift_id, start_time, end_time, breaks").eq("line_id", lineId);
61771
+ if (existingRowsError) {
61772
+ throw new Error(`Failed to read existing shift timings: ${existingRowsError.message}`);
61773
+ }
61774
+ const previousShiftHours = (existingRows || []).reduce(
61775
+ (acc, row) => {
61776
+ acc[row.shift_id] = calculateShiftHours(
61777
+ row.start_time,
61778
+ row.end_time,
61779
+ parseBreaksFromDB(row.breaks)
61780
+ );
61781
+ return acc;
61782
+ },
61783
+ {}
61784
+ );
61785
+ const changedShiftCount = (lineConfig.shifts || []).reduce((count, shift) => {
61786
+ const oldShiftHours = previousShiftHours[shift.shiftId];
61787
+ if (oldShiftHours === void 0) return count;
61788
+ const newShiftHours = calculateShiftHours(shift.startTime, shift.endTime, shift.breaks || []);
61789
+ if (!Number.isFinite(newShiftHours)) return count;
61790
+ return Math.abs(newShiftHours - oldShiftHours) > SHIFT_HOURS_EPSILON ? count + 1 : count;
61791
+ }, 0);
61407
61792
  const allSavedRows = [];
61408
61793
  for (const shift of lineConfig.shifts || []) {
61409
61794
  const shiftData = {
@@ -61425,23 +61810,65 @@ var ShiftsView = ({
61425
61810
  if (allSavedRows.length > 0) {
61426
61811
  shiftConfigStore.setFromOperatingHoursRows(lineId, allSavedRows, shiftConfigStore.get(lineId));
61427
61812
  }
61813
+ let recalculatedTargetsCount = 0;
61814
+ try {
61815
+ recalculatedTargetsCount = await recalculateTargetsForShiftHourChanges({
61816
+ lineId,
61817
+ lineConfig,
61818
+ previousShiftHours,
61819
+ updatedBy: userId
61820
+ });
61821
+ } catch (recalcError) {
61822
+ console.error("[ShiftsView] Shift timings were saved but target recalculation failed:", recalcError);
61823
+ showToast("error", "Shift timings saved, but target recalculation failed. Please review targets.");
61824
+ setSaveStatusMessages((prev) => ({
61825
+ ...prev,
61826
+ [lineId]: {
61827
+ message: "Shift timings saved, but target recalculation failed. Please review targets.",
61828
+ tone: "error"
61829
+ }
61830
+ }));
61831
+ }
61428
61832
  setLineConfigs((prev) => prev.map(
61429
61833
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
61430
61834
  ));
61431
- showToast("success", "Shift configurations saved successfully");
61835
+ let successMessage = "Shift configurations saved successfully.";
61836
+ if (changedShiftCount > 0 && recalculatedTargetsCount > 0) {
61837
+ successMessage = `Shift configurations saved. Targets updated successfully for ${recalculatedTargetsCount} workspace${recalculatedTargetsCount === 1 ? "" : "s"}.`;
61838
+ } else if (changedShiftCount > 0) {
61839
+ successMessage = "Shift configurations saved. Target recalculation completed successfully.";
61840
+ }
61841
+ showToast("success", successMessage);
61842
+ setSaveStatusMessages((prev) => ({
61843
+ ...prev,
61844
+ [lineId]: {
61845
+ message: successMessage,
61846
+ tone: "success"
61847
+ }
61848
+ }));
61432
61849
  setTimeout(() => {
61433
61850
  setLineConfigs((prev) => prev.map(
61434
61851
  (config) => config.id === lineId ? { ...config, saveSuccess: false } : config
61435
61852
  ));
61436
61853
  }, 3e3);
61854
+ setTimeout(() => {
61855
+ setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
61856
+ }, 7e3);
61437
61857
  } catch (error2) {
61438
61858
  console.error("Error saving shift configurations:", error2);
61439
61859
  showToast("error", "Failed to save shift configurations");
61860
+ setSaveStatusMessages((prev) => ({
61861
+ ...prev,
61862
+ [lineId]: {
61863
+ message: "Failed to save shift configurations",
61864
+ tone: "error"
61865
+ }
61866
+ }));
61440
61867
  setLineConfigs((prev) => prev.map(
61441
61868
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: false } : config
61442
61869
  ));
61443
61870
  }
61444
- }, [lineConfigs, supabase, showToast]);
61871
+ }, [lineConfigs, recalculateTargetsForShiftHourChanges, supabase, showToast]);
61445
61872
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `min-h-screen bg-slate-50 ${className}`, children: [
61446
61873
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-white border-b border-gray-200/80 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 sm:px-4 md:px-6 lg:px-8 py-3 sm:py-4", children: [
61447
61874
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
@@ -61496,7 +61923,14 @@ var ShiftsView = ({
61496
61923
  /* @__PURE__ */ jsxRuntime.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: [
61497
61924
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
61498
61925
  config.isSaving && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm font-medium text-blue-600", children: "Saving..." }),
61499
- config.saveSuccess && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm font-medium text-green-600", children: "Saved!" })
61926
+ config.saveSuccess && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm font-medium text-green-600", children: "Saved!" }),
61927
+ saveStatusMessages[config.id]?.message && /* @__PURE__ */ jsxRuntime.jsx(
61928
+ "span",
61929
+ {
61930
+ 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"}`,
61931
+ children: saveStatusMessages[config.id]?.message
61932
+ }
61933
+ )
61500
61934
  ] }),
61501
61935
  /* @__PURE__ */ jsxRuntime.jsxs(
61502
61936
  "button",
@@ -61553,20 +61987,6 @@ var ShiftsView = ({
61553
61987
  var AuthenticatedShiftsView = withAuth(React26__namespace.default.memo(ShiftsView));
61554
61988
  var ShiftsView_default = ShiftsView;
61555
61989
 
61556
- // src/lib/constants/actions.ts
61557
- var ACTION_NAMES = {
61558
- /** Assembly operations */
61559
- ASSEMBLY: "Assembly",
61560
- /** Packaging operations */
61561
- PACKAGING: "Packaging",
61562
- /** Inspection operations */
61563
- INSPECTION: "Inspection",
61564
- /** Testing operations */
61565
- TESTING: "Testing",
61566
- /** Quality control operations */
61567
- QUALITY_CONTROL: "Quality Control"
61568
- };
61569
-
61570
61990
  // src/views/TargetsView.utils.ts
61571
61991
  var calculatePPH = (cycleTime, breaks = [], shiftHours = 0) => {
61572
61992
  if (cycleTime === "" || cycleTime === 0) return "";
@@ -65983,7 +66403,7 @@ var TeamManagementView = ({
65983
66403
  optifye: 0
65984
66404
  });
65985
66405
  const [isAddUserDialogOpen, setIsAddUserDialogOpen] = React26.useState(false);
65986
- const canAddUsers = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
66406
+ const canAddUsers = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "plant_head" || user?.role_level === "optifye";
65987
66407
  const canViewUsageStats = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
65988
66408
  const companyIdForUsage = entityConfig?.companyId || user?.properties?.company_id;
65989
66409
  const usageDateRange = React26.useMemo(() => {