From b897a41f9510068324c747a74c7873c5b99337e0 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Wed, 5 Nov 2025 17:43:24 +0530 Subject: [PATCH] Removing unwanted space. --- .../ManageRecurringExpense.jsx | 24 ++- .../RecurringExpense/RecurringExpenseList.jsx | 24 --- .../common/MultiEmployeeSearchInput.jsx | 171 ++++++++++++++++++ .../RecurringExpense/RecurringExpensePage.jsx | 1 - 4 files changed, 194 insertions(+), 26 deletions(-) create mode 100644 src/components/common/MultiEmployeeSearchInput.jsx diff --git a/src/components/RecurringExpense/ManageRecurringExpense.jsx b/src/components/RecurringExpense/ManageRecurringExpense.jsx index acb531ff..3403d754 100644 --- a/src/components/RecurringExpense/ManageRecurringExpense.jsx +++ b/src/components/RecurringExpense/ManageRecurringExpense.jsx @@ -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 && ( @@ -262,6 +264,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) { step="0.01" inputMode="decimal" {...register("amount", { valueAsNumber: true })} + placeholder="Enter amount" /> {errors.amount && ( {errors.amount.message} @@ -282,6 +285,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) { setValue("payee", val, { shouldValidate: true }) } error={errors.payee?.message} + placeholder="Select or enter payee" /> @@ -370,6 +374,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) { min="0" step="1" {...register("paymentBufferDays", { valueAsNumber: true })} + placeholder="Enter payment buffer days" /> {errors.paymentBufferDays && ( {errors.paymentBufferDays.message} @@ -386,6 +391,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) { min="1" step="1" {...register("numberOfIteration", { valueAsNumber: true })} + placeholder="Enter number of iterations" /> {errors.numberOfIteration && ( {errors.numberOfIteration.message} @@ -394,7 +400,7 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) { {/* Notify */} -
+ {/*
)}
+
*/} + +
+
+ + + +
{/* Description */} diff --git a/src/components/RecurringExpense/RecurringExpenseList.jsx b/src/components/RecurringExpense/RecurringExpenseList.jsx index a5ac8669..df5de323 100644 --- a/src/components/RecurringExpense/RecurringExpenseList.jsx +++ b/src/components/RecurringExpense/RecurringExpenseList.jsx @@ -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", diff --git a/src/components/common/MultiEmployeeSearchInput.jsx b/src/components/common/MultiEmployeeSearchInput.jsx new file mode 100644 index 00000000..917ee9f8 --- /dev/null +++ b/src/components/common/MultiEmployeeSearchInput.jsx @@ -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 ( +
+
+ {selectedEmployees.map((emp) => ( +
+ + {emp.firstName} {emp.lastName} + handleRemove(emp.email)} + > + × + +
+ ))} +
+ + + { + setSearch(e.target.value); + setShowDropdown(true); + }} + onFocus={() => setShowDropdown(true)} + /> + + {showDropdown && (employees?.data?.length > 0 || isLoading) && ( +
    + {isLoading ? ( +
  • Searching...
  • + ) : ( + employees?.data + ?.filter((emp) => !selectedEmployees.find((e) => e.email === emp.email)) + .map((emp) => ( +
  • handleSelect(emp)} + > +
    + + {`${emp.firstName} ${emp.lastName}`} +
    +
  • + )) + )} +
+ )} + + {error && {error.message}} +
+ ); +}; + +export default MultiEmployeeSearchInput; + diff --git a/src/pages/RecurringExpense/RecurringExpensePage.jsx b/src/pages/RecurringExpense/RecurringExpensePage.jsx index 897d993d..c59580c6 100644 --- a/src/pages/RecurringExpense/RecurringExpensePage.jsx +++ b/src/pages/RecurringExpense/RecurringExpensePage.jsx @@ -41,7 +41,6 @@ const RecurringExpensePage = () => { ); }; - return (