Refactor_Expenses #321
@ -33,7 +33,11 @@ import {
|
|||||||
SearchSchema,
|
SearchSchema,
|
||||||
} from "../../components/Expenses/ExpenseSchema";
|
} from "../../components/Expenses/ExpenseSchema";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { CREATE_EXEPENSE } from "../../utils/constants";
|
import {
|
||||||
|
CREATE_EXEPENSE,
|
||||||
|
VIEW_ALL_EXPNESE,
|
||||||
|
VIEW_SELF_EXPENSE,
|
||||||
|
} from "../../utils/constants";
|
||||||
|
|
||||||
const SelectDropdown = ({
|
const SelectDropdown = ({
|
||||||
label,
|
label,
|
||||||
@ -100,12 +104,12 @@ export const useExpenseContext = () => {
|
|||||||
throw new Error("useExpenseContext must be used within an ExpenseProvider");
|
throw new Error("useExpenseContext must be used within an ExpenseProvider");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
};
|
||||||
|
|
||||||
const ExpensePage = () => {
|
const ExpensePage = () => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [filters,setFilter] = useState()
|
const [filters, setFilter] = useState();
|
||||||
const IsCreatedAble = useHasUserPermission(CREATE_EXEPENSE)
|
const IsCreatedAble = useHasUserPermission(CREATE_EXEPENSE);
|
||||||
const dropdownRef = useRef(null);
|
const dropdownRef = useRef(null);
|
||||||
const shouldCloseOnOutsideClick = useRef(false);
|
const shouldCloseOnOutsideClick = useRef(false);
|
||||||
const selectedProjectId = useSelector(
|
const selectedProjectId = useSelector(
|
||||||
@ -123,7 +127,8 @@ const ExpensePage = () => {
|
|||||||
IsOpen: false,
|
IsOpen: false,
|
||||||
Image: null,
|
Image: null,
|
||||||
});
|
});
|
||||||
|
const IsViewAll = useHasUserPermission(VIEW_ALL_EXPNESE);
|
||||||
|
const IsViewSelf = useHasUserPermission(VIEW_SELF_EXPENSE);
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
setViewExpense,
|
setViewExpense,
|
||||||
setManageExpenseModal,
|
setManageExpenseModal,
|
||||||
@ -155,26 +160,25 @@ const ExpensePage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onSubmit = (data) => {
|
const onSubmit = (data) => {
|
||||||
setFilter(data)
|
setFilter(data);
|
||||||
|
};
|
||||||
|
const isValidDate = (date) => {
|
||||||
|
return date instanceof Date && !isNaN(date);
|
||||||
};
|
};
|
||||||
const isValidDate = (date) => {
|
|
||||||
return date instanceof Date && !isNaN(date);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setDateRange = ({ startDate, endDate }) => {
|
const setDateRange = ({ startDate, endDate }) => {
|
||||||
const parsedStart = new Date(startDate);
|
const parsedStart = new Date(startDate);
|
||||||
const parsedEnd = new Date(endDate);
|
const parsedEnd = new Date(endDate);
|
||||||
|
|
||||||
setValue(
|
|
||||||
"startDate",
|
|
||||||
isValidDate(parsedStart) ? parsedStart.toISOString().split("T")[0] : null
|
|
||||||
);
|
|
||||||
setValue(
|
|
||||||
"endDate",
|
|
||||||
isValidDate(parsedEnd) ? parsedEnd.toISOString().split("T")[0] : null
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
setValue(
|
||||||
|
"startDate",
|
||||||
|
isValidDate(parsedStart) ? parsedStart.toISOString().split("T")[0] : null
|
||||||
|
);
|
||||||
|
setValue(
|
||||||
|
"endDate",
|
||||||
|
isValidDate(parsedEnd) ? parsedEnd.toISOString().split("T")[0] : null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const toggleDropdown = () => {
|
const toggleDropdown = () => {
|
||||||
setIsOpen((prev) => {
|
setIsOpen((prev) => {
|
||||||
@ -199,19 +203,17 @@ const setDateRange = ({ startDate, endDate }) => {
|
|||||||
document.removeEventListener("mousedown", handleClickOutside);
|
document.removeEventListener("mousedown", handleClickOutside);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
const clearFilter =()=>{
|
const clearFilter = () => {
|
||||||
setFilter(
|
setFilter({
|
||||||
{
|
projectIds: [],
|
||||||
projectIds: [],
|
statusIds: [],
|
||||||
statusIds: [],
|
createdByIds: [],
|
||||||
createdByIds: [],
|
paidById: [],
|
||||||
paidById: [],
|
startDate: null,
|
||||||
startDate: null,
|
endDate: null,
|
||||||
endDate: null,
|
});
|
||||||
})
|
reset();
|
||||||
reset()
|
};
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExpenseContext.Provider value={contextValue}>
|
<ExpenseContext.Provider value={contextValue}>
|
||||||
@ -222,167 +224,182 @@ const setDateRange = ({ startDate, endDate }) => {
|
|||||||
{ label: "Expense", link: null },
|
{ label: "Expense", link: null },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div className="card my-1 text-start px-0">
|
{IsViewAll || IsViewSelf ? (
|
||||||
<div className="card-body py-1 px-1">
|
<>
|
||||||
<div className="row">
|
<div className="card my-1 text-start px-0">
|
||||||
<div className="col-5 col-sm-4 d-flex aligin-items-center">
|
<div className="card-body py-1 px-1">
|
||||||
<div
|
<div className="row">
|
||||||
className="dropdown d-inline-block mt-2 align-items-center"
|
<div className="col-5 col-sm-4 d-flex aligin-items-center">
|
||||||
ref={dropdownRef}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className="bx bx-slider-alt ms-2"
|
|
||||||
role="button"
|
|
||||||
aria-expanded={isOpen}
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
onClick={() => setIsOpen((v) => !v)}
|
|
||||||
></i>
|
|
||||||
{isOpen && (
|
|
||||||
<div
|
<div
|
||||||
className="dropdown-menu p-3 overflow-hidden show d-flex align-items-center"
|
className="dropdown d-inline-block mt-2 align-items-center"
|
||||||
style={{ minWidth: "500px" }}
|
ref={dropdownRef}
|
||||||
>
|
>
|
||||||
<FormProvider {...methods}>
|
<i
|
||||||
<form
|
className="bx bx-slider-alt ms-2"
|
||||||
className="p-2 p-sm-0"
|
role="button"
|
||||||
onSubmit={handleSubmit((data) => {
|
aria-expanded={isOpen}
|
||||||
onSubmit(data);
|
style={{ cursor: "pointer" }}
|
||||||
setIsOpen(false);
|
onClick={() => setIsOpen((v) => !v)}
|
||||||
})}
|
></i>
|
||||||
|
{isOpen && (
|
||||||
|
<div
|
||||||
|
className="dropdown-menu p-3 overflow-hidden show d-flex align-items-center"
|
||||||
|
style={{ minWidth: "500px" }}
|
||||||
>
|
>
|
||||||
<div className="w-100">
|
<FormProvider {...methods}>
|
||||||
<DateRangePicker
|
<form
|
||||||
onRangeChange={setDateRange}
|
className="p-2 p-sm-0"
|
||||||
endDateMode="today"
|
onSubmit={handleSubmit((data) => {
|
||||||
DateDifference="6"
|
onSubmit(data);
|
||||||
dateFormat="DD-MM-YYYY"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="row g-2">
|
|
||||||
<div className="col-12 ">
|
|
||||||
<label className="form-label d-block text-secondary">
|
|
||||||
Select Status
|
|
||||||
</label>
|
|
||||||
<div className="d-flex flex-wrap">
|
|
||||||
{ExpenseStatus.map((status) => (
|
|
||||||
<Controller
|
|
||||||
key={status.id}
|
|
||||||
control={control}
|
|
||||||
name="statusIds"
|
|
||||||
render={({
|
|
||||||
field: { value = [], onChange },
|
|
||||||
}) => (
|
|
||||||
<div className="d-flex align-items-center me-4 mb-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input form-check-input-sm"
|
|
||||||
value={status.id}
|
|
||||||
checked={value.includes(status.id)}
|
|
||||||
onChange={(e) => {
|
|
||||||
if (e.target.checked) {
|
|
||||||
onChange([...value, status.id]);
|
|
||||||
} else {
|
|
||||||
onChange(
|
|
||||||
value.filter(
|
|
||||||
(v) => v !== status.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label className="ms-2 mb-0">
|
|
||||||
{status.displayName}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="row g-2">
|
|
||||||
<SelectMultiple
|
|
||||||
name="projectIds"
|
|
||||||
label="Select Projects"
|
|
||||||
options={projectNames}
|
|
||||||
labelKey="name"
|
|
||||||
valueKey="id"
|
|
||||||
IsLoading={projectLoading}
|
|
||||||
/>
|
|
||||||
<SelectMultiple
|
|
||||||
name="createdByIds"
|
|
||||||
label="Select creator"
|
|
||||||
options={employees}
|
|
||||||
labelKey={(item) =>
|
|
||||||
`${item.firstName} ${item.lastName}`
|
|
||||||
}
|
|
||||||
valueKey="id"
|
|
||||||
IsLoading={empLoading}
|
|
||||||
/>
|
|
||||||
<SelectMultiple
|
|
||||||
name="paidById"
|
|
||||||
label="Select Paid by"
|
|
||||||
options={employees}
|
|
||||||
labelKey={(item) =>
|
|
||||||
`${item.firstName} ${item.lastName}`
|
|
||||||
}
|
|
||||||
valueKey="id"
|
|
||||||
IsLoading={empLoading}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="d-flex justify-content-end py-1 gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-secondary btn-xs"
|
|
||||||
onClick={() => {
|
|
||||||
clearFilter()
|
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}}
|
})}
|
||||||
>
|
>
|
||||||
Clear
|
<div className="w-100">
|
||||||
</button>
|
<DateRangePicker
|
||||||
<button
|
onRangeChange={setDateRange}
|
||||||
type="submit"
|
endDateMode="today"
|
||||||
className="btn btn-primary btn-xs"
|
DateDifference="6"
|
||||||
>
|
dateFormat="DD-MM-YYYY"
|
||||||
Apply
|
/>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
<div className="row g-2">
|
||||||
</FormProvider>
|
<div className="col-12 ">
|
||||||
|
<label className="form-label d-block text-secondary">
|
||||||
|
Select Status
|
||||||
|
</label>
|
||||||
|
<div className="d-flex flex-wrap">
|
||||||
|
{ExpenseStatus.map((status) => (
|
||||||
|
<Controller
|
||||||
|
key={status.id}
|
||||||
|
control={control}
|
||||||
|
name="statusIds"
|
||||||
|
render={({
|
||||||
|
field: { value = [], onChange },
|
||||||
|
}) => (
|
||||||
|
<div className="d-flex align-items-center me-4 mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input form-check-input-sm"
|
||||||
|
value={status.id}
|
||||||
|
checked={value.includes(
|
||||||
|
status.id
|
||||||
|
)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
onChange([
|
||||||
|
...value,
|
||||||
|
status.id,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
onChange(
|
||||||
|
value.filter(
|
||||||
|
(v) => v !== status.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label className="ms-2 mb-0">
|
||||||
|
{status.displayName}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="row g-2">
|
||||||
|
<SelectMultiple
|
||||||
|
name="projectIds"
|
||||||
|
label="Select Projects"
|
||||||
|
options={projectNames}
|
||||||
|
labelKey="name"
|
||||||
|
valueKey="id"
|
||||||
|
IsLoading={projectLoading}
|
||||||
|
/>
|
||||||
|
<SelectMultiple
|
||||||
|
name="createdByIds"
|
||||||
|
label="Select creator"
|
||||||
|
options={employees}
|
||||||
|
labelKey={(item) =>
|
||||||
|
`${item.firstName} ${item.lastName}`
|
||||||
|
}
|
||||||
|
valueKey="id"
|
||||||
|
IsLoading={empLoading}
|
||||||
|
/>
|
||||||
|
<SelectMultiple
|
||||||
|
name="paidById"
|
||||||
|
label="Select Paid by"
|
||||||
|
options={employees}
|
||||||
|
labelKey={(item) =>
|
||||||
|
`${item.firstName} ${item.lastName}`
|
||||||
|
}
|
||||||
|
valueKey="id"
|
||||||
|
IsLoading={empLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex justify-content-end py-1 gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary btn-xs"
|
||||||
|
onClick={() => {
|
||||||
|
clearFilter();
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-primary btn-xs"
|
||||||
|
>
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
<div className="col-7 col-sm-8 text-end gap-2">
|
||||||
|
{IsCreatedAble && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-offset="0,8"
|
||||||
|
data-bs-placement="top"
|
||||||
|
data-bs-custom-class="tooltip"
|
||||||
|
title="Add New Expense"
|
||||||
|
className={`p-1 me-2 bg-primary rounded-circle `}
|
||||||
|
onClick={() =>
|
||||||
|
setManageExpenseModal({
|
||||||
|
IsOpen: true,
|
||||||
|
expenseId: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus fs-4 text-white"></i>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-7 col-sm-8 text-end gap-2">
|
|
||||||
{IsCreatedAble && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
data-bs-offset="0,8"
|
|
||||||
data-bs-placement="top"
|
|
||||||
data-bs-custom-class="tooltip"
|
|
||||||
title="Add New Expense"
|
|
||||||
className={`p-1 me-2 bg-primary rounded-circle `}
|
|
||||||
onClick={() =>
|
|
||||||
setManageExpenseModal({
|
|
||||||
IsOpen: true,
|
|
||||||
expenseId: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i className="bx bx-plus fs-4 text-white"></i>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ExpenseList filters={filters} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="card text-center py-1">
|
||||||
|
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
||||||
|
<p>
|
||||||
|
Access Denied: You don't have permission to perform this action. !
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<ExpenseList filters={filters} />
|
|
||||||
{ManageExpenseModal.IsOpen && (
|
{ManageExpenseModal.IsOpen && (
|
||||||
<GlobalModel
|
<GlobalModel
|
||||||
isOpen={ManageExpenseModal.IsOpen}
|
isOpen={ManageExpenseModal.IsOpen}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user