fixed check ehcke issue

This commit is contained in:
pramod.mahajan 2025-10-30 17:39:24 +05:30
parent 166b0a1caf
commit 0d1d51d56b
7 changed files with 195 additions and 131 deletions

View File

@ -127,3 +127,11 @@
.fs-md-xlarge { font-size: 170% !important; } .fs-md-xlarge { font-size: 170% !important; }
.fs-md-xxlarge { font-size: calc(1.725rem + 5.7vw) !important; } .fs-md-xxlarge { font-size: calc(1.725rem + 5.7vw) !important; }
} }
/* Tables */
.table th.actions-col,
.table td.actions-col {
width: 1%;
white-space: nowrap;
text-align: center;
}

View File

@ -12,8 +12,17 @@ import { useQueryClient } from "@tanstack/react-query";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import Pagination from "../common/Pagination"; import Pagination from "../common/Pagination";
import { SpinnerLoader } from "../common/Loader";
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, includeInactive, date }) => { const Attendance = ({
getRole,
handleModalData,
searchTerm,
projectId,
organizationId,
includeInactive,
date,
}) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
@ -24,12 +33,12 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
attendance, attendance,
loading: attLoading, loading: attLoading,
recall: attrecall, recall: attrecall,
isFetching isFetching,
} = useAttendance(selectedProject, organizationId, includeInactive, date); } = useAttendance(selectedProject, organizationId, includeInactive, date);
const filteredAttendance = ShowPending const filteredAttendance = ShowPending
? attendance?.filter( ? attendance?.filter(
(att) => att?.checkInTime !== null && att?.checkOutTime === null (att) => att?.checkInTime !== null && att?.checkOutTime === null
) )
: attendance; : attendance;
const attendanceList = Array.isArray(filteredAttendance) const attendanceList = Array.isArray(filteredAttendance)
@ -71,19 +80,19 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
); );
// Reset pagination when the filter or search term changes // Reset pagination when the filter or search term changes
useEffect(() => { useEffect(() => {}, [finalFilteredData]);
}, [finalFilteredData]);
const handler = useCallback( const handler = useCallback(
(msg) => { (msg) => {
if (selectedProject == msg.projectId) { if (selectedProject == msg.projectId) {
queryClient.setQueryData(["attendance", selectedProject], (oldData) => { queryClient.setQueryData(["attendance", selectedProject], (oldData) => {
if (!oldData) { if (!oldData) {
queryClient.invalidateQueries({ queryKey: ["attendance"] }) queryClient.invalidateQueries({ queryKey: ["attendance"] });
}; }
return oldData.map((record) => return oldData.map((record) =>
record.employeeId === msg.response.employeeId ? { ...record, ...msg.response } : record record.employeeId === msg.response.employeeId
? { ...record, ...msg.response }
: record
); );
}); });
} }
@ -109,16 +118,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
return () => eventBus.off("employee", employeeHandler); return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]); }, [employeeHandler]);
return ( return (
<> <>
<div> <div>
<div <div className="table-responsive text-nowrap ">
className="table-responsive text-nowrap h-100" <div className="d-flex justify-content-between align-items-center py-2">
style={{ minHeight: "200px" }} // Ensures fixed height
>
<div className="d-flex text-start align-items-center py-2">
<strong>Date : {formatUTCToLocalTime(todayDate)}</strong> <strong>Date : {formatUTCToLocalTime(todayDate)}</strong>
<div className="form-check form-switch text-start m-0 ms-5"> <div className="form-check form-switch text-start m-0 ms-5">
<input <input
@ -134,23 +138,29 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
</div> </div>
</div> </div>
{attLoading ? ( {attLoading ? (
<div>Loading...</div> <div
className="d-flex justify-content-center align-items-center"
style={{ minHeight: "70vh" }}
>
<SpinnerLoader />
</div>
) : currentItems?.length > 0 ? ( ) : currentItems?.length > 0 ? (
<> <>
<table className="table "> <table className="table table-hover ">
<thead> <thead>
<tr className="border-top-1"> <tr className="border-top-1">
<th colSpan={2}>Name</th> <th colSpan={2}>Name</th>
<th>Role</th> <th className="text-start actions-col text-center">Role</th>
{/* <th>Organization</th> */} {/* <th>Organization</th> */}
<th> <th>
<i className="bx bxs-down-arrow-alt text-success"></i> <i className="bx bxs-down-arrow-alt text-success"></i>
Check-In Check-In
</th> </th>
<th> <th>
<i className="bx bxs-up-arrow-alt text-danger"></i>Check-Out <i className="bx bxs-up-arrow-alt text-danger"></i>
Check-Out
</th> </th>
<th>Actions</th> <th className="actions-col">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody className="table-border-bottom-0 "> <tbody className="table-border-bottom-0 ">
@ -190,7 +200,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
</div> </div>
</td> </td>
<td>{item.jobRoleName}</td> <td className="text-start action-col">{item.jobRoleName}</td>
{/* <td>{item.organizationName || "--"}</td> */} {/* <td>{item.organizationName || "--"}</td> */}
<td> <td>
@ -204,7 +214,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
: "--"} : "--"}
</td> </td>
<td className="text-center"> <td className="text-center actions-col">
<RenderAttendanceStatus <RenderAttendanceStatus
attendanceData={item} attendanceData={item}
handleModalData={handleModalData} handleModalData={handleModalData}
@ -236,8 +246,8 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
{searchTerm {searchTerm
? "No results found for your search." ? "No results found for your search."
: attendanceList.length === 0 : attendanceList.length === 0
? "No employees assigned to the project." ? "No employees assigned to the project."
: "No pending records available."} : "No pending records available."}
</div> </div>
)} )}
</div> </div>

View File

@ -5,7 +5,11 @@ import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils";
import RenderAttendanceStatus from "./RenderAttendanceStatus"; import RenderAttendanceStatus from "./RenderAttendanceStatus";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import DateRangePicker from "../common/DateRangePicker"; import DateRangePicker from "../common/DateRangePicker";
import { clearCacheKey, getCachedData, useSelectedProject } from "../../slices/apiDataManager"; import {
clearCacheKey,
getCachedData,
useSelectedProject,
} from "../../slices/apiDataManager";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import AttendanceRepository from "../../repositories/AttendanceRepository"; import AttendanceRepository from "../../repositories/AttendanceRepository";
import { useAttendancesLogs } from "../../hooks/useAttendance"; import { useAttendancesLogs } from "../../hooks/useAttendance";
@ -13,6 +17,7 @@ import { queryClient } from "../../layouts/AuthLayout";
import { ITEMS_PER_PAGE } from "../../utils/constants"; import { ITEMS_PER_PAGE } from "../../utils/constants";
import Pagination from "../common/Pagination"; import Pagination from "../common/Pagination";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { SpinnerLoader } from "../common/Loader";
const usePagination = (data, itemsPerPage) => { const usePagination = (data, itemsPerPage) => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
@ -36,14 +41,11 @@ const usePagination = (data, itemsPerPage) => {
}; };
const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => { const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
// const selectedProject = useSelector(
// (store) => store.localVariables.projectId
// );
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
const dispatch = useDispatch(); const dispatch = useDispatch();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [showPending, setShowPending] = useState(false) const [showPending, setShowPending] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
const [processedData, setProcessedData] = useState([]); const [processedData, setProcessedData] = useState([]);
@ -87,57 +89,65 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
dateRange.endDate, dateRange.endDate,
organizationId organizationId
); );
const filtering = useCallback((dataToFilter) => { const filtering = useCallback(
const filteredData = showPending (dataToFilter) => {
? dataToFilter.filter((item) => item.checkOutTime === null) const filteredData = showPending
: dataToFilter; ? dataToFilter.filter((item) => item.checkOutTime === null)
: dataToFilter;
const group1 = filteredData const group1 = filteredData
.filter((d) => d.activity === 1 && isSameDay(d.checkInTime)) .filter((d) => d.activity === 1 && isSameDay(d.checkInTime))
.sort(sortByName); .sort(sortByName);
const group2 = filteredData const group2 = filteredData
.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)) .filter((d) => d.activity === 4 && isSameDay(d.checkOutTime))
.sort(sortByName); .sort(sortByName);
const group3 = filteredData const group3 = filteredData
.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime)) .filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime))
.sort(sortByName); .sort(sortByName);
const group4 = filteredData.filter( const group4 = filteredData.filter(
(d) => d.activity === 4 && isBeforeToday(d.checkOutTime) (d) => d.activity === 4 && isBeforeToday(d.checkOutTime)
); );
const group5 = filteredData const group5 = filteredData
.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime)) .filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime))
.sort(sortByName); .sort(sortByName);
const group6 = filteredData const group6 = filteredData
.filter((d) => d.activity === 5) .filter((d) => d.activity === 5)
.sort(sortByName); .sort(sortByName);
const sortedList = [...group1, ...group2, ...group3, ...group4, ...group5, ...group6]; const sortedList = [
...group1,
...group2,
...group3,
...group4,
...group5,
...group6,
];
// Group by date // Group by date
const groupedByDate = sortedList.reduce((acc, item) => { const groupedByDate = sortedList.reduce((acc, item) => {
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0]; const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
if (date) { if (date) {
acc[date] = acc[date] || []; acc[date] = acc[date] || [];
acc[date].push(item); acc[date].push(item);
} }
return acc; return acc;
}, {}); }, {});
const sortedDates = Object.keys(groupedByDate).sort( const sortedDates = Object.keys(groupedByDate).sort(
(a, b) => new Date(b) - new Date(a) (a, b) => new Date(b) - new Date(a)
); );
const finalData = sortedDates.flatMap((date) => groupedByDate[date]); const finalData = sortedDates.flatMap((date) => groupedByDate[date]);
setProcessedData(finalData); setProcessedData(finalData);
}, [showPending]); },
[showPending]
);
useEffect(() => {
if (data?.length) {
filtering(data);
}
}, [data, showPending]);
useEffect(() => {
if (data?.length) {
filtering(data);
}
}, [data, showPending]);
// New useEffect to handle search filtering // New useEffect to handle search filtering
const filteredSearchData = useMemo(() => { const filteredSearchData = useMemo(() => {
@ -151,8 +161,6 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
}); });
}, [processedData, searchTerm]); }, [processedData, searchTerm]);
const { const {
currentPage, currentPage,
totalPages, totalPages,
@ -210,7 +218,7 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
// }) // })
// ); // );
refetch() refetch();
} }
}, },
[selectedProject, dateRange, data, refetch] [selectedProject, dateRange, data, refetch]
@ -221,29 +229,29 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
return () => eventBus.off("employee", employeeHandler); return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]); }, [employeeHandler]);
return ( return (
<> <>
<div <div
className="dataTables_length text-start py-2 d-flex justify-content-between" className="dataTables_length text-start py-2 d-flex justify-content-between "
id="DataTables_Table_0_length" id="DataTables_Table_0_length"
> >
<div className=" col-12"> <div className=" col-12">
<DateRangePicker <DateRangePicker
onRangeChange={setDateRange} onRangeChange={setDateRange}
defaultStartDate={yesterday} defaultStartDate={yesterday}
/> />
</div> </div>
</div> </div>
<div className="table-responsive text-nowrap " > <div className="table-responsive text-nowrap ">
{isLoading ? ( {isLoading ? (
<div className="d-flex justify-content-center align-items-center"> <div
<p className="text-secondary">Loading...</p> className="d-flex justify-content-center align-items-center"
style={{ minHeight: "70vh" }}
>
<SpinnerLoader/>
</div> </div>
) : filteredSearchData?.length > 0 ? ( ) : filteredSearchData?.length > 0 ? (
<table className="table mb-0"> <table className="table mb-0 table-hover">
<thead> <thead>
<tr> <tr>
<th className="border-top-1" colSpan={2}> <th className="border-top-1" colSpan={2}>
@ -253,12 +261,13 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
{/* <th>Organization</th> */} {/* <th>Organization</th> */}
<th> <th>
<i className="bx bxs-down-arrow-alt text-success"></i> Check-In <i className="bx bxs-down-arrow-alt text-success"></i>{" "}
Check-In
</th> </th>
<th> <th>
<i className="bx bxs-up-arrow-alt text-danger"></i> Check-Out <i className="bx bxs-up-arrow-alt text-danger"></i> Check-Out
</th> </th>
<th>Action</th> <th className="actions-col">Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -269,9 +278,9 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
const previousAttendance = arr[index - 1]; const previousAttendance = arr[index - 1];
const previousDate = previousAttendance const previousDate = previousAttendance
? moment( ? moment(
previousAttendance.checkInTime || previousAttendance.checkInTime ||
previousAttendance.checkOutTime previousAttendance.checkOutTime
).format("YYYY-MM-DD") ).format("YYYY-MM-DD")
: null; : null;
if (!previousDate || currentDate !== previousDate) { if (!previousDate || currentDate !== previousDate) {
@ -281,8 +290,8 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
className="table-row-header" className="table-row-header"
> >
<td colSpan={8} className="text-start"> <td colSpan={8} className="text-start">
<strong className="mx-2"> <strong className="d-inline-block my-1 ms-2">
{formatUTCToLocalTime(currentDate)} {formatUTCToLocalTime(currentDate)}
</strong> </strong>
</td> </td>
</tr> </tr>
@ -299,7 +308,9 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<a <a
onClick={() => onClick={() =>
navigate(`/employee/${attendance.employeeId}?for=attendance`) navigate(
`/employee/${attendance.employeeId}?for=attendance`
)
} }
className="text-heading text-truncate cursor-pointer" className="text-heading text-truncate cursor-pointer"
> >
@ -322,7 +333,7 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
? convertShortTime(attendance.checkOutTime) ? convertShortTime(attendance.checkOutTime)
: "--"} : "--"}
</td> </td>
<td className="text-center"> <td className="text-center actions-col">
<RenderAttendanceStatus <RenderAttendanceStatus
attendanceData={attendance} attendanceData={attendance}
handleModalData={handleModalData} handleModalData={handleModalData}
@ -337,7 +348,14 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
</tbody> </tbody>
</table> </table>
) : ( ) : (
<div className="my-12"><span className="text-secondary">No data for this date range. Please choose another.</span></div> <div
className="d-flex justify-content-center align-items-center"
style={{ minHeight: "70vh" }}
>
<p className="text-secondary mb-0">
No data for this date range. Please choose another.
</p>
</div>
)} )}
</div> </div>
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && ( {paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (

View File

@ -12,6 +12,7 @@ import { useQueryClient } from "@tanstack/react-query";
import Pagination from "../common/Pagination"; import Pagination from "../common/Pagination";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { employee } from "../../data/masters"; import { employee } from "../../data/masters";
import { SpinnerLoader } from "../common/Loader";
const Regularization = ({ handleRequest, searchTerm, projectId, organizationId, IncludeInActive }) => { const Regularization = ({ handleRequest, searchTerm, projectId, organizationId, IncludeInActive }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -135,11 +136,14 @@ const Regularization = ({ handleRequest, searchTerm, projectId, organizationId,
<div> <div>
<div className="table-responsive text-nowrap pb-4" style={{ minHeight: "200px" }}> <div className="table-responsive text-nowrap pb-4" style={{ minHeight: "200px" }}>
{loading ? ( {loading ? (
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}> <div
<p className="text-secondary">Loading...</p> className="d-flex justify-content-center align-items-center"
style={{ minHeight: "70vh" }}
>
<SpinnerLoader/>
</div> </div>
) : currentItems?.length > 0 ? ( ) : currentItems?.length > 0 ? (
<table className="table mb-0"> <table className="table mb-0 table-hover">
<thead> <thead>
<tr> <tr>
<th colSpan={2}>Name</th> <th colSpan={2}>Name</th>
@ -154,7 +158,7 @@ const Regularization = ({ handleRequest, searchTerm, projectId, organizationId,
<th>Request By</th> <th>Request By</th>
<th>Requested At</th> <th>Requested At</th>
<th>Action</th> <th className="actions-col">Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -19,6 +19,13 @@ import { useProjectName } from "../../hooks/useProjects";
import ExpenseAnalysis from "./ExpenseAnalysis"; import ExpenseAnalysis from "./ExpenseAnalysis";
import ExpenseStatus from "./ExpenseStatus"; import ExpenseStatus from "./ExpenseStatus";
import ExpenseByProject from "./ExpenseByProject"; import ExpenseByProject from "./ExpenseByProject";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import {
APPROVE_EXPENSE,
EXPENSE_MANAGE,
VIEW_ALL_EXPNESE,
} from "../../utils/constants";
import { useHasAnyPermission } from "../../hooks/useExpense";
const Dashboard = () => { const Dashboard = () => {
// const { projectsCardData } = useDashboardProjectsCardData(); // const { projectsCardData } = useDashboardProjectsCardData();
@ -29,34 +36,40 @@ const Dashboard = () => {
const projectId = useSelector((store) => store.localVariables.projectId); const projectId = useSelector((store) => store.localVariables.projectId);
const isAllProjectsSelected = projectId === null; const isAllProjectsSelected = projectId === null;
const isViewExpense = useHasAnyPermission(
VIEW_ALL_EXPNESE,
APPROVE_EXPENSE,
EXPENSE_MANAGE
);
return ( return (
<div className="container-fluid py-5"> <div className="container-fluid py-5">
<div className="row mb-6 g-6"> {isViewExpense && (
<div className="col-12 col-xl-8"> <div className="row mb-6 g-6">
<div className="card h-100"> <div className="col-12 col-xl-8">
<ExpenseAnalysis /> <div className="card h-100">
<ExpenseAnalysis />
</div>
</div>
<div className="col-12 col-xl-4 col-md-6">
<div className="card h-100">
<ExpenseStatus />
</div>
</div>
</div>
)}
<div className="row vh-100">
{!isAllProjectsSelected && (
<div className="col-12 col-md-6 mb-sm-0 mb-4 ">
<AttendanceOverview />
</div>
)}
<div className="col-12 col-md-6">
<ExpenseByProject />
</div>
</div> </div>
</div> </div>
<div className="col-12 col-xl-4 col-md-6">
<div className="card h-100">
<ExpenseStatus />
</div>
</div>
</div>
<div className="row vh-100">
{!isAllProjectsSelected && (
<div className="col-12 col-md-6 mb-sm-0 mb-4 ">
<AttendanceOverview />
</div>
)}
<div className="col-12 col-md-6">
<ExpenseByProject />
</div>
</div>
</div>
); );
}; };

View File

@ -19,3 +19,14 @@ const Loader = () => {
export default Loader; export default Loader;
export const SpinnerLoader = ()=>{
return (
<div className="text-center">
<div className="spinner-border text-primary mb-3" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<p className="text-secondary mb-0">Loading attendance data...</p>
</div>
)
}

View File

@ -169,7 +169,7 @@ const AttendancePage = () => {
</div> </div>
{/* Search + Organization filter */} {/* Search + Organization filter */}
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto d-flex gap-2 align-items-center"> <div className="col-12 col-md-auto pb-2 mt-md-0 ms-md-auto d-flex gap-2 align-items-center nav-tabs">
{/* Organization Dropdown */} {/* Organization Dropdown */}
{/* <select {/* <select
className="form-select form-select-sm" className="form-select form-select-sm"
@ -206,7 +206,7 @@ const AttendancePage = () => {
</div> </div>
</div> </div>
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3 pb-10"> <div className="tab-content attedanceTabs py-0 px-1 px-sm-3 pb-10 page-min-h">
<> <>
{activeTab === "all" && ( {activeTab === "all" && (