Merge pull request 'Issues_Aug_1W' (#355) from Issues_Aug_1W into main

Reviewed-on: #355
merged
This commit is contained in:
pramod.mahajan 2025-08-23 11:09:24 +00:00
commit 18390e9368
36 changed files with 1464 additions and 1151 deletions

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback, useMemo } from "react";
import moment from "moment"; import moment from "moment";
import Avatar from "../common/Avatar"; import Avatar from "../common/Avatar";
import { convertShortTime } from "../../utils/dateUtils"; import { convertShortTime } from "../../utils/dateUtils";
@ -10,16 +10,18 @@ import { useAttendance } from "../../hooks/useAttendance";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { useSelectedproject } from "../../slices/apiDataManager";
const Attendance = ({ getRole, handleModalData }) => { const Attendance = ({ getRole, handleModalData, searchTerm }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const [todayDate, setTodayDate] = useState(new Date()); const [todayDate, setTodayDate] = useState(new Date());
const [ShowPending, setShowPending] = useState(false); const [ShowPending, setShowPending] = useState(false);
const selectedProject = useSelector( // const selectedProject = useSelector(
(store) => store.localVariables.projectId // (store) => store.localVariables.projectId
); // );
const selectedProject = useSelectedproject();
const { const {
attendance, attendance,
loading: attLoading, loading: attLoading,
@ -28,8 +30,8 @@ const Attendance = ({ getRole, handleModalData }) => {
} = useAttendance(selectedProject); } = useAttendance(selectedProject);
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)
@ -48,18 +50,40 @@ const Attendance = ({ getRole, handleModalData }) => {
.filter((d) => d.activity === 0) .filter((d) => d.activity === 0)
.sort(sortByName); .sort(sortByName);
const filteredData = [...group1, ...group2]; const finalFilteredData = useMemo(() => {
const combinedData = [...group1, ...group2];
if (!searchTerm) {
return combinedData;
}
const lowercasedSearchTerm = searchTerm.toLowerCase();
return combinedData.filter((item) => {
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
const role = item.jobRoleName?.toLowerCase() || "";
return (
fullName.includes(lowercasedSearchTerm) ||
role.includes(lowercasedSearchTerm) // also search by role
);
});
}, [group1, group2, searchTerm]);
const { currentPage, totalPages, currentItems, paginate } = usePagination( const { currentPage, totalPages, currentItems, paginate } = usePagination(
filteredData, finalFilteredData,
ITEMS_PER_PAGE ITEMS_PER_PAGE
); );
// Reset pagination when the filter or search term changes
useEffect(() => {
}, [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
@ -72,7 +96,7 @@ const Attendance = ({ getRole, handleModalData }) => {
const employeeHandler = useCallback( const employeeHandler = useCallback(
(msg) => { (msg) => {
if (attendances.some((item) => item.employeeId == msg.employeeId)) { if (attendance.some((item) => item.employeeId == msg.employeeId)) {
attrecall(); attrecall();
} }
}, },
@ -106,7 +130,9 @@ const Attendance = ({ getRole, handleModalData }) => {
<label className="form-check-label ms-0">Show Pending</label> <label className="form-check-label ms-0">Show Pending</label>
</div> </div>
</div> </div>
{Array.isArray(attendance) && attendance.length > 0 ? ( {attLoading ? (
<div>Loading...</div>
) : currentItems?.length > 0 ? (
<> <>
<table className="table "> <table className="table ">
<thead> <thead>
@ -188,13 +214,12 @@ const Attendance = ({ getRole, handleModalData }) => {
</tbody> </tbody>
</table> </table>
{!loading && filteredData.length > 20 && ( {!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
<nav aria-label="Page "> <nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1"> <ul className="pagination pagination-sm justify-content-end py-1">
<li <li
className={`page-item ${ className={`page-item ${currentPage === 1 ? "disabled" : ""
currentPage === 1 ? "disabled" : "" }`}
}`}
> >
<button <button
className="page-link btn-xs" className="page-link btn-xs"
@ -206,9 +231,8 @@ const Attendance = ({ getRole, handleModalData }) => {
{[...Array(totalPages)].map((_, index) => ( {[...Array(totalPages)].map((_, index) => (
<li <li
key={index} key={index}
className={`page-item ${ className={`page-item ${currentPage === index + 1 ? "active" : ""
currentPage === index + 1 ? "active" : "" }`}
}`}
> >
<button <button
className="page-link " className="page-link "
@ -219,9 +243,8 @@ const Attendance = ({ getRole, handleModalData }) => {
</li> </li>
))} ))}
<li <li
className={`page-item ${ className={`page-item ${currentPage === totalPages ? "disabled" : ""
currentPage === totalPages ? "disabled" : "" }`}
}`}
> >
<button <button
className="page-link " className="page-link "
@ -234,19 +257,15 @@ const Attendance = ({ getRole, handleModalData }) => {
</nav> </nav>
)} )}
</> </>
) : attLoading ? (
<div>Loading...</div>
) : ( ) : (
<div className="text-muted"> <div className="text-muted my-4">
{Array.isArray(attendance) {searchTerm
? "No employees assigned to the project" ? "No results found for your search."
: "Attendance data unavailable"} : attendanceList.length === 0
? "No employees assigned to the project."
: "No pending records available."}
</div> </div>
)} )}
{currentItems?.length == 0 && attendance.length > 0 && (
<div className="my-4"><span className="text-secondary">No Pending Record Available !</span></div>
)}
</div> </div>
</> </>
); );

View File

@ -6,7 +6,7 @@ import RenderAttendanceStatus from "./RenderAttendanceStatus";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice"; import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
import DateRangePicker from "../common/DateRangePicker"; import DateRangePicker from "../common/DateRangePicker";
import { clearCacheKey, getCachedData } 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";
@ -33,12 +33,11 @@ const usePagination = (data, itemsPerPage) => {
}; };
}; };
const AttendanceLog = ({ const AttendanceLog = ({ handleModalData, searchTerm }) => {
handleModalData, // const selectedProject = useSelector(
}) => { // (store) => store.localVariables.projectId
const selectedProject = useSelector( // );
(store) => store.localVariables.projectId 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);
@ -139,17 +138,29 @@ const AttendanceLog = ({
filtering(data); filtering(data);
}, [data, showPending]); }, [data, showPending]);
// New useEffect to handle search filtering
const filteredSearchData = useMemo(() => {
if (!searchTerm) {
return processedData;
}
const lowercasedSearchTerm = searchTerm.toLowerCase();
return processedData.filter((item) => {
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
return fullName.includes(lowercasedSearchTerm);
});
}, [processedData, searchTerm]);
const { const {
currentPage, currentPage,
totalPages, totalPages,
currentItems: paginatedAttendances, currentItems: paginatedAttendances,
paginate, paginate,
resetPage, resetPage,
} = usePagination(processedData, 20); } = usePagination(filteredSearchData, 20);
useEffect(() => { useEffect(() => {
resetPage(); resetPage();
}, [processedData, resetPage]); }, [filteredSearchData, resetPage]);
const handler = useCallback( const handler = useCallback(
(msg) => { (msg) => {
@ -160,20 +171,23 @@ const AttendanceLog = ({
startDate <= checkIn && startDate <= checkIn &&
checkIn <= endDate checkIn <= endDate
) { ) {
queryClient.setQueriesData(["attendanceLogs"],(oldData)=>{ queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
if(!oldData) { if (!oldData) {
queryClient.invalidateQueries({queryKey:["attendanceLogs"]}) queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
return;
} }
return oldData.map((record) => const updatedAttendance = oldData.map((record) =>
record.id === msg.response.id ? { ...record, ...msg.response } : record record.id === msg.response.id
); ? { ...record, ...msg.response }
}) : record
);
filtering(updatedAttendance); filtering(updatedAttendance);
return updatedAttendance;
});
resetPage(); resetPage();
} }
}, },
[selectedProject, dateRange, data, filtering, resetPage] [selectedProject, dateRange, filtering, resetPage]
); );
useEffect(() => { useEffect(() => {
@ -196,7 +210,7 @@ const AttendanceLog = ({
refetch() refetch()
} }
}, },
[selectedProject, dateRange, data] [selectedProject, dateRange, data, refetch]
); );
useEffect(() => { useEffect(() => {
@ -240,8 +254,10 @@ const AttendanceLog = ({
</div> </div>
<div className="table-responsive text-nowrap"> <div className="table-responsive text-nowrap">
{isLoading ? ( {isLoading ? (
<div><p className="text-secondary">Loading...</p></div> <div>
) : data?.length > 0 ? ( <p className="text-secondary">Loading...</p>
</div>
) : filteredSearchData?.length > 0 ? (
<table className="table mb-0"> <table className="table mb-0">
<thead> <thead>
<tr> <tr>
@ -332,10 +348,12 @@ const AttendanceLog = ({
<div className="my-4"><span className="text-secondary">No Record Available !</span></div> <div className="my-4"><span className="text-secondary">No Record Available !</span></div>
)} )}
</div> </div>
{paginatedAttendances?.length == 0 && data?.length > 0 && ( {paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (
<div className="my-4"><span className="text-secondary">No Pending Record Available !</span></div> <div className="my-4">
)} <span className="text-secondary">No Pending Record Available !</span>
{processedData.length > 10 && ( </div>
)}
{filteredSearchData.length > 10 && (
<nav aria-label="Page "> <nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1"> <ul className="pagination pagination-sm justify-content-end py-1">
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}> <li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>

View File

@ -9,6 +9,7 @@ import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { checkIfCurrentDate } from "../../utils/dateUtils"; import { checkIfCurrentDate } from "../../utils/dateUtils";
import { useMarkAttendance } from "../../hooks/useAttendance"; import { useMarkAttendance } from "../../hooks/useAttendance";
import { useSelectedproject } from "../../slices/apiDataManager";
const createSchema = (modeldata) => { const createSchema = (modeldata) => {
return z return z
@ -43,7 +44,8 @@ const createSchema = (modeldata) => {
}; };
const CheckCheckOutmodel = ({ modeldata, closeModal, handleSubmitForm }) => { const CheckCheckOutmodel = ({ modeldata, closeModal, handleSubmitForm }) => {
const projectId = useSelector((store) => store.localVariables.projectId); // const projectId = useSelector((store) => store.localVariables.projectId);
const projectId = useSelectedproject();
const { mutate: MarkAttendance } = useMarkAttendance(); const { mutate: MarkAttendance } = useMarkAttendance();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const coords = usePositionTracker(); const coords = usePositionTracker();

View File

@ -15,13 +15,15 @@ import {useDispatch, useSelector} from "react-redux";
import {useProfile} from "../../hooks/useProfile"; import {useProfile} from "../../hooks/useProfile";
import {refreshData, setProjectId} from "../../slices/localVariablesSlice"; import {refreshData, setProjectId} from "../../slices/localVariablesSlice";
import InfraTable from "../Project/Infrastructure/InfraTable"; import InfraTable from "../Project/Infrastructure/InfraTable";
import { useSelectedproject } from "../../slices/apiDataManager";
const InfraPlanning = () => const InfraPlanning = () =>
{ {
const {profile: LoggedUser, refetch : fetchData} = useProfile() const {profile: LoggedUser, refetch : fetchData} = useProfile()
const dispatch = useDispatch() const dispatch = useDispatch()
const selectedProject = useSelector((store)=>store.localVariables.projectId) // const selectedProject = useSelector((store)=>store.localVariables.projectId)
const selectedProject = useSelectedproject();
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject ) const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState, useMemo } from "react";
import Avatar from "../common/Avatar"; import Avatar from "../common/Avatar";
import { convertShortTime } from "../../utils/dateUtils"; import { convertShortTime } from "../../utils/dateUtils";
import RegularizationActions from "./RegularizationActions"; import RegularizationActions from "./RegularizationActions";
@ -7,12 +7,13 @@ import { useRegularizationRequests } from "../../hooks/useAttendance";
import moment from "moment"; import moment from "moment";
import usePagination from "../../hooks/usePagination"; import usePagination from "../../hooks/usePagination";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { cacheData, clearCacheKey } from "../../slices/apiDataManager"; import { cacheData, clearCacheKey, useSelectedproject } from "../../slices/apiDataManager";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
const Regularization = ({ handleRequest }) => { const Regularization = ({ handleRequest, searchTerm }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
var selectedProject = useSelector((store) => store.localVariables.projectId); // var selectedProject = useSelector((store) => store.localVariables.projectId);
const selectedProject = useSelectedproject();
const [regularizesList, setregularizedList] = useState([]); const [regularizesList, setregularizedList] = useState([]);
const { regularizes, loading, error, refetch } = const { regularizes, loading, error, refetch } =
useRegularizationRequests(selectedProject); useRegularizationRequests(selectedProject);
@ -30,8 +31,6 @@ const Regularization = ({ handleRequest }) => {
const handler = useCallback( const handler = useCallback(
(msg) => { (msg) => {
if (selectedProject == msg.projectId) { if (selectedProject == msg.projectId) {
queryClient.setQueryData( queryClient.setQueryData(
["regularizedList", selectedProject], ["regularizedList", selectedProject],
(oldData) => { (oldData) => {
@ -47,12 +46,27 @@ const Regularization = ({ handleRequest }) => {
[selectedProject, regularizes] [selectedProject, regularizes]
); );
const filteredData = [...regularizesList]?.sort(sortByName); // Filter the data based on the search term and sort it
const filteredSearchData = useMemo(() => {
const sortedList = [...regularizesList].sort(sortByName);
if (!searchTerm) {
return sortedList;
}
const lowercasedSearchTerm = searchTerm.toLowerCase();
return sortedList.filter((item) => {
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
return fullName.includes(lowercasedSearchTerm);
});
}, [regularizesList, searchTerm]);
const { currentPage, totalPages, currentItems, paginate } =
usePagination(filteredSearchData, 20);
// Reset pagination when the search term or data changes
useEffect(() => {
}, [filteredSearchData]);
const { currentPage, totalPages, currentItems, paginate } = usePagination(
filteredData,
20
);
useEffect(() => { useEffect(() => {
eventBus.on("regularization", handler); eventBus.on("regularization", handler);
return () => eventBus.off("regularization", handler); return () => eventBus.off("regularization", handler);
@ -130,8 +144,9 @@ const Regularization = ({ handleRequest }) => {
</table> </table>
) : ( ) : (
<div className="my-4"> <div className="my-4">
{" "} <span className="text-secondary">
<span className="text-secondary">No Requests Found !</span> {searchTerm ? "No results found for your search." : "No Requests Found !"}
</span>
</div> </div>
)} )}
{!loading && totalPages > 1 && ( {!loading && totalPages > 1 && (

View File

@ -4,7 +4,7 @@ import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { usePositionTracker } from '../../hooks/usePositionTracker'; import { usePositionTracker } from '../../hooks/usePositionTracker';
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice'; import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
import {cacheData, getCachedData} from '../../slices/apiDataManager'; import {cacheData, getCachedData, useSelectedproject} from '../../slices/apiDataManager';
import showToast from '../../services/toastService'; import showToast from '../../services/toastService';
import { useMarkAttendance } from '../../hooks/useAttendance'; import { useMarkAttendance } from '../../hooks/useAttendance';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
@ -17,7 +17,8 @@ const [loadingReject,setLoadingForReject] = useState(false)
const {mutate:MarkAttendance,isPending} = useMarkAttendance() const {mutate:MarkAttendance,isPending} = useMarkAttendance()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const projectId = useSelector((store)=>store.localVariables.projectId) // const projectId = useSelector((store)=>store.localVariables.projectId)
const projectId = useSelectedproject();
const {latitude,longitude} = usePositionTracker(); const {latitude,longitude} = usePositionTracker();
const dispatch = useDispatch() const dispatch = useDispatch()

View File

@ -98,42 +98,42 @@ const AttendanceOverview = () => {
colors: roles.map((_, i) => flatColors[i % flatColors.length]), colors: roles.map((_, i) => flatColors[i % flatColors.length]),
}; };
return ( return (
<div className="bg-white p-4 rounded shadow d-flex flex-column"> <div
{/* Header */} className="bg-white p-4 rounded shadow d-flex flex-column"
<div className="d-flex justify-content-between align-items-center mb-3"> >
<div className="card-title mb-0 text-start"> {/* Header */}
<h5 className="mb-1">Attendance Overview</h5> <div className="d-flex justify-content-between align-items-center mb-3">
<p className="card-subtitle">Role-wise present count</p> <div className="card-title mb-0 text-start">
</div> <h5 className="mb-1">Attendance Overview</h5>
<div className="d-flex gap-2"> <p className="card-subtitle">Role-wise present count</p>
<select </div>
className="form-select form-select-sm w-auto" <div className="d-flex gap-2">
value={dayRange} <select
onChange={(e) => setDayRange(Number(e.target.value))} className="form-select form-select-sm"
> value={dayRange}
<option value={7}>Last 7 Days</option> onChange={(e) => setDayRange(Number(e.target.value))}
<option value={15}>Last 15 Days</option> >
<option value={30}>Last 30 Days</option> <option value={7}>Last 7 Days</option>
</select> <option value={15}>Last 15 Days</option>
<button <option value={30}>Last 30 Days</option>
className={`btn btn-sm p-1 ${ </select>
view === "chart" ? "btn-primary" : "btn-outline-primary" <button
}`} className={`btn btn-sm ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`}
onClick={() => setView("chart")} onClick={() => setView("chart")}
> title="Chart View"
<i class="bx bx-bar-chart fs-5"></i> >
</button> <i className="bx bx-bar-chart-alt-2"></i>
<button </button>
className={`btn btn-sm p-1 ${ <button
view === "table" ? "btn-primary" : "btn-outline-primary" className={`btn btn-sm ${view === "table" ? "btn-primary" : "btn-outline-primary"}`}
}`} onClick={() => setView("table")}
onClick={() => setView("table")} title="Table View"
> >
<i class="bx bx-table fs-5"></i> <i className="bx bx-task text-success"></i>
</button> </button>
</div> </div>
</div> </div>
{/* Content */} {/* Content */}
<div className="flex-grow-1 d-flex align-items-center justify-content-center"> <div className="flex-grow-1 d-flex align-items-center justify-content-center">

View File

@ -19,7 +19,7 @@ import { useProfile } from "../../hooks/useProfile";
const ManageBucket = () => { const ManageBucket = () => {
const { profile } = useProfile(); const { profile } = useProfile();
const [bucketList, setBucketList] = useState([]); const [bucketList, setBucketList] = useState([]);
const {employeesList} = useAllEmployees( false ); const { employeesList } = useAllEmployees(false);
const [selectedEmployee, setSelectEmployee] = useState([]); const [selectedEmployee, setSelectEmployee] = useState([]);
const { buckets, loading, refetch } = useBuckets(); const { buckets, loading, refetch } = useBuckets();
const [action_bucket, setAction_bucket] = useState(false); const [action_bucket, setAction_bucket] = useState(false);
@ -237,9 +237,8 @@ const ManageBucket = () => {
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
/> />
<i <i
className={`bx bx-refresh cursor-pointer fs-4 ${ className={`bx bx-refresh cursor-pointer fs-4 ${loading ? "spin" : ""
loading ? "spin" : "" }`}
}`}
title="Refresh" title="Refresh"
onClick={() => refetch()} onClick={() => refetch()}
/> />
@ -248,9 +247,8 @@ const ManageBucket = () => {
<button <button
type="button" type="button"
className={`btn btn-sm btn-primary ms-auto ${ className={`btn btn-sm btn-primary ms-auto ${action_bucket ? "d-none" : ""
action_bucket ? "d-none" : "" }`}
}`}
onClick={() => { onClick={() => {
setAction_bucket(true); setAction_bucket(true);
select_bucket(null); select_bucket(null);
@ -267,22 +265,33 @@ const ManageBucket = () => {
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 pt-3 px-2 px-sm-0"> <div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 pt-3 px-2 px-sm-0">
{loading && ( {loading && (
<div className="col-12"> <div className="col-12">
<div className="d-flex justify-content-center align-items-center py-5"> <div
className="d-flex justify-content-center align-items-center py-5 w-100"
style={{ marginLeft: "250px" }}
>
Loading... Loading...
</div> </div>
</div> </div>
)} )}
{!loading && buckets.length === 0 && (
{!loading && buckets.length === 0 && searchTerm.trim() === "" && (
<div className="col-12"> <div className="col-12">
<div className="d-flex justify-content-center align-items-center py-5"> <div
No Buckets Available. className="d-flex justify-content-center align-items-center py-5 w-100"
style={{ marginLeft: "250px" }}
>
No buckets available.
</div> </div>
</div> </div>
)} )}
{!loading && sortedBucktesList.length === 0 && (
{!loading && buckets.length > 0 && sortedBucktesList.length === 0 && (
<div className="col-12"> <div className="col-12">
<div className="d-flex justify-content-center align-items-center py-5"> <div
No Matching Bucket Found. className="d-flex justify-content-center align-items-center py-5 w-100"
style={{ marginLeft: "250px" }}
>
No matching buckets found.
</div> </div>
</div> </div>
)} )}
@ -296,29 +305,29 @@ const ManageBucket = () => {
{(DirManager || {(DirManager ||
DirAdmin || DirAdmin ||
bucket?.createdBy?.id === bucket?.createdBy?.id ===
profile?.employeeInfo?.id) && ( profile?.employeeInfo?.id) && (
<div className="d-flex gap-2"> <div className="d-flex gap-2">
<i <i
className="bx bx-edit bx-sm text-primary cursor-pointer" className="bx bx-edit bx-sm text-primary cursor-pointer"
onClick={() => { onClick={() => {
select_bucket(bucket); select_bucket(bucket);
setAction_bucket(true); setAction_bucket(true);
const initialSelectedEmployees = employeesList const initialSelectedEmployees = employeesList
.filter((emp) => .filter((emp) =>
bucket.employeeIds?.includes( bucket.employeeIds?.includes(
emp.employeeId emp.employeeId
)
) )
) .map((emp) => ({ ...emp, isActive: true }));
.map((emp) => ({ ...emp, isActive: true })); setSelectEmployee(initialSelectedEmployees);
setSelectEmployee(initialSelectedEmployees); }}
}} ></i>
></i> <i
<i className="bx bx-trash bx-sm text-danger cursor-pointer ms-0"
className="bx bx-trash bx-sm text-danger cursor-pointer ms-0" onClick={() => setDeleteBucket(bucket?.id)}
onClick={() => setDeleteBucket(bucket?.id)} ></i>
></i> </div>
</div> )}
)}
</h6> </h6>
<h6 className="card-subtitle mb-2 text-muted text-start"> <h6 className="card-subtitle mb-2 text-muted text-start">
Contacts:{" "} Contacts:{" "}

View File

@ -114,39 +114,56 @@ const NotesDirectory = ({
? contactProfile?.notes || [] ? contactProfile?.notes || []
: contactNotes || []; : contactNotes || [];
const hasNotes = notesToDisplay.length > 0;
return ( return (
<div className="text-start mt-10"> <div className="text-start mt-10">
<div className="d-flex align-items-center justify-content-between"> <div className="d-flex align-items-center justify-content-between">
<div className="row w-100 align-items-center"> <div className="row w-100 align-items-center">
<div className="col col-2"> {hasNotes && (
<p className="fw-semibold m-0 ms-3">Notes :</p> <div className="col col-2">
</div> <p className="fw-semibold m-0 ms-3">Notes :</p>
</div>
)}
<div className="col d-flex justify-content-end gap-2 pe-0"> <div className="col d-flex justify-content-end gap-2 pe-0">
{" "} {" "}
<div className="d-flex align-items-center justify-content-between"> <div className="d-flex align-items-center justify-content-between">
<label
className="switch switch-primary" <label
style={{ className="switch switch-primary"
visibility: style={{
contactProfile?.notes?.length > 0 || fontSize: "15px",
contactNotes?.length > 0 }}
? "visible" >
: "hidden", <input
}} type="checkbox"
> className="switch-input"
<input onChange={() => handleSwitch(!IsActive)}
type="checkbox" checked={IsActive}
className="switch-input" style={{
onChange={() => handleSwitch(!IsActive)} transform: "scale(0.8)", // smaller toggle
value={IsActive} }}
/> />
<input type="checkbox" className="switch-input" /> <span
<span className="switch-toggle-slider"> className="switch-toggle-slider"
<span className="switch-on"></span> style={{
<span className="switch-off"></span> width: "30px", // narrower slider
</span> height: "15px", // shorter slider
<span className="switch-label">Include Deleted Notes</span> }}
</label> >
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span
className="switch-label"
style={{
fontSize: "14px", // smaller label text
marginLeft: "-14px"
}}
>
Include Deleted Notes
</span>
</label>
{!showEditor && ( {!showEditor && (
<div className="d-flex justify-content-end"> <div className="d-flex justify-content-end">
@ -222,7 +239,7 @@ const NotesDirectory = ({
/> />
)) ))
: !isLoading && : !isLoading &&
!showEditor && ( !showEditor && (
<div className="text-center mt-5">{noNotesMessage}</div> <div className="text-center mt-5">{noNotesMessage}</div>
)} )}
</div> </div>

View File

@ -11,6 +11,7 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
const [profileContactState, setProfileContactState] = useState(null); const [profileContactState, setProfileContactState] = useState(null);
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
// Safely access description, defaulting to an empty string if not present
const description = profileContactState?.description || ""; const description = profileContactState?.description || "";
const limit = 500; const limit = 500;
@ -40,7 +41,7 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
middleName = names[1]; // This was an error in the original prompt, corrected to names[1] middleName = names[1]; // This was an error in the original prompt, corrected to names[1]
lastName = names[names.length - 1]; lastName = names[names.length - 1];
// Reconstruct full name to be precise with spacing // Reconstruct full name to be precise with spacing
fullName = `${firstName} ${middleName ? middleName + ' ' : ''}${lastName}`; fullName = `${firstName} ${middleName ? middleName + " " : ""}${lastName}`;
} else { } else {
// Fallback if no names or empty string // Fallback if no names or empty string
firstName = "Contact"; firstName = "Contact";
@ -113,7 +114,7 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
</span> </span>
<i <i
className={`bx bx-copy-alt cursor-pointer bx-xs text-start ${copiedIndex === idx ? "text-secondary" : "text-primary" className={`bx bx-copy-alt cursor-pointer bx-xs text-start ${copiedIndex === idx ? "text-secondary" : "text-primary"
}`} }`}
title={copiedIndex === idx ? "Copied!" : "Copy Email"} title={copiedIndex === idx ? "Copied!" : "Copy Email"}
style={{ flexShrink: 0 }} style={{ flexShrink: 0 }}
onClick={() => handleCopy(email.emailAddress, idx)} onClick={() => handleCopy(email.emailAddress, idx)}
@ -292,31 +293,35 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
)} )}
</div> </div>
<div className="d-flex mb-2 align-items-start" style={{ marginLeft: "3rem" }}> {description && (
<div className="d-flex" style={{ minWidth: "130px" }}> <div
<span className="d-flex align-items-start"> className="d-flex mb-2 align-items-start"
<i className="bx bx-book me-1"></i> style={{ marginLeft: "3rem" }}
<span>Description</span> >
</span> <div className="d-flex" style={{ minWidth: "130px" }}>
<span style={{ marginLeft: "10px" }}>:</span> <span className="d-flex align-items-start">
</div> <i className="bx bx-book me-1"></i>
<span>Description</span>
</span>
<span style={{ marginLeft: "10px" }}>:</span>
</div>
<div className="text-start"> <div className="text-start">
{displayText} {displayText}
{isLong && ( {isLong && (
<> <>
<br /> <br />
<span <span
onClick={toggleReadMore} onClick={toggleReadMore}
className="text-primary mx-1 cursor-pointer" className="text-primary mx-1 cursor-pointer"
> >
{expanded ? "Read less" : "Read more"} {expanded ? "Read less" : "Read more"}
</span> </span>
</> </>
)} )}
</div>
</div> </div>
</div> )}
<hr className="my-1" /> <hr className="my-1" />
<NotesDirectory <NotesDirectory

View File

@ -20,58 +20,58 @@ const EmpDashboard = ({ profile }) => {
<div className="col col-sm-6 pt-5"> <div className="col col-sm-6 pt-5">
<div className="card "> <div className="card ">
<div className="card-body"> <div className="card-body">
<small className="card-text text-uppercase text-body-secondary small"> <small className="card-text text-uppercase text-body-secondary small text-start d-block">
My Projects My Projects
</small>{" "} </small>
<ul className="list-unstyled text-start my-3 py-1"> <ul className="list-unstyled text-start my-3 py-1">
{selectedProjectLoding && <span>Loading</span>} {selectedProjectLoding && <span>Loading</span>}
{projectList.map((project) => ( {projectList.map((project) => (
<li <li
className="d-flex mb-4 align-items-start flex-wrap" className="d-flex mb-4 align-items-start flex-wrap"
key={project.id} key={project.id}
> >
{/* Project Info */} {/* Project Info */}
<div className="flex-grow-1"> <div className="flex-grow-1">
<div className="d-flex flex-wrap align-items-center justify-content-between gap-2"> <div className="d-flex flex-wrap align-items-center justify-content-between gap-2">
<div className="d-flex"> <div className="d-flex">
<div className="avatar flex-shrink-0 me-3"> <div className="avatar flex-shrink-0 me-3">
<span className="avatar-initial rounded bg-label-primary"> <span className="avatar-initial rounded bg-label-primary">
<i className="icon-base bx bx-buildings icon-lg"></i> <i className="icon-base bx bx-buildings icon-lg"></i>
</span> </span>
</div> </div>
<div> <div>
<h6 className="mb-0">{project.projectShortName}</h6> <h6 className="mb-0">{project.projectShortName}</h6>
<small className="text-muted">{project.projectName}</small> <small className="text-muted">{project.projectName}</small>
<div className="label-secondary"> <div className="label-secondary">
Assigned:{" "} Assigned:{" "}
{project.assignedDate ? ( {project.assignedDate ? (
new Date(project.assignedDate).toLocaleDateString() new Date(project.assignedDate).toLocaleDateString()
) : ( ) : (
<em>NA</em> <em>NA</em>
)} )}
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<span className="badge bg-label-secondary"> <span className="badge bg-label-secondary">
{project.designation} {project.designation}
</span> </span>
</div> </div>
</div> </div>
{/* Dates */} {/* Dates */}
{project.removedDate && ( {project.removedDate && (
<div className="mt-2 d-flex flex-column flex-sm-row justify-content-between"> <div className="mt-2 d-flex flex-column flex-sm-row justify-content-between">
<div className="label-secondary"> <div className="label-secondary">
Unassigned:{" "} Unassigned:{" "}
{new Date(project.removedDate).toLocaleDateString()} {new Date(project.removedDate).toLocaleDateString()}
</div> </div>
</div> </div>
)} )}
</div> </div>
</li> </li>
))} ))}
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -1,109 +1,163 @@
import React from "react"; import React from "react";
import Avatar from "../common/Avatar";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
const EmpOverview = ({ profile }) => { const EmpOverview = ({ profile }) => {
const { loggedInUserProfile } = useProfile(); const { loggedInUserProfile } = useProfile();
return ( return (
<> <div className="row">
{" "} <div className="col-12 mb-4">
<div className="row"> <div className="card">
<div className="col-12 mb-4"> <div className="card-body">
<div className="card">
<div className="card-body"> {/* About Heading */}
<small className="card-text text-uppercase text-body-secondary small"> <small className="card-text text-uppercase text-body-secondary small d-block text-start mb-3">
About About
</small> </small>
<ul className="list-unstyled my-3 py-1">
<li className="d-flex align-items-center mb-4"> {/* Full Name */}
<i className="icon-base bx bx-user"></i> <div className="d-flex align-items-start mb-3">
<span className="fw-medium mx-2">Full Name:</span>{" "} <span className="d-flex">
<span>{`${profile?.firstName} ${profile?.lastName}`}</span> <i className="bx bx-user bx-xs me-2 mt-1"></i>
</li> <span>Full Name</span>
<li className="d-flex align-items-center mb-4"> </span>
<i className="icon-base bx bx-check"></i> <span style={{ marginLeft: "74px" }}>:</span>
<span className="fw-medium mx-2">Status:</span>{" "} <span className="ms-5">
<span>Active</span> {profile?.firstName || <em>NA</em>} {profile?.lastName || ""}
</li> </span>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-crown"></i>
<span className="fw-medium mx-2">Role:</span>{" "}
<span> {profile?.jobRole || <em>NA</em>}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-flag"></i>
<span className="fw-medium mx-2">Gender:</span>{" "}
<span> {profile?.gender || <em>NA</em>}</span>
</li>{" "}
<li className="d-flex align-items-center mb-2">
<i className="icon-base bx bx-calendar "></i>
<span className="fw-medium mx-2">Birth Date:</span>{" "}
<span>
{profile?.birthDate ? (
new Date(profile.birthDate).toLocaleDateString()
) : (
<em>NA</em>
)}
</span>
</li>
<li className="d-flex align-items-center mb-2">
<i className="icon-base bx bx-calendar "></i>
<span className="fw-medium mx-2">Joining Date:</span>{" "}
<span>
{profile?.joiningDate ? (
new Date(profile.joiningDate).toLocaleDateString()
) : (
<em>NA</em>
)}
</span>
</li>
</ul>
<small className="card-text text-uppercase text-body-secondary small">
Contacts
</small>
<ul className="list-unstyled my-3 py-1">
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-phone"></i>
<span className="fw-medium mx-2">Contact:</span>{" "}
<span> {profile?.phoneNumber || <em>NA</em>}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-envelope"></i>
<span className="fw-medium mx-2">Email:</span>{" "}
<a href={`mailto:${profile?.email}`}>
{" "}
{profile?.email || <em>NA</em>}
</a>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-user"></i>
<span className="fw-medium mx-2">
{" "}
Emergency Contact:
</span>{" "}
<span> {profile?.emergencyContactPerson || <em>NA</em>}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-phone"></i>
<span className="fw-medium mx-2"> Emergency Phone:</span>{" "}
<span> {profile?.emergencyPhoneNumber || <em>NA</em>}</span>
</li>
<li className="d-flex align-items-center ">
<div className="container p-0">
<div className="fw-medium text-start">
<i className="icon-base bx bx-map "></i> Address:
</div>
<div className="text-start ms-7">
{profile?.currentAddress || <em>NA</em>}
</div>
</div>
</li>
</ul>
</div> </div>
{/* Status */}
<div className="d-flex align-items-start mb-3">
<span className="d-flex">
<i className="bx bx-check bx-xs me-2 mt-1"></i>
<span>Status</span>
</span>
<span style={{ marginLeft: "96px" }}>:</span>
<span className="ms-5">Active</span>
</div>
{/* Role */}
<div className="d-flex align-items-start mb-3">
<span className="d-flex">
<i className="bx bx-crown bx-xs me-2 mt-1"></i>
<span>Role</span>
</span>
<span style={{ marginLeft: "110px" }}>:</span>
<span className="ms-5">{profile?.jobRole || <em>NA</em>}</span>
</div>
{/* Gender */}
<div className="d-flex align-items-start mb-3">
<span className="d-flex">
<i className="bx bx-flag bx-xs me-2 mt-1"></i>
<span>Gender</span>
</span>
<span style={{ marginLeft: "91px" }}>:</span>
<span className="ms-5">{profile?.gender || <em>NA</em>}</span>
</div>
{/* Birth Date */}
<div className="d-flex align-items-start mb-3">
<span className="d-flex">
<i className="bx bx-calendar bx-xs me-2 mt-1"></i>
<span>Birth Date</span>
</span>
<span style={{ marginLeft: "74px" }}>:</span>
<span className="ms-5">
{profile?.birthDate
? new Date(profile.birthDate).toLocaleDateString()
: <em>NA</em>}
</span>
</div>
{/* Joining Date */}
<div className="d-flex align-items-start mb-3">
<span className="d-flex">
<i className="bx bx-calendar bx-xs me-2 mt-1"></i>
<span>Joining Date</span>
</span>
<span style={{ marginLeft: "60px" }}>:</span>
<span className="ms-5">
{profile?.joiningDate
? new Date(profile.joiningDate).toLocaleDateString()
: <em>NA</em>}
</span>
</div>
{/* Contacts Heading */}
<small className="card-text text-uppercase text-body-secondary small d-block text-start mb-3 mt-4">
Contacts
</small>
{/* Contact Number */}
<div className="d-flex align-items-start mb-3">
<span className="d-flex">
<i className="bx bx-phone bx-xs me-2 mt-1"></i>
<span>Contact</span>
</span>
<span style={{ marginLeft: "87px" }}>:</span>
<span className="ms-5">{profile?.phoneNumber || <em>NA</em>}</span>
</div>
{/* Email */}
<div className="d-flex align-items-start mb-3">
<span className="d-flex" style={{ minWidth: "160px", whiteSpace: "nowrap" }}>
<i className="bx bx-envelope bx-xs me-2 mt-1"></i>
<span>Email</span>
</span>
<span className="me-5">:</span>
<span>
{profile?.email ? (
<a href={`mailto:${profile.email}`}>{profile.email}</a>
) : (
<em>NA</em>
)}
</span>
</div>
{/* Emergency Contact */}
<div className="d-flex align-items-start mb-3">
<span className="d-flex">
<i className="bx bx-user bx-xs me-2 mt-1"></i>
<span>Emergency Contact</span>
</span>
<span style={{ marginLeft: "14px" }}>:</span>
<span className="ms-5">
{profile?.emergencyContactPerson || <em>NA</em>}
</span>
</div>
{/* Emergency Phone */}
<div className="d-flex align-items-start mb-3">
<span className="d-flex">
<i className="bx bx-phone bx-xs me-2 mt-1"></i>
<span>Emergency Phone</span>
</span>
<span style={{ marginLeft: "25px" }}>:</span>
<span className="ms-5">
{profile?.emergencyPhoneNumber || <em>NA</em>}
</span>
</div>
{/* Address */}
<div className="d-flex align-items-start">
<span className="d-flex">
<i className="bx bx-map bx-xs me-2 mt-1"></i>
<span>Address</span>
</span>
<span style={{ marginLeft: "86px" }}>:</span>
<span className="ms-5">
{profile?.currentAddress || <em>NA</em>}
</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</> </div>
); );
}; };
export default EmpOverview; export default EmpOverview;

View File

@ -16,13 +16,13 @@ import {
getCachedData, getCachedData,
} from "../../slices/apiDataManager"; } from "../../slices/apiDataManager";
import { clearApiCacheKey } from "../../slices/apiCacheSlice"; import { clearApiCacheKey } from "../../slices/apiCacheSlice";
import {useMutation} from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
const mobileNumberRegex = /^[0-9]\d{9}$/; const mobileNumberRegex = /^[0-9]\d{9}$/;
const ManageEmployee = ({ employeeId, onClosed,IsAllEmployee }) => { const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { mutate: updateEmployee, isPending } = useUpdateEmployee(); const { mutate: updateEmployee, isPending } = useUpdateEmployee();
const { const {
employee, employee,
@ -130,12 +130,11 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
.min(1, { message: "Phone Number is required" }) .min(1, { message: "Phone Number is required" })
.regex(mobileNumberRegex, { message: "Invalid phone number " }), .regex(mobileNumberRegex, { message: "Invalid phone number " }),
jobRoleId: z.string().min(1, { message: "Role is required" }), jobRoleId: z.string().min(1, { message: "Role is required" }),
} ); });
useEffect( () => useEffect(() => {
{
refetch() refetch()
},[]) }, [])
const { const {
register, register,
@ -169,19 +168,19 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
}); });
const AadharNumberValue = watch("aadharNumber") || ""; const AadharNumberValue = watch("aadharNumber") || "";
const onSubmit = (data) => {
if (data.email === "") {
data.email = null;
}
updateEmployee({...data,IsAllEmployee},{ const onSubmit = (data) => {
onSuccess: () => { if (data.email === "") {
reset(); data.email = null;
onClosed(); }
},
}); updateEmployee({ ...data, IsAllEmployee }, {
}; onSuccess: () => {
reset();
onClosed();
},
});
};
useEffect(() => { useEffect(() => {
@ -212,7 +211,7 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
phoneNumber: currentEmployee.phoneNumber || "", phoneNumber: currentEmployee.phoneNumber || "",
jobRoleId: currentEmployee.jobRoleId?.toString() || "", jobRoleId: currentEmployee.jobRoleId?.toString() || "",
} }
: {} : {}
); );
setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0); setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0);
setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0); setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0);
@ -220,66 +219,82 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
return ( return (
<> <>
<form onSubmit={handleSubmit( onSubmit )} className="p-sm-0 p-2"> <form onSubmit={handleSubmit(onSubmit)} className="p-sm-0 p-2">
<div className="text-center"><p className="fs-6 fw-semibold"> {employee ? "Update Employee" : "Create Employee"}</p> </div> <div className="text-center"><p className="fs-6 fw-semibold"> {employee ? "Update Employee" : "Create Employee"}</p> </div>
<div className="row mb-3"> <div className="row mb-3">
<div className="col-sm-4"> <div className="col-sm-4">
{" "} <div className="form-text text-start">First Name</div>
<div className="form-text text-start">First Name</div>
<input
type="text"
name="FirstName"
{...register("firstName")}
className="form-control form-control-sm"
id="firstName"
placeholder="First Name"
/>
{errors.firstName && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.firstName.message}
</div>
)}
</div>{" "}
<div className="col-sm-4">
<div className="form-text text-start">Middle Name</div>
<input <input
type="text" type="text"
{...register("middleName")} name="firstName"
{...register("firstName", {
required: "First name is required",
pattern: {
value: /^[A-Za-z\s]+$/, // Only letters and spaces
message: "Only letters are allowed",
},
})}
className="form-control form-control-sm" className="form-control form-control-sm"
id="middleName" id="firstName"
placeholder="Middle Name" placeholder="First Name"
onInput={(e) => {
e.target.value = e.target.value.replace(/[^A-Za-z\s]/g, "");
}}
/> />
{errors.middleName && ( {errors.firstName && (
<div <div className="danger-text text-start" style={{ fontSize: "12px" }}>
className="danger-text text-start " {errors.firstName.message}
style={{ fontSize: "12px" }}
>
{errors.middleName.message}
</div> </div>
)} )}
</div> </div>
<div className="col-sm-4">
<div className="form-text text-start">Middle Name</div>
<input
type="text"
{...register("middleName", {
pattern: {
value: /^[A-Za-z\s]+$/, // Only letters and spaces
message: "Only letters are allowed",
},
})}
className="form-control form-control-sm"
id="middleName"
placeholder="Middle Name"
onInput={(e) => {
e.target.value = e.target.value.replace(/[^A-Za-z\s]/g, "");
}}
/>
{errors.middleName && (
<div className="danger-text text-start " style={{ fontSize: "12px" }}>
{errors.middleName.message}
</div>
)}
</div>
<div className="col-sm-4"> <div className="col-sm-4">
<div className="form-text text-start">Last Name</div> <div className="form-text text-start">Last Name</div>
<input <input
type="text" type="text"
{...register("lastName")} {...register("lastName", {
pattern: {
value: /^[A-Za-z\s]+$/, // Only letters and spaces
message: "Only letters are allowed",
},
})}
className="form-control form-control-sm" className="form-control form-control-sm"
id="lastName" id="lastName"
placeholder="Last Name" placeholder="Last Name"
onInput={(e) => {
e.target.value = e.target.value.replace(/[^A-Za-z\s]/g, "");
}}
/> />
{errors.lastName && ( {errors.lastName && (
<div <div className="danger-text text-start" style={{ fontSize: "12px" }}>
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.lastName.message} {errors.lastName.message}
</div> </div>
)} )}
</div> </div>
</div> </div>
<div className="row mb-3"> <div className="row mb-3">
<div className="col-sm-6"> <div className="col-sm-6">
@ -330,7 +345,7 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
<div className="col-sm-4"> <div className="col-sm-4">
<div className="form-text text-start">Gender</div> <div className="form-text text-start">Gender</div>
<div className="input-group input-group-merge "> <div className="input-group">
<select <select
className="form-select form-select-sm " className="form-select form-select-sm "
{...register("gender")} {...register("gender")}
@ -357,7 +372,7 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
<div className="col-sm-4"> <div className="col-sm-4">
<div className="form-text text-start">Birth Date</div> <div className="form-text text-start">Birth Date</div>
<div className="input-group input-group-merge "> <div className="input-group">
<input <input
className="form-control form-control-sm" className="form-control form-control-sm"
type="date" type="date"
@ -377,7 +392,7 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
<div className="col-sm-4"> <div className="col-sm-4">
<div className="form-text text-start">Joining Date</div> <div className="form-text text-start">Joining Date</div>
<div className="input-group input-group-merge "> <div className="input-group">
<input <input
className="form-control form-control-sm" className="form-control form-control-sm"
type="date" type="date"
@ -470,7 +485,7 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
<div className="row mb-3"> <div className="row mb-3">
<div className="col-sm-4"> <div className="col-sm-4">
<div className="form-text text-start">Official Designation</div> <div className="form-text text-start">Official Designation</div>
<div className="input-group input-group-merge "> <div className="input-group">
<select <select
className="form-select form-select-sm" className="form-select form-select-sm"
{...register("jobRoleId")} {...register("jobRoleId")}

View File

@ -13,24 +13,23 @@ import { useSelector } from "react-redux";
import moment from "moment"; import moment from "moment";
import { useExpenseFilter } from "../../hooks/useExpense"; import { useExpenseFilter } from "../../hooks/useExpense";
import { ExpenseFilterSkeleton } from "./ExpenseSkeleton"; import { ExpenseFilterSkeleton } from "./ExpenseSkeleton";
import { useLocation } from "react-router-dom";
const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
const selectedProjectId = useSelector((store) => store.localVariables.projectId); const selectedProjectId = useSelector((store) => store.localVariables.projectId);
const { data, isLoading,isError,error,isFetching , isFetched} = useExpenseFilter(); const { data, isLoading,isError,error,isFetching , isFetched} = useExpenseFilter();
const groupByList = useMemo(() => { const groupByList = useMemo(() => {
return [ return [
{ id: "transactionDate", name: "Transaction Date" }, { id: "transactionDate", name: "Transaction Date" },
{ id: "status", name: "Status" }, { id: "status", name: "Status" },
{ id: "submittedBy", name: "Submitted By" }, { id: "submittedBy", name: "Submitted By" },
{ id: "project", name: "Project" }, { id: "project", name: "Project" },
{ id: "paymentMode", name: "Payment Mode" }, { id: "paymentMode", name: "Payment Mode" },
{ id: "expensesType", name: "Expense Type" }, { id: "expensesType", name: "Expense Type" },
{ id: "createdAt", name: "Submitted Date" } { id: "createdAt", name: "Submitted Date" }
].sort((a, b) => a.name.localeCompare(b.name)); ].sort((a, b) => a.name.localeCompare(b.name));
}, []); }, []);
const [selectedGroup, setSelectedGroup] = useState(groupByList[0]); const [selectedGroup, setSelectedGroup] = useState(groupByList[0]);
@ -72,6 +71,12 @@ const groupByList = useMemo(() => {
closePanel(); closePanel();
}; };
// Close popup when navigating to another component
const location = useLocation();
useEffect(() => {
closePanel();
}, [location]);
if (isLoading || isFetching) return <ExpenseFilterSkeleton />; if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
if(isError && isFetched) return <div>Something went wrong Here- {error.message} </div> if(isError && isFetched) return <div>Something went wrong Here- {error.message} </div>
return ( return (
@ -162,21 +167,21 @@ const groupByList = useMemo(() => {
</div> </div>
</div> </div>
</div> </div>
<div className="mb-2 text-start "> <div className="mb-2 text-start ">
<label htmlFor="groupBySelect" className="form-label">Group By :</label> <label htmlFor="groupBySelect" className="form-label">Group By :</label>
<select <select
id="groupBySelect" id="groupBySelect"
className="form-select form-select-sm" className="form-select form-select-sm"
value={selectedGroup?.id || ""} value={selectedGroup?.id || ""}
onChange={handleGroupChange} onChange={handleGroupChange}
> >
{groupByList.map((group) => ( {groupByList.map((group) => (
<option key={group.id} value={group.id}> <option key={group.id} value={group.id}>
{group.name} {group.name}
</option> </option>
))} ))}
</select> </select>
</div> </div>
<div className="d-flex justify-content-end py-3 gap-2"> <div className="d-flex justify-content-end py-3 gap-2">
<button <button

View File

@ -395,7 +395,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
<div className="row my-2"> <div className="row my-2">
<div className="col-md-6"> <div className="col-md-6">
<label htmlFor="statusId" className="form-label "> <label htmlFor="statusId" className="form-label ">
TransactionId Transaction ID
</label> </label>
<input <input
type="text" type="text"

View File

@ -43,8 +43,9 @@ const Header = () => {
/^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test( /^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
pathname pathname
); );
const isExpensePage = /^\/expenses$/.test(pathname);
return !(isDirectoryPath || isProfilePage); return !(isDirectoryPath || isProfilePage || isExpensePage);
}; };
const allowedProjectStatusIds = [ const allowedProjectStatusIds = [
"603e994b-a27f-4e5d-a251-f3d69b0498ba", "603e994b-a27f-4e5d-a251-f3d69b0498ba",
@ -475,4 +476,4 @@ const Header = () => {
</nav> </nav>
); );
}; };
export default Header; export default Header;

View File

@ -49,6 +49,9 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
const infoRef = useRef(null); const infoRef = useRef(null);
const infoRef1 = useRef(null); const infoRef1 = useRef(null);
// State for search term
const [searchTerm, setSearchTerm] = useState("");
useEffect(() => { useEffect(() => {
if (typeof bootstrap !== "undefined") { if (typeof bootstrap !== "undefined") {
if (infoRef.current) { if (infoRef.current) {
@ -81,9 +84,11 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
recallEmployeeData, recallEmployeeData,
} = useEmployeesAllOrByProjectId(false, selectedProject, false); } = useEmployeesAllOrByProjectId(false, selectedProject, false);
const dispatch = useDispatch(); const dispatch = useDispatch();
const { data: jobRoleData, loading } = useMaster(); const { loading } = useMaster();
const { data: jobRoleData } = useMaster();
const [selectedRole, setSelectedRole] = useState("all"); // Changed to an array to hold multiple selected roles
const [selectedRoles, setSelectedRoles] = useState(["all"]);
const [displayedSelection, setDisplayedSelection] = useState(""); const [displayedSelection, setDisplayedSelection] = useState("");
const { const {
handleSubmit, handleSubmit,
@ -121,20 +126,79 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
useEffect(() => { useEffect(() => {
dispatch(changeMaster("Job Role")); dispatch(changeMaster("Job Role"));
// Initial state should reflect "All Roles" selected
return () => setSelectedRole("all"); setSelectedRoles(["all"]);
}, [dispatch]); }, [dispatch]);
const handleRoleChange = (event) => { // Modified handleRoleChange to handle multiple selections
setSelectedRole(event.target.value); const handleRoleChange = (event, roleId) => {
// If 'all' is selected, clear other selections
if (roleId === "all") {
setSelectedRoles(["all"]);
} else {
setSelectedRoles((prevSelectedRoles) => {
// If "all" was previously selected, remove it
const newRoles = prevSelectedRoles.filter((role) => role !== "all");
if (newRoles.includes(roleId)) {
// If role is already selected, unselect it
return newRoles.filter((id) => id !== roleId);
} else {
// If role is not selected, add it
return [...newRoles, roleId];
}
});
}
}; };
const filteredEmployees = useEffect(() => {
selectedRole === "all" // Update displayedSelection based on selectedRoles
? employees if (selectedRoles.includes("all")) {
: employees?.filter( setDisplayedSelection("All Roles");
(emp) => String(emp.jobRoleId || "") === selectedRole } else if (selectedRoles.length > 0) {
); const selectedRoleNames = selectedRoles.map(roleId => {
const role = jobRoleData?.find(r => String(r.id) === roleId);
return role ? role.name : '';
}).filter(Boolean); // Filter out empty strings for roles not found
setDisplayedSelection(selectedRoleNames.join(', '));
} else {
setDisplayedSelection("Select Roles");
}
}, [selectedRoles, jobRoleData]);
const handleSearchChange = (event) => {
setSearchTerm(event.target.value);
};
// Filter employees first by role, then by search term AND job role name
const filteredEmployees = employees?.filter((emp) => {
const matchesRole =
selectedRoles.includes("all") || selectedRoles.includes(String(emp.jobRoleId));
// Convert both first and last names and job role name to lowercase for case-insensitive matching
const fullName = `${emp.firstName} ${emp.lastName}`.toLowerCase();
const jobRoleName = jobRoleData?.find((role) => role.id === emp.jobRoleId)?.name?.toLowerCase() || "";
const searchLower = searchTerm.toLowerCase();
// Check if the full name OR job role name includes the search term
const matchesSearch = fullName.includes(searchLower) || jobRoleName.includes(searchLower);
return matchesRole && matchesSearch;
});
// Determine unique job role IDs from the filtered employees (for dropdown options)
const uniqueJobRoleIdsInFilteredEmployees = new Set(
employees?.map(emp => emp.jobRoleId).filter(Boolean)
);
// Filter jobRoleData to only include roles present in the uniqueJobRoleIdsInFilteredEmployees
const jobRolesForDropdown = jobRoleData?.filter(role =>
uniqueJobRoleIdsInFilteredEmployees.has(role.id)
);
// Calculate the count of selected roles for display
const selectedRolesCount = selectedRoles.includes("all")
? 0 // "All Roles" doesn't contribute to a specific count
: selectedRoles.length;
const onSubmit = (data) => { const onSubmit = (data) => {
const selectedEmployeeIds = data.selectedEmployees; const selectedEmployeeIds = data.selectedEmployees;
@ -192,118 +256,183 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
<div className="form-text text-start"> <div className="form-text text-start">
<div className="d-flex align-items-center form-text fs-7"> <div className="d-flex align-items-center form-text fs-7">
<span className="text-dark">Select Team</span> <span className="text-dark">Select Team</span>
<div className="me-2">{displayedSelection}</div>
<a
className="dropdown-toggle hide-arrow cursor-pointer"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i className="bx bx-filter bx-lg text-primary"></i>
</a>
<ul className="dropdown-menu p-2 text-capitalize"> {/* Dropdown */}
<li key="all"> <div className="dropdown position-relative d-inline-block">
<button <a
type="button" className={`dropdown-toggle hide-arrow cursor-pointer ${selectedRoles.includes("all") || selectedRoles.length === 0
className="dropdown-item py-1" ? "text-secondary"
onClick={() => : "text-primary"
handleRoleChange({ }`}
target: { value: "all" }, data-bs-toggle="dropdown"
}) role="button"
} aria-expanded="false"
>
<i className="bx bx-slider-alt ms-2"></i>
</a>
{/* Badge */}
{selectedRolesCount > 0 && (
<span
className="position-absolute top-0 start-100 translate-middle badge rounded-circle bg-warning text-white"
style={{
fontSize: "0.65rem",
minWidth: "18px",
height: "18px",
padding: "0",
lineHeight: "18px",
textAlign: "center",
zIndex: 10,
}}
> >
All Roles {selectedRolesCount}
</button> </span>
</li> )}
{jobRoleData?.map((user) => (
<li key={user.id}> {/* Dropdown Menu with Scroll */}
<button <ul
type="button" className="dropdown-menu p-2 text-capitalize"
className="dropdown-item py-1" style={{ maxHeight: "300px", overflowY: "auto" }}
value={user.id} >
onClick={handleRoleChange} {/* All Roles */}
> <li key="all">
{user.name} <div className="form-check dropdown-item py-0">
</button> <input
className="form-check-input"
type="checkbox"
id="checkboxAllRoles"
value="all"
checked={selectedRoles.includes("all")}
onChange={(e) =>
handleRoleChange(e, e.target.value)
}
/>
<label
className="form-check-label ms-2"
htmlFor="checkboxAllRoles"
>
All Roles
</label>
</div>
</li> </li>
))}
</ul> {/* Dynamic Roles */}
{jobRolesForDropdown?.map((role) => (
<li key={role.id}>
<div className="form-check dropdown-item py-0">
<input
className="form-check-input"
type="checkbox"
id={`checkboxRole-${role.id}`}
value={role.id}
checked={selectedRoles.includes(String(role.id))}
onChange={(e) =>
handleRoleChange(e, e.target.value)
}
/>
<label
className="form-check-label ms-2"
htmlFor={`checkboxRole-${role.id}`}
>
{role.name}
</label>
</div>
</li>
))}
</ul>
</div>
{/* Search Box */}
<input
type="text"
className="form-control form-control-sm ms-auto mb-2 mt-2"
placeholder="Search employees or roles..."
value={searchTerm}
onChange={handleSearchChange}
style={{ maxWidth: "200px" }}
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="row"> {/* Employees list */}
<div className="col-12 h-sm-25 overflow-auto mt-2" style={{height:"300px"}}> <div
{selectedRole !== "" && ( className="col-12 mt-2"
<div className="row"> style={{
{employeeLoading ? ( maxHeight: "280px",
<div className="col-12"> overflowY: "auto",
<p className="text-center">Loading employees...</p> overflowX: "hidden",
</div> }}
) : filteredEmployees?.length > 0 ? ( >
filteredEmployees.map((emp) => { {selectedRoles?.length > 0 && (
const jobRole = jobRoleData?.find( <div className="row">
(role) => role?.id === emp?.jobRoleId {employeeLoading ? (
); <div className="col-12">
<p className="text-center">Loading employees...</p>
</div>
) : filteredEmployees?.length > 0 ? (
filteredEmployees.map((emp) => {
const jobRole = jobRoleData?.find(
(role) => role?.id === emp?.jobRoleId
);
return ( return (
<div <div
key={emp.id} key={emp.id}
className="col-6 col-md-4 col-lg-3 mb-3" className="col-6 col-md-4 col-lg-3 mb-3"
> >
<div className="form-check d-flex align-items-start"> <div className="form-check d-flex align-items-start">
<Controller <Controller
name="selectedEmployees" name="selectedEmployees"
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<input <input
{...field} {...field}
className="form-check-input me-1 mt-1" className="form-check-input me-1 mt-1"
type="checkbox" type="checkbox"
id={`employee-${emp?.id}`} id={`employee-${emp?.id}`}
value={emp.id} value={emp.id}
checked={field.value?.includes(emp.id)} checked={field.value?.includes(emp.id)}
onChange={(e) => { onChange={(e) => {
handleCheckboxChange(e, emp); handleCheckboxChange(e, emp);
}} }}
/> />
)}
/>
<div className="flex-grow-1">
<p
className="mb-0"
style={{ fontSize: "13px" }}
>
{emp.firstName} {emp.lastName}
</p>
<small
className="text-muted"
style={{ fontSize: "11px" }}
>
{loading ? (
<span className="placeholder-glow">
<span className="placeholder col-6"></span>
</span>
) : (
jobRole?.name || "Unknown Role"
)} )}
/> </small>
<div className="flex-grow-1">
<p
className="mb-0"
style={{ fontSize: "13px" }}
>
{emp.firstName} {emp.lastName}
</p>
<small
className="text-muted"
style={{ fontSize: "11px" }}
>
{loading ? (
<span className="placeholder-glow">
<span className="placeholder col-6"></span>
</span>
) : (
jobRole?.name || "Unknown Role"
)}
</small>
</div>
</div> </div>
</div> </div>
); </div>
}) );
) : ( })
<div className="col-12"> ) : (
<p className="text-center"> <div className="col-12">
<p className="text-center">
No employees found for the selected role. No employees found for the selected role.
</p> </p>
</div> </div>
)} )}
</div> </div>
)} )}
</div>
</div> </div>
<div <div
@ -356,40 +485,15 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
<div className="col-md text-start mx-0 px-0"> <div className="col-md text-start mx-0 px-0">
<div className="form-check form-check-inline mt-3 px-1"> <div className="form-check form-check-inline mt-3 px-1">
<label <label className="form-text text-dark align-items-center d-flex">
className="form-text text-dark align-items-center d-flex"
htmlFor="inlineCheckbox1"
>
Pending Task of Activity : Pending Task of Activity :
<label <label className="form-check-label fs-7 ms-4">
className="form-check-label fs-7 ms-4"
htmlFor="inlineCheckbox1"
>
<strong> <strong>
{assignData?.workItem?.plannedWork - {assignData?.workItem?.plannedWork -
assignData?.workItem?.completedWork} assignData?.workItem?.completedWork}
</strong>{" "} </strong>{" "}
<u> <u>{assignData?.workItem?.activityMaster?.unitOfMeasurement}</u>
{
assignData?.workItem?.activityMaster
?.unitOfMeasurement
}
</u>
</label> </label>
<div style={{ display: "flex", alignItems: "center" }}>
<div
ref={infoRef}
tabIndex="0"
className="d-flex align-items-center avatar-group justify-content-center ms-2"
data-bs-toggle="popover"
data-bs-trigger="focus"
data-bs-placement="right"
data-bs-html="true"
style={{ cursor: "pointer" }}
>
<i class='bx bx-info-circle bx-xs m-0 text-secondary'></i>
</div>
</div>
</label> </label>
</div> </div>
</div> </div>
@ -397,10 +501,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
{/* Target for Today input and validation */} {/* Target for Today input and validation */}
<div className="col-md text-start mx-0 px-0"> <div className="col-md text-start mx-0 px-0">
<div className="form-check form-check-inline mt-2 px-1 mb-2 text-start"> <div className="form-check form-check-inline mt-2 px-1 mb-2 text-start">
<label <label className="text-dark d-flex align-items-center flex-wrap form-text">
className="text-dark text-start d-flex align-items-center flex-wrap form-text"
htmlFor="inlineCheckbox1"
>
<span>Target for Today</span>&nbsp; <span>Target for Today</span>&nbsp;
<span style={{ marginLeft: "46px" }}>:</span> <span style={{ marginLeft: "46px" }}>:</span>
</label> </label>
@ -418,51 +519,17 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
type="text" type="text"
className="form-control form-control-sm" className="form-control form-control-sm"
{...field} {...field}
id="defaultFormControlInput"
aria-describedby="defaultFormControlHelp"
/> />
<span style={{ paddingLeft: "6px" }}> <span style={{ paddingLeft: "6px", whiteSpace: "nowrap" }}>
{ <u>{assignData?.workItem?.activityMaster?.unitOfMeasurement}</u>
assignData?.workItem?.workItem?.activityMaster
?.unitOfMeasurement
}
</span> </span>
<div
className="flex align-items-center"
>
<div
ref={infoRef1}
tabIndex="0"
className="d-flex align-items-center avatar-group justify-content-center ms-2 cursor-pointer"
data-bs-toggle="popover"
data-bs-trigger="focus"
data-bs-placement="right"
data-bs-html="true"
>
<i class='bx bx-info-circle bx-xs m-0 text-secondary'></i>
</div>
</div>
</div> </div>
)} )}
/> />
</div> </div>
{errors.plannedTask && ( {errors.plannedTask && (
<div className="danger-text mt-1"> <div className="danger-text mt-1">{errors.plannedTask.message}</div>
{errors.plannedTask.message}
</div>
)}
{isHelpVisible && (
<div
className="position-absolute bg-white border p-2 rounded shadow"
style={{ zIndex: 10, marginLeft: "10px" }}
>
{/* Add your help content here */}
<p className="mb-0">
Enter the target value for today's task.
</p>
</div>
)} )}
</div> </div>
@ -476,12 +543,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
name="description" name="description"
control={control} control={control}
render={({ field }) => ( render={({ field }) => (
<textarea <textarea {...field} className="form-control" rows="2" />
{...field}
className="form-control"
id="descriptionTextarea" // Changed id for better accessibility
rows="2"
/>
)} )}
/> />
{errors.description && ( {errors.description && (

View File

@ -14,11 +14,13 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { ASSIGN_TO_PROJECT } from "../../utils/constants"; import { ASSIGN_TO_PROJECT } from "../../utils/constants";
import ConfirmModal from "../common/ConfirmModal"; import ConfirmModal from "../common/ConfirmModal";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import {useEmployeesByProjectAllocated, useManageProjectAllocation} from "../../hooks/useProjects"; import {
useEmployeesByProjectAllocated,
useManageProjectAllocation,
} from "../../hooks/useProjects";
import { useSelectedproject } from "../../slices/apiDataManager"; import { useSelectedproject } from "../../slices/apiDataManager";
const Teams = () => const Teams = () => {
{
// const {projectId} = useParams() // const {projectId} = useParams()
// const projectId = useSelector((store)=>store.localVariables.projectId) // const projectId = useSelector((store)=>store.localVariables.projectId)
const projectId = useSelectedproject(); const projectId = useSelectedproject();
@ -32,68 +34,72 @@ const Teams = () =>
const [filteredEmployees, setFilteredEmployees] = useState([]); const [filteredEmployees, setFilteredEmployees] = useState([]);
const [removingEmployeeId, setRemovingEmployeeId] = useState(null); const [removingEmployeeId, setRemovingEmployeeId] = useState(null);
const [assignedLoading, setAssignedLoading] = useState(false); const [assignedLoading, setAssignedLoading] = useState(false);
const [ activeEmployee, setActiveEmployee ] = useState( true ) const [activeEmployee, setActiveEmployee] = useState(true);
const [deleteEmployee,setDeleteEmplyee] = useState(null) const [deleteEmployee, setDeleteEmplyee] = useState(null);
const [searchTerm, setSearchTerm] = useState(""); // State for search term
const navigate = useNavigate(); const navigate = useNavigate();
const HasAssignUserPermission = useHasUserPermission( ASSIGN_TO_PROJECT ); const HasAssignUserPermission = useHasUserPermission(ASSIGN_TO_PROJECT);
const [ IsDeleteModal, setIsDeleteModal ] = useState( false ) const [IsDeleteModal, setIsDeleteModal] = useState(false);
const {projectEmployees, loading:employeeLodaing, refetch} = useEmployeesByProjectAllocated( projectId ) const {
const { projectEmployees,
mutate: submitAllocations, loading: employeeLodaing,
isPending, refetch,
isSuccess, } = useEmployeesByProjectAllocated(projectId);
isError, const {
} = useManageProjectAllocation({ mutate: submitAllocations,
onSuccessCallback: () => { isPending,
setRemovingEmployeeId(null); isSuccess,
setAssignedLoading(false); isError,
setDeleteEmplyee(null); } = useManageProjectAllocation({
closeDeleteModal(); onSuccessCallback: () => {
}, setRemovingEmployeeId(null);
onErrorCallback: () => { setAssignedLoading(false);
closeDeleteModal(); setDeleteEmplyee(null);
}, closeDeleteModal();
}); },
onErrorCallback: () => {
closeDeleteModal();
},
const removeAllocation = (item) => {
setRemovingEmployeeId(item.id);
submitAllocations({
items: [
{
empID: item.employeeId,
jobRoleId: item.jobRoleId,
projectId: projectId,
status: false,
},
],
added: false,
}); });
};
const removeAllocation = (item) => {
setRemovingEmployeeId(item.id);
submitAllocations({
items: [
{
empID: item.employeeId,
jobRoleId: item.jobRoleId,
projectId: projectId,
status: false,
},
],
added: false,
});
};
const handleEmpAlicationFormSubmit = (allocaionObj) => { const handleEmpAlicationFormSubmit = (allocaionObj) => {
let items = allocaionObj.map((item) => { let items = allocaionObj.map((item) => {
return { return {
empID: item.empID, empID: item.empID,
jobRoleId: item.jobRoleId, jobRoleId: item.jobRoleId,
projectId: projectId, projectId: projectId,
status: true, status: true,
}; };
}); });
submitAllocations({ items, added: true }); submitAllocations({ items, added: true });
setActiveEmployee(true); setActiveEmployee(true);
setFilteredEmployees(employees.filter((emp) => emp.isActive)); setFilteredEmployees(employees.filter((emp) => emp.isActive));
const dropdown = document.querySelector('select[name="DataTables_Table_0_length"]');
if (dropdown) dropdown.value = "true";
};
const dropdown = document.querySelector(
'select[name="DataTables_Table_0_length"]'
);
if (dropdown) dropdown.value = "true";
};
const getRole = (jobRoleId) => { const getRole = (jobRoleId) => {
if (loading) return "Loading..."; if (loading) return "Loading...";
@ -124,17 +130,17 @@ const {
}; };
useEffect(() => { useEffect(() => {
dispatch(changeMaster("Job Role")); dispatch(changeMaster("Job Role"));
}, [dispatch]); }, [dispatch]);
useEffect(() => { useEffect(() => {
if ( projectEmployees ) if (projectEmployees) {
{
setEmployees(projectEmployees); setEmployees(projectEmployees);
setFilteredEmployees( projectEmployees?.filter( ( emp ) => emp.isActive ) ); //setFilteredEmployees(projectEmployees?.filter((emp) => emp.isActive));
const filtered = projectEmployees.filter((emp) => emp.isActive);
setFilteredEmployees(filtered);
} }
}, [projectEmployees,employeeLodaing]); }, [projectEmployees, employeeLodaing]);
useEffect(() => { useEffect(() => {
if (data) { if (data) {
@ -142,24 +148,57 @@ const {
} }
}, [data]); }, [data]);
const filterAndSearchEmployees = useCallback(() => {
const statusFiltered = employees.filter((emp) =>
activeEmployee ? emp.isActive : !emp.isActive
);
if (searchTerm === "") {
setFilteredEmployees(statusFiltered);
return;
}
const lowercasedSearchTerm = searchTerm.toLowerCase();
const searchedAndFiltered = statusFiltered.filter((item) => {
const fullName =
`${item.firstName} ${item.middleName} ${item.lastName}`.toLowerCase();
const roleName = getRole(item.jobRoleId).toLowerCase();
return (
fullName.includes(lowercasedSearchTerm) ||
roleName.includes(lowercasedSearchTerm)
);
});
setFilteredEmployees(searchedAndFiltered);
}, [employees, activeEmployee, searchTerm, getRole]);
useEffect(() => {
filterAndSearchEmployees();
}, [employees, activeEmployee, searchTerm, filterAndSearchEmployees]);
const handleFilterEmployee = (e) => { const handleFilterEmployee = (e) => {
const filterValue = e.target.value; const filterValue = e.target.value;
if ( filterValue === "true" ) // if (filterValue === "true") {
{ // setActiveEmployee(true);
setActiveEmployee(true) // setFilteredEmployees(employees.filter((emp) => emp.isActive));
setFilteredEmployees(employees.filter((emp) => emp.isActive)); // } else {
} else { // setFilteredEmployees(employees.filter((emp) => !emp.isActive));
setFilteredEmployees( employees.filter( ( emp ) => !emp.isActive ) ); // setActiveEmployee(false);
setActiveEmployee(false) // }
} setActiveEmployee(filterValue === "true");
setSearchTerm("");
}; };
const deleteModalOpen = (item) => const handleSearch = (e) => {
{ setSearchTerm(e.target.value);
setDeleteEmplyee(item) };
setIsDeleteModal(true)
} const deleteModalOpen = (item) => {
const closeDeleteModal = ()=> setIsDeleteModal(false) setDeleteEmplyee(item);
setIsDeleteModal(true);
};
const closeDeleteModal = () => setIsDeleteModal(false);
const handler = useCallback( const handler = useCallback(
(msg) => { (msg) => {
@ -167,7 +206,7 @@ const {
refetch(); refetch();
} }
}, },
[projectId, refetch] [projectId, refetch]
); );
useEffect(() => { useEffect(() => {
@ -175,18 +214,19 @@ const {
return () => eventBus.off("assign_project_all", handler); return () => eventBus.off("assign_project_all", handler);
}, [handler]); }, [handler]);
const employeeHandler = useCallback( const employeeHandler = useCallback(
(msg) => { (msg) => {
if(filteredEmployees.some((item) => item.employeeId == msg.employeeId)){ if (filteredEmployees.some((item) => item.employeeId == msg.employeeId)) {
refetch(); refetch();
} }
},[filteredEmployees, refetch] },
[filteredEmployees, refetch]
); );
useEffect(() => { useEffect(() => {
eventBus.on("employee",employeeHandler); eventBus.on("employee", employeeHandler);
return () => eventBus.off("employee",employeeHandler) return () => eventBus.off("employee", employeeHandler);
},[employeeHandler]) }, [employeeHandler]);
return ( return (
<> <>
@ -208,8 +248,7 @@ const {
></MapUsers> ></MapUsers>
</div> </div>
{IsDeleteModal && (
{IsDeleteModal && (
<div <div
className={`modal fade ${IsDeleteModal ? "show" : ""}`} className={`modal fade ${IsDeleteModal ? "show" : ""}`}
tabIndex="-1" tabIndex="-1"
@ -220,7 +259,6 @@ const {
}} }}
aria-hidden="false" aria-hidden="false"
> >
<ConfirmModal <ConfirmModal
type={"delete"} type={"delete"}
header={"Removed Employee"} header={"Removed Employee"}
@ -235,8 +273,20 @@ const {
<div className="card card-action mb-6"> <div className="card card-action mb-6">
<div className="card-body"> <div className="card-body">
<div className="row"> <div className="row d-flex justify-content-between mb-4">
<div className="col-12 d-flex justify-content-between mb-1"> <div className="col-md-6 col-12 d-flex align-items-center">
<div className="dataTables_filter d-inline-flex align-items-center ms-2">
<input
type="search"
className="form-control form-control-sm me-4"
placeholder="Search by Name or Role"
aria-controls="DataTables_Table_0"
value={searchTerm}
onChange={handleSearch}
/>
</div>
</div>
<div className="col-md-6 col-12 d-flex justify-content-end align-items-center">
<div <div
className="dataTables_length text-start py-2 px-2" className="dataTables_length text-start py-2 px-2"
id="DataTables_Table_0_length" id="DataTables_Table_0_length"
@ -258,7 +308,7 @@ const {
</div> </div>
<button <button
type="button" type="button"
className={`link-button btn-sm m-1 ${ className={`link-button btn-primary btn-sm ${
HasAssignUserPermission ? "" : "d-none" HasAssignUserPermission ? "" : "d-none"
}`} }`}
data-bs-toggle="modal" data-bs-toggle="modal"
@ -271,91 +321,99 @@ const {
</div> </div>
<div className="table-responsive text-nowrap"> <div className="table-responsive text-nowrap">
{employeeLodaing && <p>Loading..</p>} {employeeLodaing && <p>Loading..</p>}
{!employeeLodaing && employees && employees.length > 0 && ( {!employeeLodaing &&
<table className="table "> filteredEmployees &&
<thead> filteredEmployees.length > 0 && (
<tr> <table className="table ">
<th>Name</th> <thead>
<th>Assigned Date</th> <tr>
{!activeEmployee && <th>Release Date</th>} <th>
<th>Project Role</th> <div className="text-start ms-5">Name</div>
<th>Actions</th> </th>
</tr> <th>Assigned Date</th>
</thead> {!activeEmployee && <th>Release Date</th>}
<tbody className="table-border-bottom-0"> <th>Project Role</th>
{filteredEmployees && <th>Actions</th>
filteredEmployees.map((item) => ( </tr>
<tr key={item.id}> </thead>
<td> <tbody className="table-border-bottom-0">
<div className="d-flex justify-content-start align-items-center"> {filteredEmployees &&
<Avatar filteredEmployees.map((item) => (
firstName={item.firstName} <tr key={item.id}>
lastName={item.lastName} <td>
></Avatar> <div className="d-flex justify-content-start align-items-center">
<div className="d-flex flex-column"> <Avatar
<a firstName={item.firstName}
onClick={() => lastName={item.lastName}
navigate(`/employee/${item.employeeId}?for=attendance`) ></Avatar>
} <div className="d-flex flex-column">
className="text-heading text-truncate cursor-pointer" <a
> onClick={() =>
<span className="fw-normal"> navigate(
{item.firstName} {item.middleName}{" "} `/employee/${item.employeeId}?for=attendance`
{item.lastName} )
</span> }
</a> className="text-heading text-truncate cursor-pointer"
</div>
</div>
</td>
<td>
{" "}
{moment(item.allocationDate).format(
"DD-MMM-YYYY"
)}{" "}
</td>
{!activeEmployee && <td>
{item.reAllocationDate
? moment(item.reAllocationDate).format(
"DD-MMM-YYYY"
)
: "Present"}
</td>}
<td>
<span className="badge bg-label-primary me-1">
{getRole(item.jobRoleId)}
</span>
</td>
<td>
{item.isActive && (
<button
aria-label="Delete"
type="button"
title="Remove from project"
className="btn p-0 dropdown-toggle hide-arrow"
onClick={() => deleteModalOpen(item)}
>
{" "}
{removingEmployeeId === item.id ? (
<div
className="spinner-border spinner-border-sm text-primary"
role="status"
> >
<span className="visually-hidden"> <span className="fw-normal">
Loading... {item.firstName} {item.middleName}{" "}
{item.lastName}
</span> </span>
</div> </a>
) : ( </div>
<i className="bx bx-trash me-1 text-danger"></i> </div>
)} </td>
</button> <td>
{" "}
{moment(item.allocationDate).format(
"DD-MMM-YYYY"
)}{" "}
</td>
{!activeEmployee && (
<td>
{item.reAllocationDate
? moment(item.reAllocationDate).format(
"DD-MMM-YYYY"
)
: "Present"}
</td>
)} )}
{!item.isActive && <span>Not in project</span>} <td>
</td> <span className="badge bg-label-primary me-1">
</tr> {getRole(item.jobRoleId)}
))} </span>
</tbody> </td>
</table> <td>
)} {item.isActive && (
<button
aria-label="Delete"
type="button"
title="Remove from project"
className="btn p-0 dropdown-toggle hide-arrow"
onClick={() => deleteModalOpen(item)}
>
{" "}
{removingEmployeeId === item.id ? (
<div
className="spinner-border spinner-border-sm text-primary"
role="status"
>
<span className="visually-hidden">
Loading...
</span>
</div>
) : (
<i className="bx bx-trash me-1 text-danger"></i>
)}
</button>
)}
{!item.isActive && <span>Not in project</span>}
</td>
</tr>
))}
</tbody>
</table>
)}
{!employeeLodaing && filteredEmployees.length === 0 && ( {!employeeLodaing && filteredEmployees.length === 0 && (
<div className="text-center text-muted py-3"> <div className="text-center text-muted py-3">
{activeEmployee {activeEmployee

View File

@ -31,7 +31,7 @@ const DateRangePicker = ({
clickOpens: true, clickOpens: true,
maxDate: endDate, maxDate: endDate,
onChange: (selectedDates, dateStr) => { onChange: (selectedDates, dateStr) => {
const [startDateString, endDateString] = dateStr.split(" To "); const [startDateString, endDateString] = dateStr.split(" to ");
onRangeChange?.({ startDate: startDateString, endDate: endDateString }); onRangeChange?.({ startDate: startDateString, endDate: endDateString });
}, },
}); });
@ -47,18 +47,18 @@ const DateRangePicker = ({
}, [onRangeChange, DateDifference, endDateMode]); }, [onRangeChange, DateDifference, endDateMode]);
return ( return (
<div className={`col-${sm} col-sm-${md} px-1 position-relative`}> <div className={`col-${sm} col-sm-${md} px-1`}>
<input <input
type="text" type="text"
className="form-control form-control-sm ps-2 pe-5 me-4" className="form-control form-control-sm ps-2 pe-5 me-4"
placeholder="From to End" placeholder="From to End"
id="flatpickr-range" id="flatpickr-range"
ref={inputRef} ref={inputRef}
/> />
<i <i
className="bx bx-calendar calendar-icon cursor-pointer position-absolute top-50 translate-middle-y " className="bx bx-calendar calendar-icon cursor-pointer position-relative top-50 translate-middle-y "
style={{ right: "12px" }} style={{ right: "22px", bottom: "-8px" }}
></i> ></i>
</div> </div>
); );

View File

@ -99,21 +99,22 @@ const FilterIcon = ({
}; };
return ( return (
<div className="dropdown"> <div className="dropdown" style={{marginLeft:"-14px"}}>
<a <a
className="dropdown-toggle hide-arrow cursor-pointer" className="dropdown-toggle hide-arrow cursor-pointer"
id="filterDropdown" id="filterDropdown"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false" aria-expanded="false"
> >
{/* <i className="bx bx-slider-alt ms-1" /> */}
<i <i
className="fa-solid fa-filter bx-sm" className="bx bx-slider-alt"
style={{ color: selectedBuilding || selectedFloors.length > 0 || selectedActivities.length > 0 ? "#7161EF" : "gray" }} style={{ color: selectedBuilding || selectedFloors.length > 0 || selectedActivities.length > 0 ? "#7161EF" : "gray" }}
></i> ></i>
</a> </a>
<ul <ul
className="dropdown-menu p-2" className="dropdown-menu p-2 mt-2"
aria-labelledby="filterDropdown" aria-labelledby="filterDropdown"
style={{ minWidth: "360px", fontSize: "13px" }} style={{ minWidth: "360px", fontSize: "13px" }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}

View File

@ -241,7 +241,7 @@ useEffect(() => {
{isLoading ? "Please Wait" : "Submit"} {isLoading ? "Please Wait" : "Submit"}
</button> </button>
<button <button
type="reset" type="button"
className="btn btn-sm btn-label-secondary" className="btn btn-sm btn-label-secondary"
onClick={onClose} onClick={onClose}
> >

View File

@ -116,7 +116,7 @@ useEffect(() => {
{isLoading? "Please Wait...":"Submit"} {isLoading? "Please Wait...":"Submit"}
</button> </button>
<button <button
type="reset" type="button"
className="btn btn-sm btn-label-secondary " className="btn btn-sm btn-label-secondary "
data-bs-dismiss="modal" data-bs-dismiss="modal"
aria-label="Close" aria-label="Close"

View File

@ -116,7 +116,7 @@ useEffect(() => {
{isLoading? "Please Wait...":"Submit"} {isLoading? "Please Wait...":"Submit"}
</button> </button>
<button <button
type="reset" type="button"
className="btn btn-sm btn-label-secondary " className="btn btn-sm btn-label-secondary "
data-bs-dismiss="modal" data-bs-dismiss="modal"
aria-label="Close" aria-label="Close"

View File

@ -132,7 +132,7 @@ const [descriptionLength, setDescriptionLength] = useState(data?.description?.le
{isLoading? "Please Wait...":"Submit"} {isLoading? "Please Wait...":"Submit"}
</button> </button>
<button <button
type="reset" type="button"
className="btn btn-sm btn-label-secondary" className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal" data-bs-dismiss="modal"
aria-label="Close" aria-label="Close"

View File

@ -278,7 +278,7 @@ const EditMaster = ({ master, onClose }) => {
<div className="col-12 text-center"> <div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3"> {isLoading ? "Please Wait..." : "Submit"}</button> <button type="submit" className="btn btn-sm btn-primary me-3"> {isLoading ? "Please Wait..." : "Submit"}</button>
<button <button
type="reset" type="button"
className="btn btn-sm btn-label-secondary" className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal" data-bs-dismiss="modal"
aria-label="Close" aria-label="Close"

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { cacheData, getCachedData } from "../slices/apiDataManager"; import { cacheData, getCachedData, useSelectedproject } from "../slices/apiDataManager";
import AttendanceRepository from "../repositories/AttendanceRepository"; import AttendanceRepository from "../repositories/AttendanceRepository";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import showToast from "../services/toastService"; import showToast from "../services/toastService";
@ -7,111 +7,6 @@ import { useDispatch, useSelector } from "react-redux";
import { store } from "../store/store"; import { store } from "../store/store";
import { setDefaultDateRange } from "../slices/localVariablesSlice"; import { setDefaultDateRange } from "../slices/localVariablesSlice";
// export const useAttendace =(projectId)=>{
// const [attendance, setAttendance] = useState([]);
// const[loading,setLoading] = useState(true)
// const [error, setError] = useState(null);
// const fetchData = () => {
// const Attendance_cache = getCachedData("Attendance");
// if(!Attendance_cache || Attendance_cache.projectId !== projectId){
// setLoading(true);
// AttendanceRepository.getAttendance(projectId)
// .then((response) => {
// setAttendance(response.data);
// cacheData( "Attendance", {data: response.data, projectId} )
// setLoading(false)
// })
// .catch((error) => {
// setLoading(false)
// setError("Failed to fetch data.");
// })
// } else {
// setAttendance(Attendance_cache.data);
// setLoading(false)
// }
// };
// useEffect(()=>{
// if ( projectId && projectId != 1 )
// {
// fetchData(projectId);
// }
// },[projectId])
// return {attendance,loading,error,recall:fetchData}
// }
// export const useEmployeeAttendacesLog = (id) => {
// const [logs, setLogs] = useState([]);
// const [loading, setLoading] = useState(false);
// const [error, setError] = useState(null);
// const fetchData = () => {
// const AttendanceLog_cache = getCachedData("AttendanceLogs");
// if(!AttendanceLog_cache || AttendanceLog_cache.id !== id ){
// setLoading(true)
// AttendanceRepository.getAttendanceLogs(id).then((response)=>{
// setLogs(response.data)
// cacheData("AttendanceLogs", { data: response.data, id })
// setLoading(false)
// }).catch((error)=>{
// setError("Failed to fetch data.");
// setLoading(false);
// })
// }else{
// setLogs(AttendanceLog_cache.data);
// }
// };
// useEffect(() => {
// if (id) {
// fetchData();
// }
// }, [id]);
// return { logs, loading, error };
// };
// export const useRegularizationRequests = ( projectId ) =>
// {
// const [regularizes, setregularizes] = useState([]);
// const [loading, setLoading] = useState(false);
// const [error, setError] = useState(null);
// const fetchData = () => {
// const regularizedList_cache = getCachedData("regularizedList");
// if(!regularizedList_cache || regularizedList_cache.projectId !== projectId ){
// setLoading(true)
// AttendanceRepository.getRegularizeList(projectId).then((response)=>{
// setregularizes( response.data )
// cacheData("regularizedList", { data: response.data, projectId })
// setLoading(false)
// }).catch((error)=>{
// setError("Failed to fetch data.");
// setLoading(false);
// })
// }else{
// setregularizes(regularizedList_cache.data);
// }
// };
// useEffect(() => {
// if (projectId) {
// fetchData();
// }
// }, [ projectId ] );
// return {regularizes,loading,error,refetch:fetchData}
// }
// ----------------------------Query----------------------------- // ----------------------------Query-----------------------------
@ -248,7 +143,8 @@ export const useRegularizationRequests = (projectId) => {
export const useMarkAttendance = () => { export const useMarkAttendance = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const selectedProject = useSelector((store)=>store.localVariables.projectId) // const selectedProject = useSelector((store)=>store.localVariables.projectId)
const selectedProject = useSelectedproject();
const selectedDateRange = useSelector((store)=>store.localVariables.defaultDateRange) const selectedDateRange = useSelector((store)=>store.localVariables.defaultDateRange)
return useMutation({ return useMutation({

View File

@ -176,8 +176,9 @@ export const useEmployeeProfile = (employeeId) => {
export const useEmployeesName = (projectId, search) => { export const useEmployeesName = (projectId, search) => {
return useQuery({ return useQuery({
queryKey: ["employees", projectId, search], queryKey: ["employees", projectId, search],
queryFn: async() => await EmployeeRepository.getEmployeeName(projectId, search), queryFn: async () =>
await EmployeeRepository.getEmployeeName(projectId, search),
staleTime: 5 * 60 * 1000, // Optional: cache for 5 minutes staleTime: 5 * 60 * 1000, // Optional: cache for 5 minutes
}); });
}; };
@ -194,7 +195,6 @@ export const useEmployeesNameByProject = (projectId) => {
}); });
}; };
// Mutation------------------------------------------------------------------ // Mutation------------------------------------------------------------------
export const useUpdateEmployee = () => { export const useUpdateEmployee = () => {
@ -232,36 +232,84 @@ export const useUpdateEmployee = () => {
}); });
}; };
// export const useSuspendEmployee = ({ setIsDeleteModalOpen, setemployeeLodaing }) => {
// const queryClient = useQueryClient();
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
// return useMutation({
// mutationFn: (id) => {
// setemployeeLodaing(true);
// return EmployeeRepository.deleteEmployee(id);
// },
// onSuccess: () => {
// // queryClient.invalidateQueries( ['allEmployee',false]);
// queryClient.invalidateQueries( {queryKey: [ 'projectEmployees' ]} );
// queryClient.invalidateQueries( {queryKey:[ 'employeeListByProject' ,selectedProject]} );
// showToast("Employee deleted successfully.", "success");
// setIsDeleteModalOpen(false);
// },
// onError: (error) => {
// const message =
// error.response?.data?.message ||
// error.message ||
// "An unexpected error occurred";
// showToast(message, "error");
// setIsDeleteModalOpen(false);
// },
// onSettled: () => {
// setemployeeLodaing(false);
// },
// });
// };
// Manage Role
export const useSuspendEmployee = ({ export const useSuspendEmployee = ({
setIsDeleteModalOpen, setIsDeleteModalOpen,
setemployeeLodaing, setemployeeLodaing,
}) => { }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const selectedProject = useSelector( const selectedProjectId = useSelector(
(store) => store.localVariables.projectId (store) => store.localVariables.projectId
); );
return useMutation({ return useMutation({
mutationFn: (id) => { // Expect both employeeId and active status
mutationFn: async ({ employeeId, active }) => {
setemployeeLodaing(true); setemployeeLodaing(true);
return EmployeeRepository.deleteEmployee(id); return await EmployeeRepository.deleteEmployee(employeeId, active);
}, },
onSuccess: () => { onSuccess: (_, { employeeId, active }) => {
// queryClient.invalidateQueries( ['allEmployee',false]); const message =
queryClient.invalidateQueries({ queryKey: ["projectEmployees"] }); active === false
queryClient.invalidateQueries({ ? "Employee suspended successfully."
queryKey: ["employeeListByProject", selectedProject], : "Employee reactivated successfully.";
});
showToast("Employee deleted successfully.", "success"); showToast(message, "success");
setIsDeleteModalOpen(false); setIsDeleteModalOpen(false);
// Invalidate relevant queries
queryClient.invalidateQueries({ queryKey: ["employee", employeeId] });
queryClient.invalidateQueries({ queryKey: ["allEmployees"] });
if (selectedProjectId) {
queryClient.invalidateQueries({
queryKey: ["projectEmployees", selectedProjectId],
});
}
}, },
onError: (error) => { onError: (error) => {
const message = showToast(
error.response?.data?.message || error.response?.data?.message ||
error.message || error.message ||
"An unexpected error occurred"; "An unexpected error occurred",
showToast(message, "error"); "error"
);
setIsDeleteModalOpen(false); setIsDeleteModalOpen(false);
}, },
@ -271,7 +319,6 @@ export const useSuspendEmployee = ({
}); });
}; };
// Manage Role
export const useUpdateEmployeeRoles = ({ export const useUpdateEmployeeRoles = ({
onClose, onClose,

View File

@ -4,6 +4,7 @@ import {
clearCacheKey, clearCacheKey,
getCachedData, getCachedData,
getCachedProfileData, getCachedProfileData,
useSelectedproject,
} from "../../slices/apiDataManager"; } from "../../slices/apiDataManager";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import AttendanceLog from "../../components/Activities/AttendcesLogs"; import AttendanceLog from "../../components/Activities/AttendcesLogs";
@ -25,9 +26,11 @@ import { useQueryClient } from "@tanstack/react-query";
const AttendancePage = () => { const AttendancePage = () => {
const [activeTab, setActiveTab] = useState("all"); const [activeTab, setActiveTab] = useState("all");
const [ShowPending, setShowPending] = useState(false); const [ShowPending, setShowPending] = useState(false);
const [searchTerm, setSearchTerm] = useState(""); // 🔹 New state for search
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const loginUser = getCachedProfileData(); const loginUser = getCachedProfileData();
const selectedProject = useSelector((store) => store.localVariables.projectId); // const selectedProject = useSelector((store) => store.localVariables.projectId);
const selectedProject = useSelectedproject();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [attendances, setAttendances] = useState(); const [attendances, setAttendances] = useState();
@ -69,17 +72,18 @@ const AttendancePage = () => {
setIsCreateModalOpen(false); setIsCreateModalOpen(false);
}; };
const handleToggle = (event) => {
setShowOnlyCheckout(event.target.checked);
};
useEffect(() => { useEffect(() => {
if (modelConfig !== null) { if (modelConfig !== null) {
openModel(); openModel();
} }
}, [modelConfig, isCreateModalOpen]); }, [modelConfig, isCreateModalOpen]);
// Handler to change tab and reset search term
const handleTabChange = (tabName) => {
setActiveTab(tabName);
setSearchTerm(""); // Reset search term when tab changes
};
return ( return (
<> <>
{isCreateModalOpen && modelConfig && ( {isCreateModalOpen && modelConfig && (
@ -91,11 +95,11 @@ const AttendancePage = () => {
{(modelConfig?.action === 0 || {(modelConfig?.action === 0 ||
modelConfig?.action === 1 || modelConfig?.action === 1 ||
modelConfig?.action === 2) && ( modelConfig?.action === 2) && (
<CheckCheckOutmodel <CheckCheckOutmodel
modeldata={modelConfig} modeldata={modelConfig}
closeModal={closeModal} closeModal={closeModal}
/> />
)} )}
{/* For view logs */} {/* For view logs */}
{modelConfig?.action === 6 && ( {modelConfig?.action === 6 && (
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} /> <AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
@ -120,8 +124,8 @@ const AttendancePage = () => {
type="button" type="button"
className={`nav-link ${ className={`nav-link ${
activeTab === "all" ? "active" : "" activeTab === "all" ? "active" : ""
} fs-6`} } fs-6`}
onClick={() => setActiveTab("all")} onClick={() => handleTabChange("all")}
data-bs-toggle="tab" data-bs-toggle="tab"
data-bs-target="#navs-top-home" data-bs-target="#navs-top-home"
> >
@ -133,8 +137,8 @@ const AttendancePage = () => {
type="button" type="button"
className={`nav-link ${ className={`nav-link ${
activeTab === "logs" ? "active" : "" activeTab === "logs" ? "active" : ""
} fs-6`} } fs-6`}
onClick={() => setActiveTab("logs")} onClick={() => handleTabChange("logs")}
data-bs-toggle="tab" data-bs-toggle="tab"
data-bs-target="#navs-top-profile" data-bs-target="#navs-top-profile"
> >
@ -146,40 +150,60 @@ const AttendancePage = () => {
type="button" type="button"
className={`nav-link ${ className={`nav-link ${
activeTab === "regularization" ? "active" : "" activeTab === "regularization" ? "active" : ""
} fs-6`} } fs-6`}
onClick={() => setActiveTab("regularization")} onClick={() => handleTabChange("regularization")}
data-bs-toggle="tab" data-bs-toggle="tab"
data-bs-target="#navs-top-messages" data-bs-target="#navs-top-messages"
> >
Regularization Regularization
</button> </button>
</li> </li>
{/* 🔹 Search box placed after Regularization tab */}
<li className="nav-item ms-auto me-3">
<input
type="text"
className="form-control form-control-sm mt-1"
placeholder="Search Employee..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
style={{ minWidth: "200px" }}
/>
</li>
</ul> </ul>
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3"> <div className="tab-content attedanceTabs py-0 px-1 px-sm-3">
{selectedProject ? ( {selectedProject ? (
<> <>
{activeTab === "all" && ( {activeTab === "all" && (
<div className="tab-pane fade show active py-0"> <div className="tab-pane fade show active py-0">
<Attendance handleModalData={handleModalData} getRole={getRole} /> <Attendance
</div> handleModalData={handleModalData}
)} getRole={getRole}
{activeTab === "logs" && ( searchTerm={searchTerm}
<div className="tab-pane fade show active py-0"> />
<AttendanceLog handleModalData={handleModalData} /> </div>
</div> )}
)} {activeTab === "logs" && (
{activeTab === "regularization" && DoRegularized && ( <div className="tab-pane fade show active py-0">
<div className="tab-pane fade show active py-0"> <AttendanceLog
<Regularization /> handleModalData={handleModalData}
</div> searchTerm={searchTerm}
)} />
</> </div>
) : ( )}
<div className="py-2"> {activeTab === "regularization" && DoRegularized && (
<small className="py-2">Please Select Project!</small> <div className="tab-pane fade show active py-0">
</div> <Regularization searchTerm={searchTerm} />
)} </div>
</div> )}
</>
) : (
<div className="py-2">
<small>Please Select Project!</small>
</div>
)}
</div>
</div> </div>
</div> </div>

View File

@ -16,11 +16,13 @@ import SubTask from "../../components/Activities/SubTask";
import {formatNumber} from "../../utils/dateUtils"; import {formatNumber} from "../../utils/dateUtils";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants"; import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants";
import { useSelectedproject } from "../../slices/apiDataManager";
const DailyTask = () => { const DailyTask = () => {
const selectedProject = useSelector( // const selectedProject = useSelector(
(store) => store.localVariables.projectId // (store) => store.localVariables.projectId
); // );
const selectedProject = useSelectedproject();
const dispatch = useDispatch() const dispatch = useDispatch()
const { projectNames, loading: projectLoading, fetchData } = useProjectName(); const { projectNames, loading: projectLoading, fetchData } = useProjectName();
@ -201,7 +203,7 @@ const DailyTask = () => {
<div className="card card-action mb-6 "> <div className="card card-action mb-6 ">
<div className="card-body p-1 p-sm-2"> <div className="card-body p-1 p-sm-2">
<div className="row d-flex justify-content-between align-items-center"> <div className="row d-flex justify-content-between align-items-center">
<div className="col-md-12 d-flex gap-3 align-items-center col-12 text-start mb-2 mb-md-0"> <div className="col-md-12 d-flex align-items-center col-12 text-start mb-2 mb-md-0">
<DateRangePicker <DateRangePicker
onRangeChange={setDateRange} onRangeChange={setDateRange}
endDateMode="today" endDateMode="today"

View File

@ -4,12 +4,14 @@ import InfraPlanning from "../../components/Activities/InfraPlanning";
import { useProjectName } from "../../hooks/useProjects"; import { useProjectName } from "../../hooks/useProjects";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
import { useSelectedproject } from "../../slices/apiDataManager";
const TaskPlannng = () => { const TaskPlannng = () => {
const selectedProject = useSelector( // const selectedProject = useSelector(
(store) => store.localVariables.projectId // (store) => store.localVariables.projectId
); // );
const selectedProject = useSelectedproject();
const dispatch = useDispatch() const dispatch = useDispatch()
const { projectNames, loading: projectLoading, fetchData } = useProjectName(); const { projectNames, loading: projectLoading, fetchData } = useProjectName();

View File

@ -38,7 +38,7 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
const [IsDeleting, setDeleting] = useState(false); const [IsDeleting, setDeleting] = useState(false);
const [openBucketModal, setOpenBucketModal] = useState(false); const [openBucketModal, setOpenBucketModal] = useState(false);
const [notes, setNotes] = useState([]); const [notes, setNotes] = useState([]);
const [filterAppliedNotes, setFilterAppliedNotes] = useState([]); const [filterAppliedNotes, setFilterAppliedNotes] = useState([]);
// const [selectedOrgs, setSelectedOrgs] = useState([]); // const [selectedOrgs, setSelectedOrgs] = useState([]);
// Changed to an array for multiple selections // Changed to an array for multiple selections
@ -260,7 +260,7 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
}, [prefernceContacts]); }, [prefernceContacts]);
return ( return (
<div className={IsPage ? "container-fluid":""}> <div className={IsPage ? "container-fluid" : ""}>
{IsPage && ( {IsPage && (
<Breadcrumb <Breadcrumb
data={[ data={[
@ -353,7 +353,7 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
setOpenBucketModal={setOpenBucketModal} setOpenBucketModal={setOpenBucketModal}
contactsToExport={contacts} contactsToExport={contacts}
notesToExport={notes} notesToExport={notes}
selectedNoteNames={selectedNoteNames} selectedNoteNames={selectedNoteNames}
setSelectedNoteNames={setSelectedNoteNames} setSelectedNoteNames={setSelectedNoteNames}
notesForFilter={notes} notesForFilter={notes}
setFilterAppliedNotes={setFilterAppliedNotes} setFilterAppliedNotes={setFilterAppliedNotes}
@ -361,23 +361,9 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
</div> </div>
</div> </div>
<div className="card-minHeight mt-0"> <div className="card-minHeight mt-0">
{(viewType === "card" || viewType === "list" || viewType === "notes") && (
<div className="d-flex flex-column justify-content-center align-items-center text-center">
{!loading && (viewType === "card" || viewType === "list") && contacts?.length === 0 && (
<p className="mt-10">No contact found</p>
)}
{!loading &&
(viewType === "card" || viewType === "list") &&
contacts?.length > 0 &&
currentItems.length === 0 && (
<p className="mt-10">No matching contact found</p>
)}
</div>
)}
{viewType === "list" && ( {viewType === "list" && (
<div className="card cursor-pointer mt-5"> <div className="card cursor-pointer mt-3">
<div className="card-body p-2 pb-1"> <div className="card-body p-2 pb-1" style={{ minHeight: "200px" }}>
<DirectoryListTableHeader> <DirectoryListTableHeader>
{!loading && {!loading &&
currentItems.map((contact) => ( currentItems.map((contact) => (
@ -394,12 +380,22 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
/> />
))} ))}
</DirectoryListTableHeader> </DirectoryListTableHeader>
{/* Empty state AFTER list */}
{!loading && contacts?.length === 0 && (
<p className="mt-3 ms-3 text-muted" >No contact found</p>
)}
{!loading &&
contacts?.length > 0 &&
currentItems.length === 0 && (
<p className="mt-3 ms-3 text-muted">No matching contact found</p>
)}
</div> </div>
</div> </div>
)} )}
{viewType === "card" && ( {viewType === "card" && (
<div className="row mt-4"> <div className="row mt-10">
{!loading && {!loading &&
currentItems.map((contact) => ( currentItems.map((contact) => (
<div <div
@ -418,6 +414,16 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
/> />
</div> </div>
))} ))}
{/* Empty state for card view */}
{!loading && contacts?.length === 0 && (
<p className="mt-3 ms-3 text-muted">No contact found</p>
)}
{!loading &&
contacts?.length > 0 &&
currentItems.length === 0 && (
<p className="mt-3 ms-3 text-muted">No matching contact found</p>
)}
</div> </div>
)} )}
@ -428,7 +434,7 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
setNotesForFilter={setNotes} setNotesForFilter={setNotes}
searchText={searchText} searchText={searchText}
setIsOpenModalNote={setIsOpenModalNote} setIsOpenModalNote={setIsOpenModalNote}
filterAppliedNotes={filterAppliedNotes} filterAppliedNotes={filterAppliedNotes}
/> />
</div> </div>
)} )}
@ -464,7 +470,10 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
</li> </li>
))} ))}
<li className={`page-item ${currentPage === totalPages ? "disabled" : ""}`}> <li
className={`page-item ${currentPage === totalPages ? "disabled" : ""
}`}
>
<button <button
className="page-link" className="page-link"
onClick={() => paginate(currentPage + 1)} onClick={() => paginate(currentPage + 1)}

View File

@ -3,7 +3,7 @@ import IconButton from "../../components/common/IconButton";
const DirectoryListTableHeader = ({ children }) => { const DirectoryListTableHeader = ({ children }) => {
return ( return (
<div className="table-responsive text-nowrap py-2"> <div className="table-responsive text-nowrap py-2" style={{ minHeight: "80px"}}>
<table className="table px-2"> <table className="table px-2">
<thead> <thead>
<tr> <tr>

View File

@ -318,54 +318,60 @@ const DirectoryPageHeader = ({
whiteSpace: "normal" whiteSpace: "normal"
}} }}
> >
<div className="d-flex gap-3"> {allCreators.length === 0 && filteredOrganizations.length === 0 ? (
{/* Created By */} <div className="text-center text-muted py-5">
<div style={{ flexBasis: "30%", maxHeight: "260px", overflowY: "auto" }}> No filter found
<div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}>
<p className="text-muted mb-2 pt-2">Created By</p>
</div>
{allCreators.map((name, idx) => (
<div className="form-check mb-1" key={`creator-${idx}`}>
<input
className="form-check-input form-check-input-sm"
type="checkbox"
id={`creator-${idx}`}
checked={selectedCreators.includes(name)}
onChange={() => handleToggleCreator(name)}
style={{ width: "1rem", height: "1rem" }}
/>
<label className="form-check-label text-nowrap" htmlFor={`creator-${idx}`}>
{name}
</label>
</div>
))}
</div> </div>
) : (
{/* Organization */} <div className="d-flex gap-3">
<div style={{ maxHeight: "260px", overflowY: "auto", overflowX: "hidden", }}> {/* Created By */}
<div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}> <div style={{ flexBasis: "30%", maxHeight: "260px", overflowY: "auto" }}>
<p className="text-muted mb-2 pt-2">Organization</p> <div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}>
</div> <p className="text-muted mb-2 pt-2">Created By</p>
{filteredOrganizations.map((org, idx) => (
<div className="form-check mb-1" key={`org-${idx}`}>
<input
className="form-check-input form-check-input-sm"
type="checkbox"
id={`org-${idx}`}
checked={selectedOrgs.includes(org)}
onChange={() => handleToggleOrg(org)}
style={{ width: "1rem", height: "1rem" }}
/>
<label className="form-check-label text-nowrap" htmlFor={`org-${idx}`}>
{org}
</label>
</div> </div>
))} {allCreators.map((name, idx) => (
</div> <div className="form-check mb-1" key={`creator-${idx}`}>
</div> <input
className="form-check-input form-check-input-sm"
type="checkbox"
id={`creator-${idx}`}
checked={selectedCreators.includes(name)}
onChange={() => handleToggleCreator(name)}
style={{ width: "1rem", height: "1rem" }}
/>
<label className="form-check-label text-nowrap" htmlFor={`creator-${idx}`}>
{name}
</label>
</div>
))}
</div>
{/* Organization */}
<div style={{ maxHeight: "260px", overflowY: "auto", overflowX: "hidden" }}>
<div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}>
<p className="text-muted mb-2 pt-2">Organization</p>
</div>
{filteredOrganizations.map((org, idx) => (
<div className="form-check mb-1" key={`org-${idx}`}>
<input
className="form-check-input form-check-input-sm"
type="checkbox"
id={`org-${idx}`}
checked={selectedOrgs.includes(org)}
onChange={() => handleToggleOrg(org)}
style={{ width: "1rem", height: "1rem" }}
/>
<label className="form-check-label text-nowrap" htmlFor={`org-${idx}`}>
{org}
</label>
</div>
))}
</div>
</div>
)}
</div> </div>
{/* Sticky Footer Buttons */} {/* Sticky Footer Buttons */}
<div <div
className="d-flex justify-content-end gap-2 p-2 " className="d-flex justify-content-end gap-2 p-2 "
@ -440,54 +446,74 @@ const DirectoryPageHeader = ({
<ul className="dropdown-menu p-3" style={{ width: "700px" }}> <ul className="dropdown-menu p-3" style={{ width: "700px" }}>
<p className="text-muted m-0 h6">Filter by</p> <p className="text-muted m-0 h6">Filter by</p>
<div className="d-flex flex-nowrap"> {filteredBuckets.length === 0 && filteredCategories.length === 0 ? (
<div className="mt-1 me-4" style={{ flexBasis: "50%" }}> <div className="text-center text-muted py-5">
<p className="text-small mb-1">Buckets</p> No filter found
<div className="d-flex flex-wrap"> </div>
{filteredBuckets.map(({ id, name }) => ( ) : (
<div className="form-check me-3 mb-1" style={{ minWidth: "calc(50% - 15px)" }} key={id}> <div className="d-flex flex-nowrap">
<input <div className="mt-1 me-4" style={{ flexBasis: "50%" }}>
className="form-check-input form-check-input-sm" <p className="text-small mb-1">Buckets</p>
type="checkbox" <div className="d-flex flex-wrap">
id={`bucket-${id}`} {filteredBuckets.map(({ id, name }) => (
checked={tempSelectedBucketIds.includes(id)} <div
onChange={() => handleTempBucketChange(id)} className="form-check me-3 mb-1"
style={{ width: "1rem", height: "1rem" }} style={{ minWidth: "calc(50% - 15px)" }}
/> key={id}
<label className="form-check-label text-nowrap text-small" htmlFor={`bucket-${id}`}> >
{name} <input
</label> className="form-check-input form-check-input-sm"
</div> type="checkbox"
))} id={`bucket-${id}`}
checked={tempSelectedBucketIds.includes(id)}
onChange={() => handleTempBucketChange(id)}
style={{ width: "1rem", height: "1rem" }}
/>
<label
className="form-check-label text-nowrap text-small"
htmlFor={`bucket-${id}`}
>
{name}
</label>
</div>
))}
</div>
</div>
<div className="mt-1" style={{ flexBasis: "50%" }}>
<p className="text-small mb-1">Categories</p>
<div className="d-flex flex-wrap">
{filteredCategories.map(({ id, name }) => (
<div
className="form-check me-3 mb-1"
style={{ minWidth: "calc(50% - 15px)" }}
key={id}
>
<input
className="form-check-input form-check-input-sm"
type="checkbox"
id={`cat-${id}`}
checked={tempSelectedCategoryIds.includes(id)}
onChange={() => handleTempCategoryChange(id)}
style={{ width: "1rem", height: "1rem" }}
/>
<label
className="form-check-label text-nowrap text-small"
htmlFor={`cat-${id}`}
>
{name}
</label>
</div>
))}
</div>
</div> </div>
</div> </div>
<div className="mt-1" style={{ flexBasis: "50%" }}> )}
<p className="text-small mb-1">Categories</p>
<div className="d-flex flex-wrap">
{filteredCategories.map(({ id, name }) => (
<div className="form-check me-3 mb-1" style={{ minWidth: "calc(50% - 15px)" }} key={id}>
<input
className="form-check-input form-check-input-sm"
type="checkbox"
id={`cat-${id}`}
checked={tempSelectedCategoryIds.includes(id)}
onChange={() => handleTempCategoryChange(id)}
style={{ width: "1rem", height: "1rem" }}
/>
<label className="form-check-label text-nowrap text-small" htmlFor={`cat-${id}`}>
{name}
</label>
</div>
))}
</div>
</div>
</div>
<div className="d-flex justify-content-end gap-2 mt-1"> <div className="d-flex justify-content-end gap-2 mt-1">
<button <button
className="btn btn-xs btn-secondary" className="btn btn-xs btn-secondary"
onClick={(e) => { onClick={(e) => {
// e.stopPropagation();
clearFilter(); clearFilter();
}} }}
> >
@ -503,6 +529,7 @@ const DirectoryPageHeader = ({
</button> </button>
</div> </div>
</ul> </ul>
</div> </div>
)} )}
</div> </div>

View File

@ -19,11 +19,17 @@ const LoginPage = () => {
const loginSchema = IsLoginWithOTP const loginSchema = IsLoginWithOTP
? z.object({ ? z.object({
username: z.string().email({ message: "Valid email required" }), username: z.string()
.trim()
.email({ message: "Valid email required" }),
}) })
: z.object({ : z.object({
username: z.string().email({ message: "Valid email required" }), username: z.string()
password: z.string().min(1, { message: "Password required" }), .trim()
.email({ message: "Valid email required" }),
password: z.string()
.trim()
.min(1, { message: "Password required" }),
rememberMe: z.boolean(), rememberMe: z.boolean(),
}); });
@ -41,20 +47,24 @@ const LoginPage = () => {
setLoading(true); setLoading(true);
try { try {
const username = data.username.trim();
const password = data.password?.trim();
if (!IsLoginWithOTP) { if (!IsLoginWithOTP) {
const userCredential = { const userCredential = {
username: data.username, username,
password: data.password, password,
}; };
const response = await AuthRepository.login(userCredential); const response = await AuthRepository.login(userCredential);
localStorage.setItem("jwtToken", response.data.token); localStorage.setItem("jwtToken", response.data.token);
localStorage.setItem("refreshToken", response.data.refreshToken); localStorage.setItem("refreshToken", response.data.refreshToken);
setLoading(false); setLoading(false);
navigate("/dashboard"); navigate("/dashboard");
} else { } else {
await AuthRepository.sendOTP({ email: data.username }); await AuthRepository.sendOTP({ email: username });
showToast("OTP has been sent to your email.", "success"); showToast("OTP has been sent to your email.", "success");
localStorage.setItem("otpUsername", data.username); localStorage.setItem("otpUsername", username);
localStorage.setItem("otpSentTime", now.toString()); localStorage.setItem("otpSentTime", now.toString());
navigate("/auth/login-otp"); navigate("/auth/login-otp");
} }
@ -64,6 +74,7 @@ const LoginPage = () => {
} }
}; };
useEffect(() => { useEffect(() => {
const otpSentTime = localStorage.getItem("otpSentTime"); const otpSentTime = localStorage.getItem("otpSentTime");
if ( if (

View File

@ -18,7 +18,7 @@ import {
VIEW_ALL_EMPLOYEES, VIEW_ALL_EMPLOYEES,
VIEW_TEAM_MEMBERS, VIEW_TEAM_MEMBERS,
} from "../../utils/constants"; } from "../../utils/constants";
import { clearCacheKey } from "../../slices/apiDataManager"; import { clearCacheKey, useSelectedproject } from "../../slices/apiDataManager";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp
import { import {
@ -38,9 +38,10 @@ import usePagination from "../../hooks/usePagination";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
const EmployeeList = () => { const EmployeeList = () => {
const selectedProjectId = useSelector( // const selectedProjectId = useSelector(
(store) => store.localVariables.projectId // (store) => store.localVariables.projectId
); // );
const selectedProjectId = useSelectedproject();
const { projectNames, loading: projectLoading, fetchData } = useProjectName(); const { projectNames, loading: projectLoading, fetchData } = useProjectName();
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -176,12 +177,10 @@ const EmployeeList = () => {
useEffect(() => { useEffect(() => {
if (!loading && Array.isArray(employees)) { if (!loading && Array.isArray(employees)) {
const sorted = [...employees].sort((a, b) => { const sorted = [...employees].sort((a, b) => {
const nameA = `${a.firstName || ""}${a.middleName || ""}${ const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || ""
a.lastName || "" }`.toLowerCase();
}`.toLowerCase(); const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || ""
const nameB = `${b.firstName || ""}${b.middleName || ""}${ }`.toLowerCase();
b.lastName || ""
}`.toLowerCase();
return nameA?.localeCompare(nameB); return nameA?.localeCompare(nameB);
}); });
@ -274,12 +273,21 @@ const EmployeeList = () => {
> >
<ConfirmModal <ConfirmModal
type={"delete"} type={"delete"}
header={"Suspend Employee"} header={
message={"Are you sure you want delete?"} selectedEmpFordelete?.isActive
onSubmit={suspendEmployee} ? "Suspend Employee"
: "Reactivate Employee"
}
message={`Are you sure you want to ${selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
} this employee?`}
onSubmit={() =>
suspendEmployee({
employeeId: selectedEmpFordelete.id,
active: !selectedEmpFordelete.isActive,
})
}
onClose={() => setIsDeleteModalOpen(false)} onClose={() => setIsDeleteModalOpen(false)}
loading={employeeLodaing} loading={employeeLodaing}
paramData={selectedEmpFordelete}
/> />
</div> </div>
)} )}
@ -479,9 +487,7 @@ const EmployeeList = () => {
aria-label="User: activate to sort column ascending" aria-label="User: activate to sort column ascending"
aria-sort="descending" aria-sort="descending"
> >
<div className="text-start ms-5"> <div className="text-start ms-5">Designation</div>
Official Designation
</div>
</th> </th>
<th <th
@ -505,9 +511,8 @@ const EmployeeList = () => {
Status Status
</th> </th>
<th <th
className={`sorting_disabled ${ className={`sorting_disabled ${!Manage_Employee && "d-none"
!Manage_Employee && "d-none" }`}
}`}
rowSpan="1" rowSpan="1"
colSpan="1" colSpan="1"
style={{ width: "50px" }} style={{ width: "50px" }}
@ -527,9 +532,9 @@ const EmployeeList = () => {
)} )}
{/* Conditional messages for no data or no search results */} {/* Conditional messages for no data or no search results */}
{!loading && {!loading &&
displayData?.length === 0 && displayData?.length === 0 &&
searchText && searchText &&
!showAllEmployees ? ( !showAllEmployees ? (
<tr> <tr>
<td colSpan={8}> <td colSpan={8}>
<small className="muted"> <small className="muted">
@ -539,8 +544,8 @@ const EmployeeList = () => {
</tr> </tr>
) : null} ) : null}
{!loading && {!loading &&
displayData?.length === 0 && displayData?.length === 0 &&
(!searchText || showAllEmployees) ? ( (!searchText || showAllEmployees) ? (
<tr> <tr>
<td <td
colSpan={8} colSpan={8}
@ -632,47 +637,56 @@ const EmployeeList = () => {
<i className="bx bx-dots-vertical-rounded bx-md"></i> <i className="bx bx-dots-vertical-rounded bx-md"></i>
</button> </button>
<div className="dropdown-menu dropdown-menu-end"> <div className="dropdown-menu dropdown-menu-end">
{/* View always visible */}
<button <button
onClick={() => onClick={() => navigate(`/employee/${item.id}`)}
navigate(`/employee/${item.id}`)
}
className="dropdown-item py-1" className="dropdown-item py-1"
> >
<i className="bx bx-detail bx-sm"></i> View <i className="bx bx-detail bx-sm"></i> View
</button> </button>
<button
className="dropdown-item py-1" {/* If ACTIVE employee */}
onClick={() => { {item.isActive && (
handleEmployeeModel(item.id);
}}
>
<i className="bx bx-edit bx-sm"></i> Edit
</button>
{!item.isSystem && (
<> <>
<button <button
className="dropdown-item py-1" className="dropdown-item py-1"
onClick={() => onClick={() => handleEmployeeModel(item.id)}
handleOpenDelete(item.id)
}
> >
<i className="bx bx-task-x bx-sm"></i>{" "} <i className="bx bx-edit bx-sm"></i> Edit
Suspend
</button> </button>
{/* Suspend only when active */}
{item.isActive && (
<button
className="dropdown-item py-1"
onClick={() => handleOpenDelete(item)}
>
<i className="bx bx-task-x bx-sm"></i> Suspend
</button>
)}
<button <button
className="dropdown-item py-1" className="dropdown-item py-1"
type="button" type="button"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#managerole-modal" data-bs-target="#managerole-modal"
onClick={() => onClick={() => setEmpForManageRole(item.id)}
setEmpForManageRole(item.id)
}
> >
<i className="bx bx-cog bx-sm"></i>{" "} <i className="bx bx-cog bx-sm"></i> Manage Role
Manage Role
</button> </button>
</> </>
)} )}
{/* If INACTIVE employee AND inactive toggle is ON */}
{!item.isActive && showInactive && (
<button
className="dropdown-item py-1"
onClick={() => handleOpenDelete(item)}
>
<i className="bx bx-refresh bx-sm me-1"></i> Re-activate
</button>
)}
</div> </div>
</div> </div>
</td> </td>
@ -689,9 +703,8 @@ const EmployeeList = () => {
<nav aria-label="Page"> <nav aria-label="Page">
<ul className="pagination pagination-sm justify-content-end py-1"> <ul className="pagination pagination-sm justify-content-end py-1">
<li <li
className={`page-item ${ className={`page-item ${currentPage === 1 ? "disabled" : ""
currentPage === 1 ? "disabled" : "" }`}
}`}
> >
<button <button
className="page-link btn-xs" className="page-link btn-xs"
@ -704,9 +717,8 @@ const EmployeeList = () => {
{[...Array(totalPages)]?.map((_, index) => ( {[...Array(totalPages)]?.map((_, index) => (
<li <li
key={index} key={index}
className={`page-item ${ className={`page-item ${currentPage === index + 1 ? "active" : ""
currentPage === index + 1 ? "active" : "" }`}
}`}
> >
<button <button
className="page-link" className="page-link"
@ -718,9 +730,8 @@ const EmployeeList = () => {
))} ))}
<li <li
className={`page-item ${ className={`page-item ${currentPage === totalPages ? "disabled" : ""
currentPage === totalPages ? "disabled" : "" }`}
}`}
> >
<button <button
className="page-link" className="page-link"

View File

@ -10,7 +10,7 @@ const EmployeeRepository = {
updateEmployee: (id, data) => api.put(`/users/${id}`, data), updateEmployee: (id, data) => api.put(`/users/${id}`, data),
// deleteEmployee: ( id ) => api.delete( `/users/${ id }` ), // deleteEmployee: ( id ) => api.delete( `/users/${ id }` ),
getEmployeeProfile: (id) => api.get(`/api/employee/profile/get/${id}`), getEmployeeProfile: (id) => api.get(`/api/employee/profile/get/${id}`),
deleteEmployee: (id) => api.delete(`/api/employee/${id}`), deleteEmployee: (id,active) => api.delete(`/api/employee/${id}?active=${active}`),
getEmployeeName: (projectId, search) => { getEmployeeName: (projectId, search) => {
const params = new URLSearchParams(); const params = new URLSearchParams();