Removing unwanted space.
This commit is contained in:
parent
cb0ed03525
commit
b897a41f95
@ -9,6 +9,7 @@ import { FREQUENCY_FOR_RECURRING, INR_CURRENCY_CODE } from '../../utils/constant
|
||||
import { useCurrencies, useProjectName } from '../../hooks/useProjects';
|
||||
import { useCreateRecurringExpense, usePayee, useUpdateRecurringExpense } from '../../hooks/useExpense';
|
||||
import InputSuggestions from '../common/InputSuggestion';
|
||||
import MultiEmployeeSearchInput from '../common/MultiEmployeeSearchInput';
|
||||
|
||||
function ManageRecurringExpense({ closeModal, requestToEdit = null }) {
|
||||
const data = {}
|
||||
@ -159,6 +160,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) {
|
||||
id="title"
|
||||
className="form-control form-control-sm"
|
||||
{...register("title")}
|
||||
placeholder="Enter title"
|
||||
/>
|
||||
{errors.title && (
|
||||
<small className="danger-text">
|
||||
@ -262,6 +264,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) {
|
||||
step="0.01"
|
||||
inputMode="decimal"
|
||||
{...register("amount", { valueAsNumber: true })}
|
||||
placeholder="Enter amount"
|
||||
/>
|
||||
{errors.amount && (
|
||||
<small className="danger-text">{errors.amount.message}</small>
|
||||
@ -282,6 +285,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) {
|
||||
setValue("payee", val, { shouldValidate: true })
|
||||
}
|
||||
error={errors.payee?.message}
|
||||
placeholder="Select or enter payee"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -370,6 +374,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) {
|
||||
min="0"
|
||||
step="1"
|
||||
{...register("paymentBufferDays", { valueAsNumber: true })}
|
||||
placeholder="Enter payment buffer days"
|
||||
/>
|
||||
{errors.paymentBufferDays && (
|
||||
<small className="danger-text">{errors.paymentBufferDays.message}</small>
|
||||
@ -386,6 +391,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) {
|
||||
min="1"
|
||||
step="1"
|
||||
{...register("numberOfIteration", { valueAsNumber: true })}
|
||||
placeholder="Enter number of iterations"
|
||||
/>
|
||||
{errors.numberOfIteration && (
|
||||
<small className="danger-text">{errors.numberOfIteration.message}</small>
|
||||
@ -394,7 +400,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) {
|
||||
</div>
|
||||
|
||||
{/* Notify */}
|
||||
<div className="row my-2 text-start">
|
||||
{/* <div className="row my-2 text-start">
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="notifyTo" className="form-label" required>
|
||||
Notify Employees
|
||||
@ -412,6 +418,22 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) {
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="row my-2 text-start">
|
||||
<div className="col-md-6">
|
||||
<Label htmlFor="notifyTo" className="form-label" required>
|
||||
Notify Employees
|
||||
</Label>
|
||||
|
||||
<MultiEmployeeSearchInput
|
||||
control={control}
|
||||
name="notifyTo"
|
||||
projectId={watch("projectId")}
|
||||
placeholder="Select Employees"
|
||||
forAll={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
|
||||
@ -29,12 +29,6 @@ const RecurringExpenseList = ({ search, filterStatuses }) => {
|
||||
);
|
||||
|
||||
const recurringExpenseColumns = [
|
||||
// {
|
||||
// key: "recurringPaymentUId",
|
||||
// label: "Recurring Payment ID",
|
||||
// align: "text-start ps-2",
|
||||
// getValue: (e) => e?.recurringPaymentUId || "N/A",
|
||||
// },
|
||||
{
|
||||
key: "expenseCategory",
|
||||
label: "Category",
|
||||
@ -47,13 +41,6 @@ const RecurringExpenseList = ({ search, filterStatuses }) => {
|
||||
align: "text-start",
|
||||
getValue: (e) => e?.title || "N/A",
|
||||
},
|
||||
// {
|
||||
// key: "strikeDate",
|
||||
// label: "Strike Date",
|
||||
// align: "text-center",
|
||||
// getValue: (e) =>
|
||||
// e?.strikeDate ? formatUTCToLocalTime(e.strikeDate) : "N/A",
|
||||
// },
|
||||
{
|
||||
key: "payee",
|
||||
label: "Payee",
|
||||
@ -78,17 +65,6 @@ const RecurringExpenseList = ({ search, filterStatuses }) => {
|
||||
? `${e?.currency?.symbol || ""}${e.amount.toLocaleString()}`
|
||||
: "N/A",
|
||||
},
|
||||
|
||||
|
||||
// {
|
||||
// key: "latestPRGeneratedAt",
|
||||
// label: "Last Generation Date",
|
||||
// align: "text-center",
|
||||
// getValue: (e) =>
|
||||
// e?.latestPRGeneratedAt
|
||||
// ? formatUTCToLocalTime(e.latestPRGeneratedAt)
|
||||
// : "N/A",
|
||||
// },
|
||||
{
|
||||
key: "createdAt",
|
||||
label: "Next Generation Date",
|
||||
|
||||
171
src/components/common/MultiEmployeeSearchInput.jsx
Normal file
171
src/components/common/MultiEmployeeSearchInput.jsx
Normal file
@ -0,0 +1,171 @@
|
||||
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
|
||||
);
|
||||
|
||||
// Initialize selected employees from emails (comma-separated string)
|
||||
useEffect(() => {
|
||||
if (value && employees?.data) {
|
||||
const emails = value.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;
|
||||
|
||||
@ -41,7 +41,6 @@ const RecurringExpensePage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<RecurringExpenseContext.Provider value={contextValue}>
|
||||
<div className="container-fluid">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user