@optifye/dashboard-core 6.10.45 → 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
@@ -30299,37 +30299,13 @@ var HourlyOutputChartComponent = ({
30299
30299
  }, []);
30300
30300
  const xAxisConfig = React26__default.useMemo(() => {
30301
30301
  if (containerWidth >= 960) {
30302
- return { interval: 0, angle: -45, height: 92, tickFont: 10, tickMargin: 12, labelMode: "full" };
30302
+ return { interval: 0, angle: -45, height: 92, tickFont: 10, tickMargin: 12 };
30303
30303
  }
30304
30304
  if (containerWidth >= 760) {
30305
- return { interval: 0, angle: -35, height: 76, tickFont: 9, tickMargin: 8, labelMode: "compact" };
30305
+ return { interval: 0, angle: -35, height: 76, tickFont: 9, tickMargin: 8 };
30306
30306
  }
30307
- return { interval: 0, angle: -30, height: 64, tickFont: 9, tickMargin: 6, labelMode: "start" };
30307
+ return { interval: 0, angle: -30, height: 64, tickFont: 9, tickMargin: 6 };
30308
30308
  }, [containerWidth]);
30309
- const formatXAxisTick = React26__default.useCallback((raw) => {
30310
- const label = typeof raw === "string" ? raw : String(raw);
30311
- if (xAxisConfig.labelMode === "full") return label;
30312
- const parts = label.split("-");
30313
- if (parts.length !== 2) return label;
30314
- const parsePart = (part) => {
30315
- const match = part.match(/^(\d{1,2})(?::(\d{2}))?(AM|PM)$/);
30316
- if (!match) return null;
30317
- const [, hh, mm, meridiem] = match;
30318
- const time2 = mm ? `${hh}:${mm}` : hh;
30319
- const merShort = meridiem === "AM" ? "A" : "P";
30320
- return { time: time2, meridiem, merShort };
30321
- };
30322
- const start = parsePart(parts[0]);
30323
- const end = parsePart(parts[1]);
30324
- if (!start || !end) return label;
30325
- if (xAxisConfig.labelMode === "start") {
30326
- return `${start.time}${start.merShort}`;
30327
- }
30328
- if (start.meridiem === end.meridiem) {
30329
- return `${start.time}-${end.time}${end.merShort}`;
30330
- }
30331
- return `${start.time}${start.merShort}-${end.time}${end.merShort}`;
30332
- }, [xAxisConfig.labelMode]);
30333
30309
  const formatHour = React26__default.useCallback((hourIndex) => {
30334
30310
  const isLastHour = hourIndex === SHIFT_DURATION - 1;
30335
30311
  const startDecimalHour = shiftStartTime.decimalHour + hourIndex;
@@ -30539,7 +30515,7 @@ var HourlyOutputChartComponent = ({
30539
30515
  right: 10,
30540
30516
  bottom: 10,
30541
30517
  // Small bottom margin
30542
- left: 0
30518
+ left: 6
30543
30519
  },
30544
30520
  barCategoryGap: "25%",
30545
30521
  children: [
@@ -30553,8 +30529,7 @@ var HourlyOutputChartComponent = ({
30553
30529
  angle: xAxisConfig.angle,
30554
30530
  textAnchor: "end",
30555
30531
  tickMargin: xAxisConfig.tickMargin,
30556
- height: xAxisConfig.height,
30557
- tickFormatter: formatXAxisTick
30532
+ height: xAxisConfig.height
30558
30533
  }
30559
30534
  ),
30560
30535
  /* @__PURE__ */ jsx(
@@ -30562,7 +30537,7 @@ var HourlyOutputChartComponent = ({
30562
30537
  {
30563
30538
  yAxisId: "default",
30564
30539
  tickMargin: 8,
30565
- width: 35,
30540
+ width: 48,
30566
30541
  domain: [0, maxYValue],
30567
30542
  ticks: generateYAxisTicks(),
30568
30543
  tickFormatter: (value) => value,
@@ -30571,7 +30546,7 @@ var HourlyOutputChartComponent = ({
30571
30546
  return /* @__PURE__ */ jsx("g", { transform: `translate(${x},${y})`, children: /* @__PURE__ */ jsx(
30572
30547
  "text",
30573
30548
  {
30574
- x: 0,
30549
+ x: -2,
30575
30550
  y: 0,
30576
30551
  dy: 4,
30577
30552
  textAnchor: "end",
@@ -51450,13 +51425,31 @@ var InviteUserDialog = ({
51450
51425
  const [selectedRole, setSelectedRole] = useState("supervisor");
51451
51426
  const [selectedLines, setSelectedLines] = useState([]);
51452
51427
  const [selectedFactories, setSelectedFactories] = useState([]);
51428
+ const [lineSearch, setLineSearch] = useState("");
51429
+ const [factorySearch, setFactorySearch] = useState("");
51453
51430
  const [isSubmitting, setIsSubmitting] = useState(false);
51454
51431
  const [error, setError] = useState(null);
51455
- const [isLinesDropdownOpen, setIsLinesDropdownOpen] = useState(false);
51456
- const [isFactoriesDropdownOpen, setIsFactoriesDropdownOpen] = useState(false);
51457
51432
  const canInviteIT = user?.role_level === "owner" || user?.role_level === "optifye";
51458
51433
  const canInvitePlantHead = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
51459
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
+ );
51460
51453
  useEffect(() => {
51461
51454
  if (!isOpen) {
51462
51455
  setEmail("");
@@ -51465,9 +51458,9 @@ var InviteUserDialog = ({
51465
51458
  setSelectedRole("supervisor");
51466
51459
  setSelectedLines([]);
51467
51460
  setSelectedFactories([]);
51461
+ setLineSearch("");
51462
+ setFactorySearch("");
51468
51463
  setError(null);
51469
- setIsLinesDropdownOpen(false);
51470
- setIsFactoriesDropdownOpen(false);
51471
51464
  }
51472
51465
  }, [isOpen]);
51473
51466
  const validateEmail = (email2) => {
@@ -51536,7 +51529,6 @@ var InviteUserDialog = ({
51536
51529
  try {
51537
51530
  const dashboardUrl = typeof window !== "undefined" ? window.location.origin : "";
51538
51531
  if (dashboardUrl) {
51539
- console.log("Sending welcome email to:", email.trim());
51540
51532
  const { data: emailData, error: emailError } = await supabase.functions.invoke("hyper-service", {
51541
51533
  body: {
51542
51534
  action: "send-welcome-email",
@@ -51549,13 +51541,8 @@ var InviteUserDialog = ({
51549
51541
  console.error("Failed to send welcome email:", emailError);
51550
51542
  toast.warning("User added successfully, but welcome email could not be sent");
51551
51543
  } else if (emailData?.success) {
51552
- console.log("Welcome email sent successfully:", emailData);
51553
51544
  toast.success("User added and welcome email sent!");
51554
- } else {
51555
- console.log("Welcome email response:", emailData);
51556
51545
  }
51557
- } else {
51558
- console.warn("Dashboard URL not available, skipping welcome email");
51559
51546
  }
51560
51547
  } catch (emailErr) {
51561
51548
  console.error("Error sending welcome email:", emailErr);
@@ -51581,7 +51568,7 @@ var InviteUserDialog = ({
51581
51568
  children: /* @__PURE__ */ jsxs(
51582
51569
  "div",
51583
51570
  {
51584
- 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",
51585
51572
  onClick: (e) => e.stopPropagation(),
51586
51573
  children: [
51587
51574
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
@@ -51609,7 +51596,7 @@ var InviteUserDialog = ({
51609
51596
  /* @__PURE__ */ jsx("span", { children: error })
51610
51597
  ] }),
51611
51598
  /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
51612
- /* @__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: [
51613
51600
  /* @__PURE__ */ jsxs("div", { children: [
51614
51601
  /* @__PURE__ */ jsxs("label", { htmlFor: "firstName", className: "block text-sm font-medium text-gray-700 mb-2", children: [
51615
51602
  "First Name ",
@@ -51768,124 +51755,192 @@ var InviteUserDialog = ({
51768
51755
  )
51769
51756
  ] })
51770
51757
  ] }),
51771
- selectedRole === "plant_head" && availableFactories.length > 0 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
51772
- /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: [
51773
- /* @__PURE__ */ jsx(Building2, { className: "w-4 h-4 inline mr-1" }),
51774
- "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
+ ] })
51775
51771
  ] }),
51776
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Select one or multiple factories this plant head will manage" }),
51777
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
51778
- /* @__PURE__ */ jsx(
51779
- "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",
51780
51811
  {
51781
- type: "button",
51782
- onClick: () => setIsFactoriesDropdownOpen(!isFactoriesDropdownOpen),
51783
- disabled: isSubmitting,
51784
- 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",
51785
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
51786
- 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) => {
51787
- const factory = availableFactories.find((f) => f.id === factoryId);
51788
- return factory ? /* @__PURE__ */ jsxs(
51789
- "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",
51790
51817
  {
51791
- className: "inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51792
- children: [
51793
- factory.name,
51794
- /* @__PURE__ */ jsx(
51795
- "button",
51796
- {
51797
- type: "button",
51798
- onClick: (e) => {
51799
- e.stopPropagation();
51800
- toggleFactorySelection(factory.id);
51801
- },
51802
- className: "ml-0.5 hover:bg-blue-200 rounded-sm transition-colors",
51803
- disabled: isSubmitting,
51804
- children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
51805
- }
51806
- )
51807
- ]
51808
- },
51809
- factory.id
51810
- ) : null;
51811
- }) }),
51812
- /* @__PURE__ */ jsx(ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isFactoriesDropdownOpen && "rotate-180") })
51813
- ] })
51814
- }
51815
- ),
51816
- 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(
51817
- "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",
51818
51834
  {
51819
- onClick: () => toggleFactorySelection(factory.id),
51820
- className: cn(
51821
- "px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
51822
- selectedFactories.includes(factory.id) && "bg-blue-50"
51823
- ),
51835
+ className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51824
51836
  children: [
51825
- /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700", children: factory.name }),
51826
- 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
+ )
51827
51848
  ]
51828
51849
  },
51829
51850
  factory.id
51830
51851
  )) })
51831
51852
  ] })
51832
51853
  ] }),
51833
- selectedRole === "supervisor" && availableLines.length > 0 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
51834
- /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: [
51835
- /* @__PURE__ */ jsx(Users, { className: "w-4 h-4 inline mr-1" }),
51836
- "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
+ ] })
51837
51867
  ] }),
51838
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Select one or multiple lines this supervisor will monitor" }),
51839
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
51840
- /* @__PURE__ */ jsx(
51841
- "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",
51842
51907
  {
51843
- type: "button",
51844
- onClick: () => setIsLinesDropdownOpen(!isLinesDropdownOpen),
51845
- disabled: isSubmitting,
51846
- 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",
51847
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
51848
- 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) => {
51849
- const line = availableLines.find((l) => l.id === lineId);
51850
- return line ? /* @__PURE__ */ jsxs(
51851
- "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",
51852
51913
  {
51853
- className: "inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51854
- children: [
51855
- line.name,
51856
- /* @__PURE__ */ jsx(
51857
- "button",
51858
- {
51859
- type: "button",
51860
- onClick: (e) => {
51861
- e.stopPropagation();
51862
- toggleLineSelection(line.id);
51863
- },
51864
- className: "ml-0.5 hover:bg-blue-200 rounded-sm transition-colors",
51865
- disabled: isSubmitting,
51866
- children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
51867
- }
51868
- )
51869
- ]
51870
- },
51871
- line.id
51872
- ) : null;
51873
- }) }),
51874
- /* @__PURE__ */ jsx(ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isLinesDropdownOpen && "rotate-180") })
51875
- ] })
51876
- }
51877
- ),
51878
- 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(
51879
- "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",
51880
51930
  {
51881
- onClick: () => toggleLineSelection(line.id),
51882
- className: cn(
51883
- "px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
51884
- selectedLines.includes(line.id) && "bg-blue-50"
51885
- ),
51931
+ className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51886
51932
  children: [
51887
- /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700", children: line.name }),
51888
- 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
+ )
51889
51944
  ]
51890
51945
  },
51891
51946
  line.id
@@ -52420,10 +52475,22 @@ var LineAssignmentDropdown = ({
52420
52475
  canEdit,
52421
52476
  onUpdate
52422
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;
52423
52484
  const [isOpen, setIsOpen] = useState(false);
52424
52485
  const [selectedIds, setSelectedIds] = useState(currentLineIds);
52425
52486
  const [isSaving, setIsSaving] = useState(false);
52426
- 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
+ });
52427
52494
  const buttonRef = useRef(null);
52428
52495
  const dropdownRef = useRef(null);
52429
52496
  useEffect(() => {
@@ -52433,10 +52500,31 @@ var LineAssignmentDropdown = ({
52433
52500
  const updatePosition = () => {
52434
52501
  if (isOpen && buttonRef.current) {
52435
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);
52436
52522
  setPosition({
52437
- top: rect.bottom,
52438
- left: rect.left,
52439
- width: rect.width
52523
+ top,
52524
+ left,
52525
+ width,
52526
+ maxHeight,
52527
+ openAbove
52440
52528
  });
52441
52529
  }
52442
52530
  };
@@ -52514,7 +52602,7 @@ var LineAssignmentDropdown = ({
52514
52602
  assignedLines.length - 2
52515
52603
  ] });
52516
52604
  };
52517
- const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentLineIds.sort());
52605
+ const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentLineIds].sort());
52518
52606
  if (!canEdit) {
52519
52607
  return /* @__PURE__ */ jsx("div", { className: "text-sm", children: getDisplayText() });
52520
52608
  }
@@ -52544,13 +52632,13 @@ var LineAssignmentDropdown = ({
52544
52632
  "div",
52545
52633
  {
52546
52634
  ref: dropdownRef,
52547
- 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",
52548
52636
  style: {
52549
- top: `${position.top + 4}px`,
52637
+ top: `${position.top}px`,
52550
52638
  left: `${position.left}px`,
52551
- minWidth: `${Math.max(position.width, 300)}px`,
52552
- maxWidth: "400px",
52553
- maxHeight: "calc(100vh - 100px)"
52639
+ width: `${position.width}px`,
52640
+ maxHeight: `${position.maxHeight}px`,
52641
+ transform: position.openAbove ? "translateY(-100%)" : void 0
52554
52642
  },
52555
52643
  children: [
52556
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: [
@@ -52567,7 +52655,7 @@ var LineAssignmentDropdown = ({
52567
52655
  }
52568
52656
  )
52569
52657
  ] }) }),
52570
- /* @__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(
52571
52659
  "label",
52572
52660
  {
52573
52661
  className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
@@ -52627,10 +52715,22 @@ var FactoryAssignmentDropdown = ({
52627
52715
  canEdit,
52628
52716
  onUpdate
52629
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;
52630
52724
  const [isOpen, setIsOpen] = useState(false);
52631
52725
  const [selectedIds, setSelectedIds] = useState(currentFactoryIds);
52632
52726
  const [isSaving, setIsSaving] = useState(false);
52633
- 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
+ });
52634
52734
  const buttonRef = useRef(null);
52635
52735
  const dropdownRef = useRef(null);
52636
52736
  useEffect(() => {
@@ -52640,10 +52740,31 @@ var FactoryAssignmentDropdown = ({
52640
52740
  const updatePosition = () => {
52641
52741
  if (isOpen && buttonRef.current) {
52642
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);
52643
52762
  setPosition({
52644
- top: rect.bottom,
52645
- left: rect.left,
52646
- width: rect.width
52763
+ top,
52764
+ left,
52765
+ width,
52766
+ maxHeight,
52767
+ openAbove
52647
52768
  });
52648
52769
  }
52649
52770
  };
@@ -52721,7 +52842,7 @@ var FactoryAssignmentDropdown = ({
52721
52842
  assignedFactories.length - 2
52722
52843
  ] });
52723
52844
  };
52724
- const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentFactoryIds.sort());
52845
+ const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentFactoryIds].sort());
52725
52846
  if (!canEdit) {
52726
52847
  return /* @__PURE__ */ jsx("div", { className: "text-sm", children: getDisplayText() });
52727
52848
  }
@@ -52751,13 +52872,13 @@ var FactoryAssignmentDropdown = ({
52751
52872
  "div",
52752
52873
  {
52753
52874
  ref: dropdownRef,
52754
- 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",
52755
52876
  style: {
52756
- top: `${position.top + 4}px`,
52877
+ top: `${position.top}px`,
52757
52878
  left: `${position.left}px`,
52758
- minWidth: `${Math.max(position.width, 300)}px`,
52759
- maxWidth: "400px",
52760
- maxHeight: "calc(100vh - 100px)"
52879
+ width: `${position.width}px`,
52880
+ maxHeight: `${position.maxHeight}px`,
52881
+ transform: position.openAbove ? "translateY(-100%)" : void 0
52761
52882
  },
52762
52883
  children: [
52763
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: [
@@ -52774,7 +52895,7 @@ var FactoryAssignmentDropdown = ({
52774
52895
  }
52775
52896
  )
52776
52897
  ] }) }),
52777
- /* @__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(
52778
52899
  "label",
52779
52900
  {
52780
52901
  className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
@@ -60907,6 +61028,20 @@ var ProfileView = () => {
60907
61028
  ] });
60908
61029
  };
60909
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
+ };
60910
61045
  var calculateShiftHours = (startTime, endTime, breaks = []) => {
60911
61046
  if (!startTime || !endTime) return 8;
60912
61047
  const [startHour, startMinute] = startTime.split(":").map(Number);
@@ -60920,6 +61055,43 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
60920
61055
  const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
60921
61056
  return Number(hoursDiff.toFixed(1));
60922
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
+ };
60923
61095
  var getStoredLineState = (lineId) => {
60924
61096
  try {
60925
61097
  return JSON.parse(localStorage.getItem(`line_${lineId}_open`) || "false");
@@ -60936,6 +61108,7 @@ var formatBreaks = (breaks) => {
60936
61108
  }))
60937
61109
  };
60938
61110
  };
61111
+ var SHIFT_HOURS_EPSILON = 1e-3;
60939
61112
  var BreakRow = memo$1(({
60940
61113
  break: breakItem,
60941
61114
  onUpdate,
@@ -61231,16 +61404,22 @@ var ShiftsView = ({
61231
61404
  );
61232
61405
  const [loading, setLoading] = useState(true);
61233
61406
  const [error, setError] = useState(null);
61407
+ const [saveStatusMessages, setSaveStatusMessages] = useState(
61408
+ () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: null }), {})
61409
+ );
61234
61410
  const showToast = useCallback((type, message) => {
61235
61411
  if (onToast) {
61236
- onToast(type, message);
61237
- } else {
61238
- if (type === "success") {
61239
- toast.success(message);
61240
- } else {
61241
- 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);
61242
61416
  }
61243
61417
  }
61418
+ if (type === "success") {
61419
+ toast.success(message);
61420
+ } else {
61421
+ toast.error(message);
61422
+ }
61244
61423
  }, [onToast]);
61245
61424
  useEffect(() => {
61246
61425
  const fetchShiftConfigs = async () => {
@@ -61391,15 +61570,196 @@ var ShiftsView = ({
61391
61570
  return config;
61392
61571
  }));
61393
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
+ );
61394
61730
  const handleSaveShifts = useCallback(async (lineId) => {
61731
+ const userId = "6bf6f271-1e55-4a95-9b89-1c3820b58739";
61395
61732
  setLineConfigs((prev) => prev.map(
61396
61733
  (config) => config.id === lineId ? { ...config, isSaving: true, saveSuccess: false } : config
61397
61734
  ));
61735
+ setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
61398
61736
  try {
61399
61737
  const lineConfig = lineConfigs.find((config) => config.id === lineId);
61400
61738
  if (!lineConfig) {
61401
61739
  throw new Error("Line configuration not found");
61402
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);
61403
61763
  const allSavedRows = [];
61404
61764
  for (const shift of lineConfig.shifts || []) {
61405
61765
  const shiftData = {
@@ -61421,23 +61781,65 @@ var ShiftsView = ({
61421
61781
  if (allSavedRows.length > 0) {
61422
61782
  shiftConfigStore.setFromOperatingHoursRows(lineId, allSavedRows, shiftConfigStore.get(lineId));
61423
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
+ }
61424
61803
  setLineConfigs((prev) => prev.map(
61425
61804
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
61426
61805
  ));
61427
- 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
+ }));
61428
61820
  setTimeout(() => {
61429
61821
  setLineConfigs((prev) => prev.map(
61430
61822
  (config) => config.id === lineId ? { ...config, saveSuccess: false } : config
61431
61823
  ));
61432
61824
  }, 3e3);
61825
+ setTimeout(() => {
61826
+ setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
61827
+ }, 7e3);
61433
61828
  } catch (error2) {
61434
61829
  console.error("Error saving shift configurations:", error2);
61435
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
+ }));
61436
61838
  setLineConfigs((prev) => prev.map(
61437
61839
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: false } : config
61438
61840
  ));
61439
61841
  }
61440
- }, [lineConfigs, supabase, showToast]);
61842
+ }, [lineConfigs, recalculateTargetsForShiftHourChanges, supabase, showToast]);
61441
61843
  return /* @__PURE__ */ jsxs("div", { className: `min-h-screen bg-slate-50 ${className}`, children: [
61442
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: [
61443
61845
  /* @__PURE__ */ jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
@@ -61492,7 +61894,14 @@ var ShiftsView = ({
61492
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: [
61493
61895
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
61494
61896
  config.isSaving && /* @__PURE__ */ jsx("span", { className: "text-xs sm:text-sm font-medium text-blue-600", children: "Saving..." }),
61495
- 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
+ )
61496
61905
  ] }),
61497
61906
  /* @__PURE__ */ jsxs(
61498
61907
  "button",
@@ -61549,20 +61958,6 @@ var ShiftsView = ({
61549
61958
  var AuthenticatedShiftsView = withAuth(React26__default.memo(ShiftsView));
61550
61959
  var ShiftsView_default = ShiftsView;
61551
61960
 
61552
- // src/lib/constants/actions.ts
61553
- var ACTION_NAMES = {
61554
- /** Assembly operations */
61555
- ASSEMBLY: "Assembly",
61556
- /** Packaging operations */
61557
- PACKAGING: "Packaging",
61558
- /** Inspection operations */
61559
- INSPECTION: "Inspection",
61560
- /** Testing operations */
61561
- TESTING: "Testing",
61562
- /** Quality control operations */
61563
- QUALITY_CONTROL: "Quality Control"
61564
- };
61565
-
61566
61961
  // src/views/TargetsView.utils.ts
61567
61962
  var calculatePPH = (cycleTime, breaks = [], shiftHours = 0) => {
61568
61963
  if (cycleTime === "" || cycleTime === 0) return "";
@@ -65979,7 +66374,7 @@ var TeamManagementView = ({
65979
66374
  optifye: 0
65980
66375
  });
65981
66376
  const [isAddUserDialogOpen, setIsAddUserDialogOpen] = useState(false);
65982
- 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";
65983
66378
  const canViewUsageStats = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
65984
66379
  const companyIdForUsage = entityConfig?.companyId || user?.properties?.company_id;
65985
66380
  const usageDateRange = useMemo(() => {