marco.pms.web/src/components/common/MultiEmployeeSearchInput.jsx

182 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect, useRef } from "react";
import { useEmployeesName } from "../../hooks/useEmployees";
import { useDebounce } from "../../utils/appUtils";
import { useController } from "react-hook-form";
import Avatar from "../common/Avatar";
const MultiEmployeeSearchInput = ({
control,
name,
projectId,
placeholder,
forAll,
}) => {
const {
field: { onChange, value, ref },
fieldState: { error },
} = useController({ name, control });
const [search, setSearch] = useState("");
const [showDropdown, setShowDropdown] = useState(false);
const [selectedEmployees, setSelectedEmployees] = useState([]);
const dropdownRef = useRef(null);
const debouncedSearch = useDebounce(search, 500);
const { data: employees, isLoading } = useEmployeesName(
projectId,
debouncedSearch,
forAll
);
useEffect(() => {
if (value && employees?.data) {
// Ensure value is a string (sometimes it may come as array/object)
const stringValue =
typeof value === "string"
? value
: Array.isArray(value)
? value.join(",")
: "";
const emails = stringValue.split(",").filter(Boolean);
const foundEmps = employees.data.filter((emp) =>
emails.includes(emp.email)
);
setSelectedEmployees(foundEmps);
if (forAll && foundEmps.length > 0) {
setSearch(""); // clear search field
}
}
}, [value, employees?.data, forAll]);
const handleSelect = (employee) => {
if (!selectedEmployees.find((emp) => emp.email === employee.email)) {
const newSelected = [...selectedEmployees, employee];
setSelectedEmployees(newSelected);
// Store emails instead of IDs
onChange(
newSelected
.map((e) => e.email)
.filter(Boolean)
.join(",")
);
setSearch("");
setShowDropdown(false);
}
};
const handleRemove = (email) => {
const newSelected = selectedEmployees.filter((e) => e.email !== email);
setSelectedEmployees(newSelected);
onChange(
newSelected
.map((e) => e.email)
.filter(Boolean)
.join(",")
);
};
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setShowDropdown(false);
}
};
const handleEsc = (event) => {
if (event.key === "Escape") setShowDropdown(false);
};
document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("keydown", handleEsc);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("keydown", handleEsc);
};
}, []);
return (
<div className="position-relative" ref={dropdownRef}>
<div className="d-flex flex-wrap gap-1 mb-1">
{selectedEmployees.map((emp) => (
<div
key={emp.email}
className="badge bg-label-secondary d-flex align-items-center py-0 px-1"
style={{ fontSize: "0.75rem" }}
>
<Avatar
size="xs"
classAvatar="m-0 me-1"
firstName={emp.firstName}
lastName={emp.lastName}
/>
{emp.firstName} {emp.lastName}
<span
className="ms-1"
style={{ cursor: "pointer", fontSize: "0.75rem" }}
onClick={() => handleRemove(emp.email)}
>
×
</span>
</div>
))}
</div>
<input
type="search"
ref={ref}
className="form-control form-control-sm"
placeholder={placeholder}
value={search}
onChange={(e) => {
setSearch(e.target.value);
setShowDropdown(true);
}}
onFocus={() => setShowDropdown(true)}
/>
{showDropdown && (employees?.data?.length > 0 || isLoading) && (
<ul
className="list-group position-absolute bg-white w-100 shadow z-3 rounded-1 px-0"
style={{ maxHeight: 200, overflowY: "auto", zIndex: 1050 }}
>
{isLoading ? (
<li className="list-group-item py-1 px-2 text-muted">Searching...</li>
) : (
employees?.data
?.filter((emp) => !selectedEmployees.find((e) => e.email === emp.email))
.map((emp) => (
<li
key={emp.email}
className="list-group-item list-group-item-action py-1 px-2"
style={{ cursor: "pointer" }}
onClick={() => handleSelect(emp)}
>
<div className="d-flex align-items-center">
<Avatar
size="xs"
classAvatar="m-0 me-2"
firstName={emp.firstName}
lastName={emp.lastName}
/>
<span className="text-muted">{`${emp.firstName} ${emp.lastName}`}</span>
</div>
</li>
))
)}
</ul>
)}
{error && <small className="text-danger">{error.message}</small>}
</div>
);
};
export default MultiEmployeeSearchInput;