Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into Tenant_Manag

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

View File

@ -6,7 +6,7 @@ import RenderAttendanceStatus from "./RenderAttendanceStatus";
import { useSelector, useDispatch } from "react-redux";
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
import DateRangePicker from "../common/DateRangePicker";
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager";
import { clearCacheKey, getCachedData, useSelectedproject } from "../../slices/apiDataManager";
import eventBus from "../../services/eventBus";
import AttendanceRepository from "../../repositories/AttendanceRepository";
import { useAttendancesLogs } from "../../hooks/useAttendance";
@ -33,12 +33,11 @@ const usePagination = (data, itemsPerPage) => {
};
};
const AttendanceLog = ({
handleModalData,
}) => {
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const AttendanceLog = ({ handleModalData, searchTerm }) => {
// const selectedProject = useSelector(
// (store) => store.localVariables.projectId
// );
const selectedProject = useSelectedproject();
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
@ -139,17 +138,29 @@ const AttendanceLog = ({
filtering(data);
}, [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 {
currentPage,
totalPages,
currentItems: paginatedAttendances,
paginate,
resetPage,
} = usePagination(processedData, 20);
} = usePagination(filteredSearchData, 20);
useEffect(() => {
resetPage();
}, [processedData, resetPage]);
}, [filteredSearchData, resetPage]);
const handler = useCallback(
(msg) => {
@ -160,20 +171,23 @@ const AttendanceLog = ({
startDate <= checkIn &&
checkIn <= endDate
) {
queryClient.setQueriesData(["attendanceLogs"],(oldData)=>{
if(!oldData) {
queryClient.invalidateQueries({queryKey:["attendanceLogs"]})
queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
if (!oldData) {
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
return;
}
return oldData.map((record) =>
record.id === msg.response.id ? { ...record, ...msg.response } : record
);
})
filtering(updatedAttendance);
const updatedAttendance = oldData.map((record) =>
record.id === msg.response.id
? { ...record, ...msg.response }
: record
);
filtering(updatedAttendance);
return updatedAttendance;
});
resetPage();
}
},
[selectedProject, dateRange, data, filtering, resetPage]
[selectedProject, dateRange, filtering, resetPage]
);
useEffect(() => {
@ -196,7 +210,7 @@ const AttendanceLog = ({
refetch()
}
},
[selectedProject, dateRange, data]
[selectedProject, dateRange, data, refetch]
);
useEffect(() => {
@ -240,8 +254,10 @@ const AttendanceLog = ({
</div>
<div className="table-responsive text-nowrap">
{isLoading ? (
<div><p className="text-secondary">Loading...</p></div>
) : data?.length > 0 ? (
<div>
<p className="text-secondary">Loading...</p>
</div>
) : filteredSearchData?.length > 0 ? (
<table className="table mb-0">
<thead>
<tr>
@ -332,10 +348,12 @@ const AttendanceLog = ({
<div className="my-4"><span className="text-secondary">No Record Available !</span></div>
)}
</div>
{paginatedAttendances?.length == 0 && data?.length > 0 && (
<div className="my-4"><span className="text-secondary">No Pending Record Available !</span></div>
)}
{processedData.length > 10 && (
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (
<div className="my-4">
<span className="text-secondary">No Pending Record Available !</span>
</div>
)}
{filteredSearchData.length > 10 && (
<nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1">
<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 { checkIfCurrentDate } from "../../utils/dateUtils";
import { useMarkAttendance } from "../../hooks/useAttendance";
import { useSelectedproject } from "../../slices/apiDataManager";
const createSchema = (modeldata) => {
return z
@ -43,7 +44,8 @@ const createSchema = (modeldata) => {
};
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 [isLoading, setIsLoading] = useState(false);
const coords = usePositionTracker();

View File

@ -15,6 +15,7 @@ import {useDispatch, useSelector} from "react-redux";
import {useProfile} from "../../hooks/useProfile";
import {refreshData, setProjectId} from "../../slices/localVariablesSlice";
import InfraTable from "../Project/Infrastructure/InfraTable";
import { useSelectedproject } from "../../slices/apiDataManager";
import Loader from "../common/Loader";
@ -22,7 +23,8 @@ const InfraPlanning = () =>
{
const {profile: LoggedUser, refetch : fetchData} = useProfile()
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 )

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 { convertShortTime } from "../../utils/dateUtils";
import RegularizationActions from "./RegularizationActions";
@ -7,12 +7,13 @@ import { useRegularizationRequests } from "../../hooks/useAttendance";
import moment from "moment";
import usePagination from "../../hooks/usePagination";
import eventBus from "../../services/eventBus";
import { cacheData, clearCacheKey } from "../../slices/apiDataManager";
import { cacheData, clearCacheKey, useSelectedproject } from "../../slices/apiDataManager";
import { useQueryClient } from "@tanstack/react-query";
const Regularization = ({ handleRequest }) => {
const Regularization = ({ handleRequest, searchTerm }) => {
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 { regularizes, loading, error, refetch } =
useRegularizationRequests(selectedProject);
@ -30,8 +31,6 @@ const Regularization = ({ handleRequest }) => {
const handler = useCallback(
(msg) => {
if (selectedProject == msg.projectId) {
queryClient.setQueryData(
["regularizedList", selectedProject],
(oldData) => {
@ -47,12 +46,27 @@ const Regularization = ({ handleRequest }) => {
[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(() => {
eventBus.on("regularization", handler);
return () => eventBus.off("regularization", handler);
@ -130,8 +144,9 @@ const Regularization = ({ handleRequest }) => {
</table>
) : (
<div className="my-4">
{" "}
<span className="text-secondary">No Requests Found !</span>
<span className="text-secondary">
{searchTerm ? "No results found for your search." : "No Requests Found !"}
</span>
</div>
)}
{!loading && totalPages > 1 && (

View File

@ -4,7 +4,7 @@ import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
import { useDispatch, useSelector } from 'react-redux';
import { usePositionTracker } from '../../hooks/usePositionTracker';
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 { useMarkAttendance } from '../../hooks/useAttendance';
import { useQueryClient } from '@tanstack/react-query';
@ -17,7 +17,8 @@ const [loadingReject,setLoadingForReject] = useState(false)
const {mutate:MarkAttendance,isPending} = useMarkAttendance()
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 dispatch = useDispatch()

View File

@ -98,42 +98,42 @@ const AttendanceOverview = () => {
colors: roles.map((_, i) => flatColors[i % flatColors.length]),
};
return (
<div className="bg-white p-4 rounded shadow d-flex flex-column">
{/* Header */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div className="card-title mb-0 text-start">
<h5 className="mb-1">Attendance Overview</h5>
<p className="card-subtitle">Role-wise present count</p>
</div>
<div className="d-flex gap-2">
<select
className="form-select form-select-sm w-auto"
value={dayRange}
onChange={(e) => setDayRange(Number(e.target.value))}
>
<option value={7}>Last 7 Days</option>
<option value={15}>Last 15 Days</option>
<option value={30}>Last 30 Days</option>
</select>
<button
className={`btn btn-sm p-1 ${
view === "chart" ? "btn-primary" : "btn-outline-primary"
}`}
onClick={() => setView("chart")}
>
<i class="bx bx-bar-chart fs-5"></i>
</button>
<button
className={`btn btn-sm p-1 ${
view === "table" ? "btn-primary" : "btn-outline-primary"
}`}
onClick={() => setView("table")}
>
<i class="bx bx-table fs-5"></i>
</button>
</div>
</div>
return (
<div
className="bg-white p-4 rounded shadow d-flex flex-column"
>
{/* Header */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div className="card-title mb-0 text-start">
<h5 className="mb-1">Attendance Overview</h5>
<p className="card-subtitle">Role-wise present count</p>
</div>
<div className="d-flex gap-2">
<select
className="form-select form-select-sm"
value={dayRange}
onChange={(e) => setDayRange(Number(e.target.value))}
>
<option value={7}>Last 7 Days</option>
<option value={15}>Last 15 Days</option>
<option value={30}>Last 30 Days</option>
</select>
<button
className={`btn btn-sm ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`}
onClick={() => setView("chart")}
title="Chart View"
>
<i className="bx bx-bar-chart-alt-2"></i>
</button>
<button
className={`btn btn-sm ${view === "table" ? "btn-primary" : "btn-outline-primary"}`}
onClick={() => setView("table")}
title="Table View"
>
<i className="bx bx-task text-success"></i>
</button>
</div>
</div>
{/* Content */}
<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 { profile } = useProfile();
const [bucketList, setBucketList] = useState([]);
const {employeesList} = useAllEmployees( false );
const { employeesList } = useAllEmployees(false);
const [selectedEmployee, setSelectEmployee] = useState([]);
const { buckets, loading, refetch } = useBuckets();
const [action_bucket, setAction_bucket] = useState(false);
@ -237,9 +237,8 @@ const ManageBucket = () => {
onChange={(e) => setSearchTerm(e.target.value)}
/>
<i
className={`bx bx-refresh cursor-pointer fs-4 ${
loading ? "spin" : ""
}`}
className={`bx bx-refresh cursor-pointer fs-4 ${loading ? "spin" : ""
}`}
title="Refresh"
onClick={() => refetch()}
/>
@ -248,9 +247,8 @@ const ManageBucket = () => {
<button
type="button"
className={`btn btn-sm btn-primary ms-auto ${
action_bucket ? "d-none" : ""
}`}
className={`btn btn-sm btn-primary ms-auto ${action_bucket ? "d-none" : ""
}`}
onClick={() => {
setAction_bucket(true);
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">
{loading && (
<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...
</div>
</div>
)}
{!loading && buckets.length === 0 && (
{!loading && buckets.length === 0 && searchTerm.trim() === "" && (
<div className="col-12">
<div className="d-flex justify-content-center align-items-center py-5">
No Buckets Available.
<div
className="d-flex justify-content-center align-items-center py-5 w-100"
style={{ marginLeft: "250px" }}
>
No buckets available.
</div>
</div>
)}
{!loading && sortedBucktesList.length === 0 && (
{!loading && buckets.length > 0 && sortedBucktesList.length === 0 && (
<div className="col-12">
<div className="d-flex justify-content-center align-items-center py-5">
No Matching Bucket Found.
<div
className="d-flex justify-content-center align-items-center py-5 w-100"
style={{ marginLeft: "250px" }}
>
No matching buckets found.
</div>
</div>
)}
@ -296,29 +305,29 @@ const ManageBucket = () => {
{(DirManager ||
DirAdmin ||
bucket?.createdBy?.id ===
profile?.employeeInfo?.id) && (
<div className="d-flex gap-2">
<i
className="bx bx-edit bx-sm text-primary cursor-pointer"
onClick={() => {
select_bucket(bucket);
setAction_bucket(true);
const initialSelectedEmployees = employeesList
.filter((emp) =>
bucket.employeeIds?.includes(
emp.employeeId
profile?.employeeInfo?.id) && (
<div className="d-flex gap-2">
<i
className="bx bx-edit bx-sm text-primary cursor-pointer"
onClick={() => {
select_bucket(bucket);
setAction_bucket(true);
const initialSelectedEmployees = employeesList
.filter((emp) =>
bucket.employeeIds?.includes(
emp.employeeId
)
)
)
.map((emp) => ({ ...emp, isActive: true }));
setSelectEmployee(initialSelectedEmployees);
}}
></i>
<i
className="bx bx-trash bx-sm text-danger cursor-pointer ms-0"
onClick={() => setDeleteBucket(bucket?.id)}
></i>
</div>
)}
.map((emp) => ({ ...emp, isActive: true }));
setSelectEmployee(initialSelectedEmployees);
}}
></i>
<i
className="bx bx-trash bx-sm text-danger cursor-pointer ms-0"
onClick={() => setDeleteBucket(bucket?.id)}
></i>
</div>
)}
</h6>
<h6 className="card-subtitle mb-2 text-muted text-start">
Contacts:{" "}

View File

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

View File

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

View File

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

View File

@ -1,109 +1,163 @@
import React from "react";
import Avatar from "../common/Avatar";
import { useProfile } from "../../hooks/useProfile";
const EmpOverview = ({ profile }) => {
const { loggedInUserProfile } = useProfile();
return (
<>
{" "}
<div className="row">
<div className="col-12 mb-4">
<div className="card">
<div className="card-body">
<small className="card-text text-uppercase text-body-secondary small">
About
</small>
<ul className="list-unstyled my-3 py-1">
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-user"></i>
<span className="fw-medium mx-2">Full Name:</span>{" "}
<span>{`${profile?.firstName} ${profile?.lastName}`}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-check"></i>
<span className="fw-medium mx-2">Status:</span>{" "}
<span>Active</span>
</li>
<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 className="row">
<div className="col-12 mb-4">
<div className="card">
<div className="card-body">
{/* About Heading */}
<small className="card-text text-uppercase text-body-secondary small d-block text-start mb-3">
About
</small>
{/* Full Name */}
<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>Full Name</span>
</span>
<span style={{ marginLeft: "74px" }}>:</span>
<span className="ms-5">
{profile?.firstName || <em>NA</em>} {profile?.lastName || ""}
</span>
</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>
);
};
export default EmpOverview;

View File

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

View File

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

View File

@ -395,7 +395,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
<div className="row my-2">
<div className="col-md-6">
<label htmlFor="statusId" className="form-label ">
TransactionId
Transaction ID
</label>
<input
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(
pathname
);
const isExpensePage = /^\/expenses$/.test(pathname);
return !(isDirectoryPath || isProfilePage);
return !(isDirectoryPath || isProfilePage || isExpensePage);
};
const allowedProjectStatusIds = [
"603e994b-a27f-4e5d-a251-f3d69b0498ba",
@ -475,4 +476,4 @@ const Header = () => {
</nav>
);
};
export default Header;
export default Header;

View File

@ -49,6 +49,9 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
const infoRef = useRef(null);
const infoRef1 = useRef(null);
// State for search term
const [searchTerm, setSearchTerm] = useState("");
useEffect(() => {
if (typeof bootstrap !== "undefined") {
if (infoRef.current) {
@ -81,9 +84,11 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
recallEmployeeData,
} = useEmployeesAllOrByProjectId(false, selectedProject, false);
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 {
handleSubmit,
@ -121,20 +126,79 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
useEffect(() => {
dispatch(changeMaster("Job Role"));
return () => setSelectedRole("all");
// Initial state should reflect "All Roles" selected
setSelectedRoles(["all"]);
}, [dispatch]);
const handleRoleChange = (event) => {
setSelectedRole(event.target.value);
// Modified handleRoleChange to handle multiple selections
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 =
selectedRole === "all"
? employees
: employees?.filter(
(emp) => String(emp.jobRoleId || "") === selectedRole
);
useEffect(() => {
// Update displayedSelection based on selectedRoles
if (selectedRoles.includes("all")) {
setDisplayedSelection("All Roles");
} 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 selectedEmployeeIds = data.selectedEmployees;
@ -192,118 +256,183 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
<div className="form-text text-start">
<div className="d-flex align-items-center form-text fs-7">
<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">
<li key="all">
<button
type="button"
className="dropdown-item py-1"
onClick={() =>
handleRoleChange({
target: { value: "all" },
})
}
{/* Dropdown */}
<div className="dropdown position-relative d-inline-block">
<a
className={`dropdown-toggle hide-arrow cursor-pointer ${selectedRoles.includes("all") || selectedRoles.length === 0
? "text-secondary"
: "text-primary"
}`}
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
</button>
</li>
{jobRoleData?.map((user) => (
<li key={user.id}>
<button
type="button"
className="dropdown-item py-1"
value={user.id}
onClick={handleRoleChange}
>
{user.name}
</button>
{selectedRolesCount}
</span>
)}
{/* Dropdown Menu with Scroll */}
<ul
className="dropdown-menu p-2 text-capitalize"
style={{ maxHeight: "300px", overflowY: "auto" }}
>
{/* All Roles */}
<li key="all">
<div className="form-check dropdown-item py-0">
<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>
))}
</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 className="row">
<div className="col-12 h-sm-25 overflow-auto mt-2" style={{height:"300px"}}>
{selectedRole !== "" && (
<div className="row">
{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
);
{/* Employees list */}
<div
className="col-12 mt-2"
style={{
maxHeight: "280px",
overflowY: "auto",
overflowX: "hidden",
}}
>
{selectedRoles?.length > 0 && (
<div className="row">
{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 (
<div
key={emp.id}
className="col-6 col-md-4 col-lg-3 mb-3"
>
<div className="form-check d-flex align-items-start">
<Controller
name="selectedEmployees"
control={control}
render={({ field }) => (
<input
{...field}
className="form-check-input me-1 mt-1"
type="checkbox"
id={`employee-${emp?.id}`}
value={emp.id}
checked={field.value?.includes(emp.id)}
onChange={(e) => {
handleCheckboxChange(e, emp);
}}
/>
return (
<div
key={emp.id}
className="col-6 col-md-4 col-lg-3 mb-3"
>
<div className="form-check d-flex align-items-start">
<Controller
name="selectedEmployees"
control={control}
render={({ field }) => (
<input
{...field}
className="form-check-input me-1 mt-1"
type="checkbox"
id={`employee-${emp?.id}`}
value={emp.id}
checked={field.value?.includes(emp.id)}
onChange={(e) => {
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"
)}
/>
<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>
</small>
</div>
</div>
);
})
) : (
<div className="col-12">
<p className="text-center">
</div>
);
})
) : (
<div className="col-12">
<p className="text-center">
No employees found for the selected role.
</p>
</div>
)}
</div>
)}
</div>
</p>
</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="form-check form-check-inline mt-3 px-1">
<label
className="form-text text-dark align-items-center d-flex"
htmlFor="inlineCheckbox1"
>
<label className="form-text text-dark align-items-center d-flex">
Pending Task of Activity :
<label
className="form-check-label fs-7 ms-4"
htmlFor="inlineCheckbox1"
>
<label className="form-check-label fs-7 ms-4">
<strong>
{assignData?.workItem?.plannedWork -
assignData?.workItem?.completedWork}
</strong>{" "}
<u>
{
assignData?.workItem?.activityMaster
?.unitOfMeasurement
}
</u>
<u>{assignData?.workItem?.activityMaster?.unitOfMeasurement}</u>
</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>
</div>
</div>
@ -397,10 +501,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
{/* Target for Today input and validation */}
<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">
<label
className="text-dark text-start d-flex align-items-center flex-wrap form-text"
htmlFor="inlineCheckbox1"
>
<label className="text-dark d-flex align-items-center flex-wrap form-text">
<span>Target for Today</span>&nbsp;
<span style={{ marginLeft: "46px" }}>:</span>
</label>
@ -418,51 +519,17 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
type="text"
className="form-control form-control-sm"
{...field}
id="defaultFormControlInput"
aria-describedby="defaultFormControlHelp"
/>
<span style={{ paddingLeft: "6px" }}>
{
assignData?.workItem?.workItem?.activityMaster
?.unitOfMeasurement
}
<span style={{ paddingLeft: "6px", whiteSpace: "nowrap" }}>
<u>{assignData?.workItem?.activityMaster?.unitOfMeasurement}</u>
</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>
{errors.plannedTask && (
<div className="danger-text mt-1">
{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 className="danger-text mt-1">{errors.plannedTask.message}</div>
)}
</div>
@ -476,12 +543,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
name="description"
control={control}
render={({ field }) => (
<textarea
{...field}
className="form-control"
id="descriptionTextarea" // Changed id for better accessibility
rows="2"
/>
<textarea {...field} className="form-control" rows="2" />
)}
/>
{errors.description && (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { cacheData, getCachedData } from "../slices/apiDataManager";
import { cacheData, getCachedData, useSelectedproject } from "../slices/apiDataManager";
import AttendanceRepository from "../repositories/AttendanceRepository";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import showToast from "../services/toastService";
@ -7,111 +7,6 @@ import { useDispatch, useSelector } from "react-redux";
import { store } from "../store/store";
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-----------------------------
@ -248,7 +143,8 @@ export const useRegularizationRequests = (projectId) => {
export const useMarkAttendance = () => {
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)
return useMutation({

View File

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

View File

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

View File

@ -16,11 +16,13 @@ import SubTask from "../../components/Activities/SubTask";
import {formatNumber} from "../../utils/dateUtils";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants";
import { useSelectedproject } from "../../slices/apiDataManager";
const DailyTask = () => {
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
// const selectedProject = useSelector(
// (store) => store.localVariables.projectId
// );
const selectedProject = useSelectedproject();
const dispatch = useDispatch()
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
@ -203,7 +205,7 @@ const DailyTask = () => {
<div className="card card-action mb-6 ">
<div className="card-body p-1 p-sm-2">
<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
onRangeChange={setDateRange}
endDateMode="today"

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import IconButton from "../../components/common/IconButton";
const DirectoryListTableHeader = ({ children }) => {
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">
<thead>
<tr>

View File

@ -318,54 +318,60 @@ const DirectoryPageHeader = ({
whiteSpace: "normal"
}}
>
<div className="d-flex gap-3">
{/* Created By */}
<div style={{ flexBasis: "30%", maxHeight: "260px", overflowY: "auto" }}>
<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>
))}
{allCreators.length === 0 && filteredOrganizations.length === 0 ? (
<div className="text-center text-muted py-5">
No filter found
</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 className="d-flex gap-3">
{/* Created By */}
<div style={{ flexBasis: "30%", maxHeight: "260px", overflowY: "auto" }}>
<div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}>
<p className="text-muted mb-2 pt-2">Created By</p>
</div>
))}
</div>
</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>
{/* 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>
{/* Sticky Footer Buttons */}
<div
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" }}>
<p className="text-muted m-0 h6">Filter by</p>
<div className="d-flex flex-nowrap">
<div className="mt-1 me-4" style={{ flexBasis: "50%" }}>
<p className="text-small mb-1">Buckets</p>
<div className="d-flex flex-wrap">
{filteredBuckets.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={`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>
))}
{filteredBuckets.length === 0 && filteredCategories.length === 0 ? (
<div className="text-center text-muted py-5">
No filter found
</div>
) : (
<div className="d-flex flex-nowrap">
<div className="mt-1 me-4" style={{ flexBasis: "50%" }}>
<p className="text-small mb-1">Buckets</p>
<div className="d-flex flex-wrap">
{filteredBuckets.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={`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 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">
<button
className="btn btn-xs btn-secondary"
onClick={(e) => {
// e.stopPropagation();
clearFilter();
}}
>
@ -503,6 +529,7 @@ const DirectoryPageHeader = ({
</button>
</div>
</ul>
</div>
)}
</div>

View File

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

View File

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

View File

@ -10,7 +10,7 @@ const EmployeeRepository = {
updateEmployee: (id, data) => api.put(`/users/${id}`, data),
// deleteEmployee: ( id ) => api.delete( `/users/${ 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) => {
const params = new URLSearchParams();