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