@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.js CHANGED
@@ -30328,37 +30328,13 @@ var HourlyOutputChartComponent = ({
30328
30328
  }, []);
30329
30329
  const xAxisConfig = React26__namespace.default.useMemo(() => {
30330
30330
  if (containerWidth >= 960) {
30331
- return { interval: 0, angle: -45, height: 92, tickFont: 10, tickMargin: 12, labelMode: "full" };
30331
+ return { interval: 0, angle: -45, height: 92, tickFont: 10, tickMargin: 12 };
30332
30332
  }
30333
30333
  if (containerWidth >= 760) {
30334
- return { interval: 0, angle: -35, height: 76, tickFont: 9, tickMargin: 8, labelMode: "compact" };
30334
+ return { interval: 0, angle: -35, height: 76, tickFont: 9, tickMargin: 8 };
30335
30335
  }
30336
- return { interval: 0, angle: -30, height: 64, tickFont: 9, tickMargin: 6, labelMode: "start" };
30336
+ return { interval: 0, angle: -30, height: 64, tickFont: 9, tickMargin: 6 };
30337
30337
  }, [containerWidth]);
30338
- const formatXAxisTick = React26__namespace.default.useCallback((raw) => {
30339
- const label = typeof raw === "string" ? raw : String(raw);
30340
- if (xAxisConfig.labelMode === "full") return label;
30341
- const parts = label.split("-");
30342
- if (parts.length !== 2) return label;
30343
- const parsePart = (part) => {
30344
- const match = part.match(/^(\d{1,2})(?::(\d{2}))?(AM|PM)$/);
30345
- if (!match) return null;
30346
- const [, hh, mm, meridiem] = match;
30347
- const time2 = mm ? `${hh}:${mm}` : hh;
30348
- const merShort = meridiem === "AM" ? "A" : "P";
30349
- return { time: time2, meridiem, merShort };
30350
- };
30351
- const start = parsePart(parts[0]);
30352
- const end = parsePart(parts[1]);
30353
- if (!start || !end) return label;
30354
- if (xAxisConfig.labelMode === "start") {
30355
- return `${start.time}${start.merShort}`;
30356
- }
30357
- if (start.meridiem === end.meridiem) {
30358
- return `${start.time}-${end.time}${end.merShort}`;
30359
- }
30360
- return `${start.time}${start.merShort}-${end.time}${end.merShort}`;
30361
- }, [xAxisConfig.labelMode]);
30362
30338
  const formatHour = React26__namespace.default.useCallback((hourIndex) => {
30363
30339
  const isLastHour = hourIndex === SHIFT_DURATION - 1;
30364
30340
  const startDecimalHour = shiftStartTime.decimalHour + hourIndex;
@@ -30568,7 +30544,7 @@ var HourlyOutputChartComponent = ({
30568
30544
  right: 10,
30569
30545
  bottom: 10,
30570
30546
  // Small bottom margin
30571
- left: 0
30547
+ left: 6
30572
30548
  },
30573
30549
  barCategoryGap: "25%",
30574
30550
  children: [
@@ -30582,8 +30558,7 @@ var HourlyOutputChartComponent = ({
30582
30558
  angle: xAxisConfig.angle,
30583
30559
  textAnchor: "end",
30584
30560
  tickMargin: xAxisConfig.tickMargin,
30585
- height: xAxisConfig.height,
30586
- tickFormatter: formatXAxisTick
30561
+ height: xAxisConfig.height
30587
30562
  }
30588
30563
  ),
30589
30564
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -30591,7 +30566,7 @@ var HourlyOutputChartComponent = ({
30591
30566
  {
30592
30567
  yAxisId: "default",
30593
30568
  tickMargin: 8,
30594
- width: 35,
30569
+ width: 48,
30595
30570
  domain: [0, maxYValue],
30596
30571
  ticks: generateYAxisTicks(),
30597
30572
  tickFormatter: (value) => value,
@@ -30600,7 +30575,7 @@ var HourlyOutputChartComponent = ({
30600
30575
  return /* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${x},${y})`, children: /* @__PURE__ */ jsxRuntime.jsx(
30601
30576
  "text",
30602
30577
  {
30603
- x: 0,
30578
+ x: -2,
30604
30579
  y: 0,
30605
30580
  dy: 4,
30606
30581
  textAnchor: "end",
@@ -51479,13 +51454,31 @@ var InviteUserDialog = ({
51479
51454
  const [selectedRole, setSelectedRole] = React26.useState("supervisor");
51480
51455
  const [selectedLines, setSelectedLines] = React26.useState([]);
51481
51456
  const [selectedFactories, setSelectedFactories] = React26.useState([]);
51457
+ const [lineSearch, setLineSearch] = React26.useState("");
51458
+ const [factorySearch, setFactorySearch] = React26.useState("");
51482
51459
  const [isSubmitting, setIsSubmitting] = React26.useState(false);
51483
51460
  const [error, setError] = React26.useState(null);
51484
- const [isLinesDropdownOpen, setIsLinesDropdownOpen] = React26.useState(false);
51485
- const [isFactoriesDropdownOpen, setIsFactoriesDropdownOpen] = React26.useState(false);
51486
51461
  const canInviteIT = user?.role_level === "owner" || user?.role_level === "optifye";
51487
51462
  const canInvitePlantHead = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
51488
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
+ );
51489
51482
  React26.useEffect(() => {
51490
51483
  if (!isOpen) {
51491
51484
  setEmail("");
@@ -51494,9 +51487,9 @@ var InviteUserDialog = ({
51494
51487
  setSelectedRole("supervisor");
51495
51488
  setSelectedLines([]);
51496
51489
  setSelectedFactories([]);
51490
+ setLineSearch("");
51491
+ setFactorySearch("");
51497
51492
  setError(null);
51498
- setIsLinesDropdownOpen(false);
51499
- setIsFactoriesDropdownOpen(false);
51500
51493
  }
51501
51494
  }, [isOpen]);
51502
51495
  const validateEmail = (email2) => {
@@ -51565,7 +51558,6 @@ var InviteUserDialog = ({
51565
51558
  try {
51566
51559
  const dashboardUrl = typeof window !== "undefined" ? window.location.origin : "";
51567
51560
  if (dashboardUrl) {
51568
- console.log("Sending welcome email to:", email.trim());
51569
51561
  const { data: emailData, error: emailError } = await supabase.functions.invoke("hyper-service", {
51570
51562
  body: {
51571
51563
  action: "send-welcome-email",
@@ -51578,13 +51570,8 @@ var InviteUserDialog = ({
51578
51570
  console.error("Failed to send welcome email:", emailError);
51579
51571
  sonner.toast.warning("User added successfully, but welcome email could not be sent");
51580
51572
  } else if (emailData?.success) {
51581
- console.log("Welcome email sent successfully:", emailData);
51582
51573
  sonner.toast.success("User added and welcome email sent!");
51583
- } else {
51584
- console.log("Welcome email response:", emailData);
51585
51574
  }
51586
- } else {
51587
- console.warn("Dashboard URL not available, skipping welcome email");
51588
51575
  }
51589
51576
  } catch (emailErr) {
51590
51577
  console.error("Error sending welcome email:", emailErr);
@@ -51610,7 +51597,7 @@ var InviteUserDialog = ({
51610
51597
  children: /* @__PURE__ */ jsxRuntime.jsxs(
51611
51598
  "div",
51612
51599
  {
51613
- 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",
51614
51601
  onClick: (e) => e.stopPropagation(),
51615
51602
  children: [
51616
51603
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-6 border-b border-gray-200", children: [
@@ -51638,7 +51625,7 @@ var InviteUserDialog = ({
51638
51625
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
51639
51626
  ] }),
51640
51627
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-5", children: [
51641
- /* @__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: [
51642
51629
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
51643
51630
  /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: "firstName", className: "block text-sm font-medium text-gray-700 mb-2", children: [
51644
51631
  "First Name ",
@@ -51797,124 +51784,192 @@ var InviteUserDialog = ({
51797
51784
  )
51798
51785
  ] })
51799
51786
  ] }),
51800
- selectedRole === "plant_head" && availableFactories.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51801
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: [
51802
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, { className: "w-4 h-4 inline mr-1" }),
51803
- "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
+ ] })
51804
51800
  ] }),
51805
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Select one or multiple factories this plant head will manage" }),
51806
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51807
- /* @__PURE__ */ jsxRuntime.jsx(
51808
- "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",
51809
51840
  {
51810
- type: "button",
51811
- onClick: () => setIsFactoriesDropdownOpen(!isFactoriesDropdownOpen),
51812
- disabled: isSubmitting,
51813
- 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",
51814
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
51815
- 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) => {
51816
- const factory = availableFactories.find((f) => f.id === factoryId);
51817
- return factory ? /* @__PURE__ */ jsxRuntime.jsxs(
51818
- "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",
51819
51846
  {
51820
- className: "inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51821
- children: [
51822
- factory.name,
51823
- /* @__PURE__ */ jsxRuntime.jsx(
51824
- "button",
51825
- {
51826
- type: "button",
51827
- onClick: (e) => {
51828
- e.stopPropagation();
51829
- toggleFactorySelection(factory.id);
51830
- },
51831
- className: "ml-0.5 hover:bg-blue-200 rounded-sm transition-colors",
51832
- disabled: isSubmitting,
51833
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
51834
- }
51835
- )
51836
- ]
51837
- },
51838
- factory.id
51839
- ) : null;
51840
- }) }),
51841
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isFactoriesDropdownOpen && "rotate-180") })
51842
- ] })
51843
- }
51844
- ),
51845
- 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(
51846
- "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",
51847
51863
  {
51848
- onClick: () => toggleFactorySelection(factory.id),
51849
- className: cn(
51850
- "px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
51851
- selectedFactories.includes(factory.id) && "bg-blue-50"
51852
- ),
51864
+ className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51853
51865
  children: [
51854
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-700", children: factory.name }),
51855
- 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
+ )
51856
51877
  ]
51857
51878
  },
51858
51879
  factory.id
51859
51880
  )) })
51860
51881
  ] })
51861
51882
  ] }),
51862
- selectedRole === "supervisor" && availableLines.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51863
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: [
51864
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { className: "w-4 h-4 inline mr-1" }),
51865
- "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
+ ] })
51866
51896
  ] }),
51867
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Select one or multiple lines this supervisor will monitor" }),
51868
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
51869
- /* @__PURE__ */ jsxRuntime.jsx(
51870
- "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",
51871
51936
  {
51872
- type: "button",
51873
- onClick: () => setIsLinesDropdownOpen(!isLinesDropdownOpen),
51874
- disabled: isSubmitting,
51875
- 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",
51876
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
51877
- 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) => {
51878
- const line = availableLines.find((l) => l.id === lineId);
51879
- return line ? /* @__PURE__ */ jsxRuntime.jsxs(
51880
- "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",
51881
51942
  {
51882
- className: "inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51883
- children: [
51884
- line.name,
51885
- /* @__PURE__ */ jsxRuntime.jsx(
51886
- "button",
51887
- {
51888
- type: "button",
51889
- onClick: (e) => {
51890
- e.stopPropagation();
51891
- toggleLineSelection(line.id);
51892
- },
51893
- className: "ml-0.5 hover:bg-blue-200 rounded-sm transition-colors",
51894
- disabled: isSubmitting,
51895
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
51896
- }
51897
- )
51898
- ]
51899
- },
51900
- line.id
51901
- ) : null;
51902
- }) }),
51903
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: cn("w-4 h-4 text-gray-400 transition-transform flex-shrink-0", isLinesDropdownOpen && "rotate-180") })
51904
- ] })
51905
- }
51906
- ),
51907
- 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(
51908
- "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",
51909
51959
  {
51910
- onClick: () => toggleLineSelection(line.id),
51911
- className: cn(
51912
- "px-4 py-2.5 cursor-pointer flex items-center justify-between hover:bg-gray-50 transition-colors",
51913
- selectedLines.includes(line.id) && "bg-blue-50"
51914
- ),
51960
+ className: "inline-flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 text-xs rounded-md font-medium",
51915
51961
  children: [
51916
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-700", children: line.name }),
51917
- 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
+ )
51918
51973
  ]
51919
51974
  },
51920
51975
  line.id
@@ -52449,10 +52504,22 @@ var LineAssignmentDropdown = ({
52449
52504
  canEdit,
52450
52505
  onUpdate
52451
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;
52452
52513
  const [isOpen, setIsOpen] = React26.useState(false);
52453
52514
  const [selectedIds, setSelectedIds] = React26.useState(currentLineIds);
52454
52515
  const [isSaving, setIsSaving] = React26.useState(false);
52455
- 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
+ });
52456
52523
  const buttonRef = React26.useRef(null);
52457
52524
  const dropdownRef = React26.useRef(null);
52458
52525
  React26.useEffect(() => {
@@ -52462,10 +52529,31 @@ var LineAssignmentDropdown = ({
52462
52529
  const updatePosition = () => {
52463
52530
  if (isOpen && buttonRef.current) {
52464
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);
52465
52551
  setPosition({
52466
- top: rect.bottom,
52467
- left: rect.left,
52468
- width: rect.width
52552
+ top,
52553
+ left,
52554
+ width,
52555
+ maxHeight,
52556
+ openAbove
52469
52557
  });
52470
52558
  }
52471
52559
  };
@@ -52543,7 +52631,7 @@ var LineAssignmentDropdown = ({
52543
52631
  assignedLines.length - 2
52544
52632
  ] });
52545
52633
  };
52546
- const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentLineIds.sort());
52634
+ const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentLineIds].sort());
52547
52635
  if (!canEdit) {
52548
52636
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm", children: getDisplayText() });
52549
52637
  }
@@ -52573,13 +52661,13 @@ var LineAssignmentDropdown = ({
52573
52661
  "div",
52574
52662
  {
52575
52663
  ref: dropdownRef,
52576
- 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",
52577
52665
  style: {
52578
- top: `${position.top + 4}px`,
52666
+ top: `${position.top}px`,
52579
52667
  left: `${position.left}px`,
52580
- minWidth: `${Math.max(position.width, 300)}px`,
52581
- maxWidth: "400px",
52582
- maxHeight: "calc(100vh - 100px)"
52668
+ width: `${position.width}px`,
52669
+ maxHeight: `${position.maxHeight}px`,
52670
+ transform: position.openAbove ? "translateY(-100%)" : void 0
52583
52671
  },
52584
52672
  children: [
52585
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: [
@@ -52596,7 +52684,7 @@ var LineAssignmentDropdown = ({
52596
52684
  }
52597
52685
  )
52598
52686
  ] }) }),
52599
- /* @__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(
52600
52688
  "label",
52601
52689
  {
52602
52690
  className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
@@ -52656,10 +52744,22 @@ var FactoryAssignmentDropdown = ({
52656
52744
  canEdit,
52657
52745
  onUpdate
52658
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;
52659
52753
  const [isOpen, setIsOpen] = React26.useState(false);
52660
52754
  const [selectedIds, setSelectedIds] = React26.useState(currentFactoryIds);
52661
52755
  const [isSaving, setIsSaving] = React26.useState(false);
52662
- 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
+ });
52663
52763
  const buttonRef = React26.useRef(null);
52664
52764
  const dropdownRef = React26.useRef(null);
52665
52765
  React26.useEffect(() => {
@@ -52669,10 +52769,31 @@ var FactoryAssignmentDropdown = ({
52669
52769
  const updatePosition = () => {
52670
52770
  if (isOpen && buttonRef.current) {
52671
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);
52672
52791
  setPosition({
52673
- top: rect.bottom,
52674
- left: rect.left,
52675
- width: rect.width
52792
+ top,
52793
+ left,
52794
+ width,
52795
+ maxHeight,
52796
+ openAbove
52676
52797
  });
52677
52798
  }
52678
52799
  };
@@ -52750,7 +52871,7 @@ var FactoryAssignmentDropdown = ({
52750
52871
  assignedFactories.length - 2
52751
52872
  ] });
52752
52873
  };
52753
- const hasChanges = JSON.stringify(selectedIds.sort()) !== JSON.stringify(currentFactoryIds.sort());
52874
+ const hasChanges = JSON.stringify([...selectedIds].sort()) !== JSON.stringify([...currentFactoryIds].sort());
52754
52875
  if (!canEdit) {
52755
52876
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm", children: getDisplayText() });
52756
52877
  }
@@ -52780,13 +52901,13 @@ var FactoryAssignmentDropdown = ({
52780
52901
  "div",
52781
52902
  {
52782
52903
  ref: dropdownRef,
52783
- 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",
52784
52905
  style: {
52785
- top: `${position.top + 4}px`,
52906
+ top: `${position.top}px`,
52786
52907
  left: `${position.left}px`,
52787
- minWidth: `${Math.max(position.width, 300)}px`,
52788
- maxWidth: "400px",
52789
- maxHeight: "calc(100vh - 100px)"
52908
+ width: `${position.width}px`,
52909
+ maxHeight: `${position.maxHeight}px`,
52910
+ transform: position.openAbove ? "translateY(-100%)" : void 0
52790
52911
  },
52791
52912
  children: [
52792
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: [
@@ -52803,7 +52924,7 @@ var FactoryAssignmentDropdown = ({
52803
52924
  }
52804
52925
  )
52805
52926
  ] }) }),
52806
- /* @__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(
52807
52928
  "label",
52808
52929
  {
52809
52930
  className: "flex items-center gap-3 px-4 py-2.5 hover:bg-gray-50 cursor-pointer transition-colors",
@@ -60936,6 +61057,20 @@ var ProfileView = () => {
60936
61057
  ] });
60937
61058
  };
60938
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
+ };
60939
61074
  var calculateShiftHours = (startTime, endTime, breaks = []) => {
60940
61075
  if (!startTime || !endTime) return 8;
60941
61076
  const [startHour, startMinute] = startTime.split(":").map(Number);
@@ -60949,6 +61084,43 @@ var calculateShiftHours = (startTime, endTime, breaks = []) => {
60949
61084
  const hoursDiff = (endMinutes - startMinutes - totalBreakMinutes) / 60;
60950
61085
  return Number(hoursDiff.toFixed(1));
60951
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
+ };
60952
61124
  var getStoredLineState = (lineId) => {
60953
61125
  try {
60954
61126
  return JSON.parse(localStorage.getItem(`line_${lineId}_open`) || "false");
@@ -60965,6 +61137,7 @@ var formatBreaks = (breaks) => {
60965
61137
  }))
60966
61138
  };
60967
61139
  };
61140
+ var SHIFT_HOURS_EPSILON = 1e-3;
60968
61141
  var BreakRow = React26.memo(({
60969
61142
  break: breakItem,
60970
61143
  onUpdate,
@@ -61260,16 +61433,22 @@ var ShiftsView = ({
61260
61433
  );
61261
61434
  const [loading, setLoading] = React26.useState(true);
61262
61435
  const [error, setError] = React26.useState(null);
61436
+ const [saveStatusMessages, setSaveStatusMessages] = React26.useState(
61437
+ () => lineIds.reduce((acc, id3) => ({ ...acc, [id3]: null }), {})
61438
+ );
61263
61439
  const showToast = React26.useCallback((type, message) => {
61264
61440
  if (onToast) {
61265
- onToast(type, message);
61266
- } else {
61267
- if (type === "success") {
61268
- sonner.toast.success(message);
61269
- } else {
61270
- 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);
61271
61445
  }
61272
61446
  }
61447
+ if (type === "success") {
61448
+ sonner.toast.success(message);
61449
+ } else {
61450
+ sonner.toast.error(message);
61451
+ }
61273
61452
  }, [onToast]);
61274
61453
  React26.useEffect(() => {
61275
61454
  const fetchShiftConfigs = async () => {
@@ -61420,15 +61599,196 @@ var ShiftsView = ({
61420
61599
  return config;
61421
61600
  }));
61422
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
+ );
61423
61759
  const handleSaveShifts = React26.useCallback(async (lineId) => {
61760
+ const userId = "6bf6f271-1e55-4a95-9b89-1c3820b58739";
61424
61761
  setLineConfigs((prev) => prev.map(
61425
61762
  (config) => config.id === lineId ? { ...config, isSaving: true, saveSuccess: false } : config
61426
61763
  ));
61764
+ setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
61427
61765
  try {
61428
61766
  const lineConfig = lineConfigs.find((config) => config.id === lineId);
61429
61767
  if (!lineConfig) {
61430
61768
  throw new Error("Line configuration not found");
61431
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);
61432
61792
  const allSavedRows = [];
61433
61793
  for (const shift of lineConfig.shifts || []) {
61434
61794
  const shiftData = {
@@ -61450,23 +61810,65 @@ var ShiftsView = ({
61450
61810
  if (allSavedRows.length > 0) {
61451
61811
  shiftConfigStore.setFromOperatingHoursRows(lineId, allSavedRows, shiftConfigStore.get(lineId));
61452
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
+ }
61453
61832
  setLineConfigs((prev) => prev.map(
61454
61833
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: true } : config
61455
61834
  ));
61456
- 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
+ }));
61457
61849
  setTimeout(() => {
61458
61850
  setLineConfigs((prev) => prev.map(
61459
61851
  (config) => config.id === lineId ? { ...config, saveSuccess: false } : config
61460
61852
  ));
61461
61853
  }, 3e3);
61854
+ setTimeout(() => {
61855
+ setSaveStatusMessages((prev) => ({ ...prev, [lineId]: null }));
61856
+ }, 7e3);
61462
61857
  } catch (error2) {
61463
61858
  console.error("Error saving shift configurations:", error2);
61464
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
+ }));
61465
61867
  setLineConfigs((prev) => prev.map(
61466
61868
  (config) => config.id === lineId ? { ...config, isSaving: false, saveSuccess: false } : config
61467
61869
  ));
61468
61870
  }
61469
- }, [lineConfigs, supabase, showToast]);
61871
+ }, [lineConfigs, recalculateTargetsForShiftHourChanges, supabase, showToast]);
61470
61872
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `min-h-screen bg-slate-50 ${className}`, children: [
61471
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: [
61472
61874
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm:hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
@@ -61521,7 +61923,14 @@ var ShiftsView = ({
61521
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: [
61522
61924
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
61523
61925
  config.isSaving && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs sm:text-sm font-medium text-blue-600", children: "Saving..." }),
61524
- 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
+ )
61525
61934
  ] }),
61526
61935
  /* @__PURE__ */ jsxRuntime.jsxs(
61527
61936
  "button",
@@ -61578,20 +61987,6 @@ var ShiftsView = ({
61578
61987
  var AuthenticatedShiftsView = withAuth(React26__namespace.default.memo(ShiftsView));
61579
61988
  var ShiftsView_default = ShiftsView;
61580
61989
 
61581
- // src/lib/constants/actions.ts
61582
- var ACTION_NAMES = {
61583
- /** Assembly operations */
61584
- ASSEMBLY: "Assembly",
61585
- /** Packaging operations */
61586
- PACKAGING: "Packaging",
61587
- /** Inspection operations */
61588
- INSPECTION: "Inspection",
61589
- /** Testing operations */
61590
- TESTING: "Testing",
61591
- /** Quality control operations */
61592
- QUALITY_CONTROL: "Quality Control"
61593
- };
61594
-
61595
61990
  // src/views/TargetsView.utils.ts
61596
61991
  var calculatePPH = (cycleTime, breaks = [], shiftHours = 0) => {
61597
61992
  if (cycleTime === "" || cycleTime === 0) return "";
@@ -66008,7 +66403,7 @@ var TeamManagementView = ({
66008
66403
  optifye: 0
66009
66404
  });
66010
66405
  const [isAddUserDialogOpen, setIsAddUserDialogOpen] = React26.useState(false);
66011
- 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";
66012
66407
  const canViewUsageStats = user?.role_level === "owner" || user?.role_level === "it" || user?.role_level === "optifye";
66013
66408
  const companyIdForUsage = entityConfig?.companyId || user?.properties?.company_id;
66014
66409
  const usageDateRange = React26.useMemo(() => {