Merge pull request 'Issues_Aug_1W' (#355) from Issues_Aug_1W into main
Reviewed-on: #355 merged
This commit is contained in:
commit
18390e9368
@ -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>
|
||||
</>
|
||||
);
|
||||
|
@ -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" : ""}`}>
|
||||
|
@ -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();
|
||||
|
@ -15,13 +15,15 @@ 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";
|
||||
|
||||
|
||||
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 )
|
||||
|
||||
|
||||
|
@ -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 && (
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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:{" "}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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")}
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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;
|
@ -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>
|
||||
<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 && (
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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()}
|
||||
|
@ -241,7 +241,7 @@ useEffect(() => {
|
||||
{isLoading ? "Please Wait" : "Submit"}
|
||||
</button>
|
||||
<button
|
||||
type="reset"
|
||||
type="button"
|
||||
className="btn btn-sm btn-label-secondary"
|
||||
onClick={onClose}
|
||||
>
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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({
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
||||
@ -201,7 +203,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"
|
||||
|
@ -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();
|
||||
|
@ -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)}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 (
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user