Removing unwanted space.

This commit is contained in:
Kartik Sharma 2025-11-05 17:43:24 +05:30
parent cb0ed03525
commit b897a41f95
4 changed files with 194 additions and 26 deletions

View File

@ -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 */}

View File

@ -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",

View 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;

View File

@ -41,7 +41,6 @@ const RecurringExpensePage = () => {
);
};
return (
<RecurringExpenseContext.Provider value={contextValue}>
<div className="container-fluid">