diff --git a/src/Context/FabContext.jsx b/src/Context/FabContext.jsx index 7151e6d7..d78d4078 100644 --- a/src/Context/FabContext.jsx +++ b/src/Context/FabContext.jsx @@ -4,9 +4,30 @@ const FabContext = createContext(); export const FabProvider = ({ children }) => { const [actions, setActions] = useState([]); + const [showTrigger, setShowTrigger] = useState(true); + const [isOffcanvasOpen, setIsOffcanvasOpen] = useState(false); + const [offcanvas, setOffcanvas] = useState({ + isOpen: false, + title: "", + content: null, + }); + + const openOffcanvas = (title, content) => { + setOffcanvas({ isOpen: true, title, content }); + setTimeout(() => { + const offcanvasElement = document.getElementById("globalOffcanvas"); + if (offcanvasElement) { + const bsOffcanvas = new window.bootstrap.Offcanvas(offcanvasElement); + bsOffcanvas.show(); + } + }, 100); + }; +const setOffcanvasContent = (title, content) => { + setOffcanvas(prev => ({ ...prev, title, content })); +}; return ( - + {children} ); diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 311fa763..2f52dd7f 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React,{useEffect} from "react"; import { useSelector } from "react-redux"; import { useDashboardProjectsCardData, @@ -14,16 +14,25 @@ import ProjectCompletionChart from "./ProjectCompletionChart"; import ProjectProgressChart from "./ProjectProgressChart"; import ProjectOverview from "../Project/ProjectOverview"; import AttendanceOverview from "./AttendanceChart"; +import { useFab } from "../../Context/FabContext"; const Dashboard = () => { const { projectsCardData } = useDashboardProjectsCardData(); const { teamsCardData } = useDashboardTeamsCardData(); const { tasksCardData } = useDashboardTasksCardData(); + const {setShowTrigger} = useFab() // Get the selected project ID from Redux store const projectId = useSelector((store) => store.localVariables.projectId); const isAllProjectsSelected = projectId === null; + + useEffect(() => { + setShowTrigger(false); + console.log("OffCanvas") + return () => setShowTrigger(true); + }, [setShowTrigger]) + return (
diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx new file mode 100644 index 00000000..51c8271a --- /dev/null +++ b/src/components/Expenses/ExpenseFilterPanel.jsx @@ -0,0 +1,152 @@ +// components/Expense/ExpenseFilterPanel.jsx +import React, { useEffect } from "react"; +import { FormProvider, useForm, Controller } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + defaultFilter, + SearchSchema, +} from "./ExpenseSchema"; + +import DateRangePicker from "../common/DateRangePicker"; +import SelectMultiple from "../common/SelectMultiple"; + +import { useProjectName } from "../../hooks/useProjects"; +import { useExpenseStatus } from "../../hooks/masterHook/useMaster"; +import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees"; +import { useSelector } from "react-redux"; + +const ExpenseFilterPanel = ({ onApply }) => { + const selectedProjectId = useSelector((store) => store.localVariables.projectId); + + const { projectNames, loading: projectLoading } = useProjectName(); + const { ExpenseStatus = [] } = useExpenseStatus(); + const { employees, loading: empLoading } = useEmployeesAllOrByProjectId( + true, + selectedProjectId, + true + ); + + const methods = useForm({ + resolver: zodResolver(SearchSchema), + defaultValues: defaultFilter, + }); + + const { control, handleSubmit, setValue, reset } = methods; + + const isValidDate = (date) => date instanceof Date && !isNaN(date); + const setDateRange = ({ startDate, endDate }) => { + const parsedStart = new Date(startDate); + 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 + ); + }; + + const closePanel = () => { + document.querySelector(".offcanvas.show .btn-close")?.click(); + }; + + const onSubmit = (data) => { + onApply(data); + closePanel(); + }; + + const onClear = () => { + reset(defaultFilter); + onApply(defaultFilter); + closePanel(); + }; + + return ( + +
+ +
+ +
+ + + +
+ + `${item.firstName} ${item.lastName}`} + valueKey="id" + IsLoading={empLoading} + /> + `${item.firstName} ${item.lastName}`} + valueKey="id" + IsLoading={empLoading} + /> + +
+ +
+ {ExpenseStatus.map((status) => ( + ( +
+ { + const checked = e.target.checked; + onChange( + checked + ? [...value, status.id] + : value.filter((v) => v !== status.id) + ); + }} + /> + +
+ )} + /> + ))} +
+
+
+ +
+ + +
+
+
+ ); +}; + +export default ExpenseFilterPanel; diff --git a/src/components/common/DateRangePicker.jsx b/src/components/common/DateRangePicker.jsx index a79c9d4b..d2fdb2b3 100644 --- a/src/components/common/DateRangePicker.jsx +++ b/src/components/common/DateRangePicker.jsx @@ -28,9 +28,9 @@ const DateRangePicker = ({ altFormat: "d-m-Y", defaultDate: [startDate, endDate], static: false, - appendTo: document.body, + // appendTo: document.body, clickOpens: true, - maxDate: endDate, // ✅ Disable future dates + maxDate: endDate, onChange: (selectedDates, dateStr) => { const [startDateString, endDateString] = dateStr.split(" To "); onRangeChange?.({ startDate: startDateString, endDate: endDateString }); @@ -51,23 +51,15 @@ const DateRangePicker = ({
diff --git a/src/components/common/GlobalOffcanvas .jsx b/src/components/common/GlobalOffcanvas .jsx new file mode 100644 index 00000000..bc7b09a7 --- /dev/null +++ b/src/components/common/GlobalOffcanvas .jsx @@ -0,0 +1,52 @@ +import React, { useEffect, useRef } from "react"; +import { useFab } from "../../Context/FabContext"; + +const GlobalOffcanvas = () => { + const { offcanvas, setIsOffcanvasOpen } = useFab(); + const offcanvasRef = useRef(); + + useEffect(() => { + const el = offcanvasRef.current; + const bsOffcanvas = new bootstrap.Offcanvas(el); + + const handleShow = () => setIsOffcanvasOpen(true); + const handleHide = () => setIsOffcanvasOpen(false); + + el.addEventListener("show.bs.offcanvas", handleShow); + el.addEventListener("hidden.bs.offcanvas", handleHide); + + return () => { + el.removeEventListener("show.bs.offcanvas", handleShow); + el.removeEventListener("hidden.bs.offcanvas", handleHide); + }; + }, []); + + return ( +
+
+
+ {offcanvas.title} +
+ +
+
+ {offcanvas.content ||

No content

} +
+
+ ); +}; + +export default GlobalOffcanvas; diff --git a/src/components/common/OffcanvasTrigger.jsx b/src/components/common/OffcanvasTrigger.jsx new file mode 100644 index 00000000..3c74fb14 --- /dev/null +++ b/src/components/common/OffcanvasTrigger.jsx @@ -0,0 +1,26 @@ +import { createPortal } from "react-dom"; +import { useFab } from "../../Context/FabContext"; + +const OffcanvasTrigger = () => { + const { openOffcanvas, offcanvas, showTrigger } = useFab(); + + if (!showTrigger || !offcanvas.content) return null; + + const btn = ( + openOffcanvas(offcanvas.title, offcanvas.content)} + role="button" + style={{ + top: "25%", + right: "0%", + cursor: "pointer", + zIndex: 1056, + }} + /> + ); + + return createPortal(btn, document.body); +}; + +export default OffcanvasTrigger; diff --git a/src/layouts/HomeLayout.jsx b/src/layouts/HomeLayout.jsx index 34639ee0..0de9e4b9 100644 --- a/src/layouts/HomeLayout.jsx +++ b/src/layouts/HomeLayout.jsx @@ -7,6 +7,8 @@ import Footer from "../components/Layout/Footer"; import FloatingMenu from "../components/common/FloatingMenu"; import { FabProvider } from "../Context/FabContext"; import { useSelector } from "react-redux"; +import OffcanvasTrigger from "../components/common/OffcanvasTrigger"; +import GlobalOffcanvas from "../components/common/GlobalOffcanvas "; const HomeLayout = () => { const loggedUser = useSelector((store) => store.globalVariables.loginUser); @@ -34,9 +36,11 @@ const HomeLayout = () => {
+
+ ); diff --git a/src/pages/Activities/AttendancePage.jsx b/src/pages/Activities/AttendancePage.jsx index 0aaa4b43..a8100d11 100644 --- a/src/pages/Activities/AttendancePage.jsx +++ b/src/pages/Activities/AttendancePage.jsx @@ -8,24 +8,19 @@ import { import Breadcrumb from "../../components/common/Breadcrumb"; import AttendanceLog from "../../components/Activities/AttendcesLogs"; import Attendance from "../../components/Activities/Attendance"; -// import AttendanceModel from "../../components/Activities/AttendanceModel"; import showToast from "../../services/toastService"; -// import { useProjects } from "../../hooks/useProjects"; import Regularization from "../../components/Activities/Regularization"; import { useAttendance } from "../../hooks/useAttendance"; import { useDispatch, useSelector } from "react-redux"; import { setProjectId } from "../../slices/localVariablesSlice"; -// import { markCurrentAttendance } from "../../slices/apiSlice/attendanceAllSlice"; import { hasUserPermission } from "../../utils/authUtils"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { REGULARIZE_ATTENDANCE } from "../../utils/constants"; import eventBus from "../../services/eventBus"; -// import AttendanceRepository from "../../repositories/AttendanceRepository"; import { useProjectName } from "../../hooks/useProjects"; import GlobalModel from "../../components/common/GlobalModel"; import CheckCheckOutmodel from "../../components/Activities/CheckCheckOutForm"; import AttendLogs from "../../components/Activities/AttendLogs"; -// import Confirmation from "../../components/Activities/Confirmation"; import { useQueryClient } from "@tanstack/react-query"; const AttendancePage = () => { @@ -35,11 +30,7 @@ const AttendancePage = () => { const loginUser = getCachedProfileData(); var selectedProject = useSelector((store) => store.localVariables.projectId); const dispatch = useDispatch(); - // const { - // attendance, - // loading: attLoading, - // recall: attrecall, - // } = useAttendance(selectedProject); + const [attendances, setAttendances] = useState(); const [empRoles, setEmpRoles] = useState(null); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); @@ -53,33 +44,6 @@ const AttendancePage = () => { date: new Date().toLocaleDateString(), }); - // const handler = useCallback( - // (msg) => { - // if (selectedProject == msg.projectId) { - // const updatedAttendance = attendances.map((item) => - // item.employeeId === msg.response.employeeId - // ? { ...item, ...msg.response } - // : item - // ); - // queryClient.setQueryData(["attendance", selectedProject], (oldData) => { - // if (!oldData) return oldData; - // return oldData.map((emp) => - // emp.employeeId === data.employeeId ? { ...emp, ...data } : emp - // ); - // }); - // } - // }, - // [selectedProject, attrecall] - // ); - - // const employeeHandler = useCallback( - // (msg) => { - // if (attendances.some((item) => item.employeeId == msg.employeeId)) { - // attrecall(); - // } - // }, - // [selectedProject, attendances] - // ); useEffect(() => { if (selectedProject == null) { dispatch(setProjectId(projectNames[0]?.id)); @@ -117,32 +81,9 @@ const AttendancePage = () => { } }, [modelConfig, isCreateModalOpen]); - // useEffect(() => { - // eventBus.on("attendance", handler); - // return () => eventBus.off("attendance", handler); - // }, [handler]); - - // useEffect(() => { - // eventBus.on("employee", employeeHandler); - // return () => eventBus.off("employee", employeeHandler); - // }, [employeeHandler]); return ( <> - {/* {isCreateModalOpen && modelConfig && ( - - )} */} + {isCreateModalOpen && modelConfig && ( { reset(); }; + const { setOffcanvasContent, setShowTrigger } = useFab(); + useEffect(() => { + setShowTrigger(true); + + setOffcanvasContent( + "Expense Filters", + setFilter(data)} + /> + ); + return () => { + setOffcanvasContent("", null); + setShowTrigger(false); + }; + }, []); + return (
@@ -235,7 +253,7 @@ const ExpensePage = () => { ref={dropdownRef} >