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