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 moment from "moment";
|
||||||
import Avatar from "../common/Avatar";
|
import Avatar from "../common/Avatar";
|
||||||
import { convertShortTime } from "../../utils/dateUtils";
|
import { convertShortTime } from "../../utils/dateUtils";
|
||||||
@ -10,16 +10,18 @@ import { useAttendance } from "../../hooks/useAttendance";
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
|
import { useSelectedproject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const Attendance = ({ getRole, handleModalData }) => {
|
const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [todayDate, setTodayDate] = useState(new Date());
|
const [todayDate, setTodayDate] = useState(new Date());
|
||||||
const [ShowPending, setShowPending] = useState(false);
|
const [ShowPending, setShowPending] = useState(false);
|
||||||
const selectedProject = useSelector(
|
// const selectedProject = useSelector(
|
||||||
(store) => store.localVariables.projectId
|
// (store) => store.localVariables.projectId
|
||||||
);
|
// );
|
||||||
|
const selectedProject = useSelectedproject();
|
||||||
const {
|
const {
|
||||||
attendance,
|
attendance,
|
||||||
loading: attLoading,
|
loading: attLoading,
|
||||||
@ -28,8 +30,8 @@ const Attendance = ({ getRole, handleModalData }) => {
|
|||||||
} = useAttendance(selectedProject);
|
} = useAttendance(selectedProject);
|
||||||
const filteredAttendance = ShowPending
|
const filteredAttendance = ShowPending
|
||||||
? attendance?.filter(
|
? attendance?.filter(
|
||||||
(att) => att?.checkInTime !== null && att?.checkOutTime === null
|
(att) => att?.checkInTime !== null && att?.checkOutTime === null
|
||||||
)
|
)
|
||||||
: attendance;
|
: attendance;
|
||||||
|
|
||||||
const attendanceList = Array.isArray(filteredAttendance)
|
const attendanceList = Array.isArray(filteredAttendance)
|
||||||
@ -48,18 +50,40 @@ const Attendance = ({ getRole, handleModalData }) => {
|
|||||||
.filter((d) => d.activity === 0)
|
.filter((d) => d.activity === 0)
|
||||||
.sort(sortByName);
|
.sort(sortByName);
|
||||||
|
|
||||||
const filteredData = [...group1, ...group2];
|
const finalFilteredData = useMemo(() => {
|
||||||
|
const combinedData = [...group1, ...group2];
|
||||||
|
if (!searchTerm) {
|
||||||
|
return combinedData;
|
||||||
|
}
|
||||||
|
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||||
|
|
||||||
|
return combinedData.filter((item) => {
|
||||||
|
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
|
||||||
|
const role = item.jobRoleName?.toLowerCase() || "";
|
||||||
|
return (
|
||||||
|
fullName.includes(lowercasedSearchTerm) ||
|
||||||
|
role.includes(lowercasedSearchTerm) // ✅ also search by role
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [group1, group2, searchTerm]);
|
||||||
|
|
||||||
|
|
||||||
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
||||||
filteredData,
|
finalFilteredData,
|
||||||
ITEMS_PER_PAGE
|
ITEMS_PER_PAGE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reset pagination when the filter or search term changes
|
||||||
|
useEffect(() => {
|
||||||
|
}, [finalFilteredData]);
|
||||||
|
|
||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if (selectedProject == msg.projectId) {
|
if (selectedProject == msg.projectId) {
|
||||||
queryClient.setQueryData(["attendance", selectedProject], (oldData) => {
|
queryClient.setQueryData(["attendance", selectedProject], (oldData) => {
|
||||||
if (!oldData) {
|
if (!oldData) {
|
||||||
queryClient.invalidateQueries({queryKey:["attendance"]})
|
queryClient.invalidateQueries({ queryKey: ["attendance"] })
|
||||||
};
|
};
|
||||||
return oldData.map((record) =>
|
return oldData.map((record) =>
|
||||||
record.employeeId === msg.response.employeeId ? { ...record, ...msg.response } : record
|
record.employeeId === msg.response.employeeId ? { ...record, ...msg.response } : record
|
||||||
@ -72,7 +96,7 @@ const Attendance = ({ getRole, handleModalData }) => {
|
|||||||
|
|
||||||
const employeeHandler = useCallback(
|
const employeeHandler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if (attendances.some((item) => item.employeeId == msg.employeeId)) {
|
if (attendance.some((item) => item.employeeId == msg.employeeId)) {
|
||||||
attrecall();
|
attrecall();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -106,7 +130,9 @@ const Attendance = ({ getRole, handleModalData }) => {
|
|||||||
<label className="form-check-label ms-0">Show Pending</label>
|
<label className="form-check-label ms-0">Show Pending</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{Array.isArray(attendance) && attendance.length > 0 ? (
|
{attLoading ? (
|
||||||
|
<div>Loading...</div>
|
||||||
|
) : currentItems?.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<table className="table ">
|
<table className="table ">
|
||||||
<thead>
|
<thead>
|
||||||
@ -188,13 +214,12 @@ const Attendance = ({ getRole, handleModalData }) => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{!loading && filteredData.length > 20 && (
|
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
|
||||||
<nav aria-label="Page ">
|
<nav aria-label="Page ">
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
<li
|
<li
|
||||||
className={`page-item ${
|
className={`page-item ${currentPage === 1 ? "disabled" : ""
|
||||||
currentPage === 1 ? "disabled" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link btn-xs"
|
className="page-link btn-xs"
|
||||||
@ -206,9 +231,8 @@ const Attendance = ({ getRole, handleModalData }) => {
|
|||||||
{[...Array(totalPages)].map((_, index) => (
|
{[...Array(totalPages)].map((_, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className={`page-item ${
|
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
||||||
currentPage === index + 1 ? "active" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link "
|
className="page-link "
|
||||||
@ -219,9 +243,8 @@ const Attendance = ({ getRole, handleModalData }) => {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<li
|
<li
|
||||||
className={`page-item ${
|
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||||
currentPage === totalPages ? "disabled" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link "
|
className="page-link "
|
||||||
@ -234,19 +257,15 @@ const Attendance = ({ getRole, handleModalData }) => {
|
|||||||
</nav>
|
</nav>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : attLoading ? (
|
|
||||||
<div>Loading...</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="text-muted">
|
<div className="text-muted my-4">
|
||||||
{Array.isArray(attendance)
|
{searchTerm
|
||||||
? "No employees assigned to the project"
|
? "No results found for your search."
|
||||||
: "Attendance data unavailable"}
|
: attendanceList.length === 0
|
||||||
|
? "No employees assigned to the project."
|
||||||
|
: "No pending records available."}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentItems?.length == 0 && attendance.length > 0 && (
|
|
||||||
<div className="my-4"><span className="text-secondary">No Pending Record Available !</span></div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,7 @@ import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
|||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
|
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
|
||||||
import DateRangePicker from "../common/DateRangePicker";
|
import DateRangePicker from "../common/DateRangePicker";
|
||||||
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager";
|
import { clearCacheKey, getCachedData, useSelectedproject } from "../../slices/apiDataManager";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import AttendanceRepository from "../../repositories/AttendanceRepository";
|
import AttendanceRepository from "../../repositories/AttendanceRepository";
|
||||||
import { useAttendancesLogs } from "../../hooks/useAttendance";
|
import { useAttendancesLogs } from "../../hooks/useAttendance";
|
||||||
@ -33,12 +33,11 @@ const usePagination = (data, itemsPerPage) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const AttendanceLog = ({
|
const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||||
handleModalData,
|
// const selectedProject = useSelector(
|
||||||
}) => {
|
// (store) => store.localVariables.projectId
|
||||||
const selectedProject = useSelector(
|
// );
|
||||||
(store) => store.localVariables.projectId
|
const selectedProject = useSelectedproject();
|
||||||
);
|
|
||||||
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@ -139,17 +138,29 @@ const AttendanceLog = ({
|
|||||||
filtering(data);
|
filtering(data);
|
||||||
}, [data, showPending]);
|
}, [data, showPending]);
|
||||||
|
|
||||||
|
// New useEffect to handle search filtering
|
||||||
|
const filteredSearchData = useMemo(() => {
|
||||||
|
if (!searchTerm) {
|
||||||
|
return processedData;
|
||||||
|
}
|
||||||
|
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||||
|
return processedData.filter((item) => {
|
||||||
|
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
|
||||||
|
return fullName.includes(lowercasedSearchTerm);
|
||||||
|
});
|
||||||
|
}, [processedData, searchTerm]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
currentPage,
|
currentPage,
|
||||||
totalPages,
|
totalPages,
|
||||||
currentItems: paginatedAttendances,
|
currentItems: paginatedAttendances,
|
||||||
paginate,
|
paginate,
|
||||||
resetPage,
|
resetPage,
|
||||||
} = usePagination(processedData, 20);
|
} = usePagination(filteredSearchData, 20);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resetPage();
|
resetPage();
|
||||||
}, [processedData, resetPage]);
|
}, [filteredSearchData, resetPage]);
|
||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
@ -160,20 +171,23 @@ const AttendanceLog = ({
|
|||||||
startDate <= checkIn &&
|
startDate <= checkIn &&
|
||||||
checkIn <= endDate
|
checkIn <= endDate
|
||||||
) {
|
) {
|
||||||
queryClient.setQueriesData(["attendanceLogs"],(oldData)=>{
|
queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
|
||||||
if(!oldData) {
|
if (!oldData) {
|
||||||
queryClient.invalidateQueries({queryKey:["attendanceLogs"]})
|
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return oldData.map((record) =>
|
const updatedAttendance = oldData.map((record) =>
|
||||||
record.id === msg.response.id ? { ...record, ...msg.response } : record
|
record.id === msg.response.id
|
||||||
);
|
? { ...record, ...msg.response }
|
||||||
})
|
: record
|
||||||
|
);
|
||||||
filtering(updatedAttendance);
|
filtering(updatedAttendance);
|
||||||
|
return updatedAttendance;
|
||||||
|
});
|
||||||
resetPage();
|
resetPage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedProject, dateRange, data, filtering, resetPage]
|
[selectedProject, dateRange, filtering, resetPage]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -196,7 +210,7 @@ const AttendanceLog = ({
|
|||||||
refetch()
|
refetch()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedProject, dateRange, data]
|
[selectedProject, dateRange, data, refetch]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -240,8 +254,10 @@ const AttendanceLog = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="table-responsive text-nowrap">
|
<div className="table-responsive text-nowrap">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div><p className="text-secondary">Loading...</p></div>
|
<div>
|
||||||
) : data?.length > 0 ? (
|
<p className="text-secondary">Loading...</p>
|
||||||
|
</div>
|
||||||
|
) : filteredSearchData?.length > 0 ? (
|
||||||
<table className="table mb-0">
|
<table className="table mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -332,10 +348,12 @@ const AttendanceLog = ({
|
|||||||
<div className="my-4"><span className="text-secondary">No Record Available !</span></div>
|
<div className="my-4"><span className="text-secondary">No Record Available !</span></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{paginatedAttendances?.length == 0 && data?.length > 0 && (
|
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (
|
||||||
<div className="my-4"><span className="text-secondary">No Pending Record Available !</span></div>
|
<div className="my-4">
|
||||||
)}
|
<span className="text-secondary">No Pending Record Available !</span>
|
||||||
{processedData.length > 10 && (
|
</div>
|
||||||
|
)}
|
||||||
|
{filteredSearchData.length > 10 && (
|
||||||
<nav aria-label="Page ">
|
<nav aria-label="Page ">
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
||||||
|
@ -9,6 +9,7 @@ import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice";
|
|||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
||||||
import { useMarkAttendance } from "../../hooks/useAttendance";
|
import { useMarkAttendance } from "../../hooks/useAttendance";
|
||||||
|
import { useSelectedproject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const createSchema = (modeldata) => {
|
const createSchema = (modeldata) => {
|
||||||
return z
|
return z
|
||||||
@ -43,7 +44,8 @@ const createSchema = (modeldata) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CheckCheckOutmodel = ({ modeldata, closeModal, handleSubmitForm }) => {
|
const CheckCheckOutmodel = ({ modeldata, closeModal, handleSubmitForm }) => {
|
||||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
// const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
|
const projectId = useSelectedproject();
|
||||||
const { mutate: MarkAttendance } = useMarkAttendance();
|
const { mutate: MarkAttendance } = useMarkAttendance();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const coords = usePositionTracker();
|
const coords = usePositionTracker();
|
||||||
|
@ -15,13 +15,15 @@ import {useDispatch, useSelector} from "react-redux";
|
|||||||
import {useProfile} from "../../hooks/useProfile";
|
import {useProfile} from "../../hooks/useProfile";
|
||||||
import {refreshData, setProjectId} from "../../slices/localVariablesSlice";
|
import {refreshData, setProjectId} from "../../slices/localVariablesSlice";
|
||||||
import InfraTable from "../Project/Infrastructure/InfraTable";
|
import InfraTable from "../Project/Infrastructure/InfraTable";
|
||||||
|
import { useSelectedproject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
|
|
||||||
const InfraPlanning = () =>
|
const InfraPlanning = () =>
|
||||||
{
|
{
|
||||||
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
||||||
|
const selectedProject = useSelectedproject();
|
||||||
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
|
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState, useMemo } from "react";
|
||||||
import Avatar from "../common/Avatar";
|
import Avatar from "../common/Avatar";
|
||||||
import { convertShortTime } from "../../utils/dateUtils";
|
import { convertShortTime } from "../../utils/dateUtils";
|
||||||
import RegularizationActions from "./RegularizationActions";
|
import RegularizationActions from "./RegularizationActions";
|
||||||
@ -7,12 +7,13 @@ import { useRegularizationRequests } from "../../hooks/useAttendance";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import usePagination from "../../hooks/usePagination";
|
import usePagination from "../../hooks/usePagination";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import { cacheData, clearCacheKey } from "../../slices/apiDataManager";
|
import { cacheData, clearCacheKey, useSelectedproject } from "../../slices/apiDataManager";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
const Regularization = ({ handleRequest }) => {
|
const Regularization = ({ handleRequest, searchTerm }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
var selectedProject = useSelector((store) => store.localVariables.projectId);
|
// var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||||
|
const selectedProject = useSelectedproject();
|
||||||
const [regularizesList, setregularizedList] = useState([]);
|
const [regularizesList, setregularizedList] = useState([]);
|
||||||
const { regularizes, loading, error, refetch } =
|
const { regularizes, loading, error, refetch } =
|
||||||
useRegularizationRequests(selectedProject);
|
useRegularizationRequests(selectedProject);
|
||||||
@ -30,8 +31,6 @@ const Regularization = ({ handleRequest }) => {
|
|||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if (selectedProject == msg.projectId) {
|
if (selectedProject == msg.projectId) {
|
||||||
|
|
||||||
|
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
["regularizedList", selectedProject],
|
["regularizedList", selectedProject],
|
||||||
(oldData) => {
|
(oldData) => {
|
||||||
@ -47,12 +46,27 @@ const Regularization = ({ handleRequest }) => {
|
|||||||
[selectedProject, regularizes]
|
[selectedProject, regularizes]
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredData = [...regularizesList]?.sort(sortByName);
|
// Filter the data based on the search term and sort it
|
||||||
|
const filteredSearchData = useMemo(() => {
|
||||||
|
const sortedList = [...regularizesList].sort(sortByName);
|
||||||
|
if (!searchTerm) {
|
||||||
|
return sortedList;
|
||||||
|
}
|
||||||
|
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||||
|
return sortedList.filter((item) => {
|
||||||
|
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
|
||||||
|
return fullName.includes(lowercasedSearchTerm);
|
||||||
|
});
|
||||||
|
}, [regularizesList, searchTerm]);
|
||||||
|
|
||||||
|
const { currentPage, totalPages, currentItems, paginate } =
|
||||||
|
usePagination(filteredSearchData, 20);
|
||||||
|
|
||||||
|
// Reset pagination when the search term or data changes
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
}, [filteredSearchData]);
|
||||||
|
|
||||||
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
|
||||||
filteredData,
|
|
||||||
20
|
|
||||||
);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eventBus.on("regularization", handler);
|
eventBus.on("regularization", handler);
|
||||||
return () => eventBus.off("regularization", handler);
|
return () => eventBus.off("regularization", handler);
|
||||||
@ -130,8 +144,9 @@ const Regularization = ({ handleRequest }) => {
|
|||||||
</table>
|
</table>
|
||||||
) : (
|
) : (
|
||||||
<div className="my-4">
|
<div className="my-4">
|
||||||
{" "}
|
<span className="text-secondary">
|
||||||
<span className="text-secondary">No Requests Found !</span>
|
{searchTerm ? "No results found for your search." : "No Requests Found !"}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!loading && totalPages > 1 && (
|
{!loading && totalPages > 1 && (
|
||||||
|
@ -4,7 +4,7 @@ import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
||||||
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
|
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
|
||||||
import {cacheData, getCachedData} from '../../slices/apiDataManager';
|
import {cacheData, getCachedData, useSelectedproject} from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import { useMarkAttendance } from '../../hooks/useAttendance';
|
import { useMarkAttendance } from '../../hooks/useAttendance';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
@ -17,7 +17,8 @@ const [loadingReject,setLoadingForReject] = useState(false)
|
|||||||
const {mutate:MarkAttendance,isPending} = useMarkAttendance()
|
const {mutate:MarkAttendance,isPending} = useMarkAttendance()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const projectId = useSelector((store)=>store.localVariables.projectId)
|
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
||||||
|
const projectId = useSelectedproject();
|
||||||
const {latitude,longitude} = usePositionTracker();
|
const {latitude,longitude} = usePositionTracker();
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
@ -98,42 +98,42 @@ const AttendanceOverview = () => {
|
|||||||
colors: roles.map((_, i) => flatColors[i % flatColors.length]),
|
colors: roles.map((_, i) => flatColors[i % flatColors.length]),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-4 rounded shadow d-flex flex-column">
|
<div
|
||||||
{/* Header */}
|
className="bg-white p-4 rounded shadow d-flex flex-column"
|
||||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
>
|
||||||
<div className="card-title mb-0 text-start">
|
{/* Header */}
|
||||||
<h5 className="mb-1">Attendance Overview</h5>
|
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||||
<p className="card-subtitle">Role-wise present count</p>
|
<div className="card-title mb-0 text-start">
|
||||||
</div>
|
<h5 className="mb-1">Attendance Overview</h5>
|
||||||
<div className="d-flex gap-2">
|
<p className="card-subtitle">Role-wise present count</p>
|
||||||
<select
|
</div>
|
||||||
className="form-select form-select-sm w-auto"
|
<div className="d-flex gap-2">
|
||||||
value={dayRange}
|
<select
|
||||||
onChange={(e) => setDayRange(Number(e.target.value))}
|
className="form-select form-select-sm"
|
||||||
>
|
value={dayRange}
|
||||||
<option value={7}>Last 7 Days</option>
|
onChange={(e) => setDayRange(Number(e.target.value))}
|
||||||
<option value={15}>Last 15 Days</option>
|
>
|
||||||
<option value={30}>Last 30 Days</option>
|
<option value={7}>Last 7 Days</option>
|
||||||
</select>
|
<option value={15}>Last 15 Days</option>
|
||||||
<button
|
<option value={30}>Last 30 Days</option>
|
||||||
className={`btn btn-sm p-1 ${
|
</select>
|
||||||
view === "chart" ? "btn-primary" : "btn-outline-primary"
|
<button
|
||||||
}`}
|
className={`btn btn-sm ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`}
|
||||||
onClick={() => setView("chart")}
|
onClick={() => setView("chart")}
|
||||||
>
|
title="Chart View"
|
||||||
<i class="bx bx-bar-chart fs-5"></i>
|
>
|
||||||
</button>
|
<i className="bx bx-bar-chart-alt-2"></i>
|
||||||
<button
|
</button>
|
||||||
className={`btn btn-sm p-1 ${
|
<button
|
||||||
view === "table" ? "btn-primary" : "btn-outline-primary"
|
className={`btn btn-sm ${view === "table" ? "btn-primary" : "btn-outline-primary"}`}
|
||||||
}`}
|
onClick={() => setView("table")}
|
||||||
onClick={() => setView("table")}
|
title="Table View"
|
||||||
>
|
>
|
||||||
<i class="bx bx-table fs-5"></i>
|
<i className="bx bx-task text-success"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-grow-1 d-flex align-items-center justify-content-center">
|
<div className="flex-grow-1 d-flex align-items-center justify-content-center">
|
||||||
|
@ -19,7 +19,7 @@ import { useProfile } from "../../hooks/useProfile";
|
|||||||
const ManageBucket = () => {
|
const ManageBucket = () => {
|
||||||
const { profile } = useProfile();
|
const { profile } = useProfile();
|
||||||
const [bucketList, setBucketList] = useState([]);
|
const [bucketList, setBucketList] = useState([]);
|
||||||
const {employeesList} = useAllEmployees( false );
|
const { employeesList } = useAllEmployees(false);
|
||||||
const [selectedEmployee, setSelectEmployee] = useState([]);
|
const [selectedEmployee, setSelectEmployee] = useState([]);
|
||||||
const { buckets, loading, refetch } = useBuckets();
|
const { buckets, loading, refetch } = useBuckets();
|
||||||
const [action_bucket, setAction_bucket] = useState(false);
|
const [action_bucket, setAction_bucket] = useState(false);
|
||||||
@ -237,9 +237,8 @@ const ManageBucket = () => {
|
|||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
className={`bx bx-refresh cursor-pointer fs-4 ${
|
className={`bx bx-refresh cursor-pointer fs-4 ${loading ? "spin" : ""
|
||||||
loading ? "spin" : ""
|
}`}
|
||||||
}`}
|
|
||||||
title="Refresh"
|
title="Refresh"
|
||||||
onClick={() => refetch()}
|
onClick={() => refetch()}
|
||||||
/>
|
/>
|
||||||
@ -248,9 +247,8 @@ const ManageBucket = () => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm btn-primary ms-auto ${
|
className={`btn btn-sm btn-primary ms-auto ${action_bucket ? "d-none" : ""
|
||||||
action_bucket ? "d-none" : ""
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAction_bucket(true);
|
setAction_bucket(true);
|
||||||
select_bucket(null);
|
select_bucket(null);
|
||||||
@ -267,22 +265,33 @@ const ManageBucket = () => {
|
|||||||
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 pt-3 px-2 px-sm-0">
|
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 pt-3 px-2 px-sm-0">
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<div className="d-flex justify-content-center align-items-center py-5">
|
<div
|
||||||
|
className="d-flex justify-content-center align-items-center py-5 w-100"
|
||||||
|
style={{ marginLeft: "250px" }}
|
||||||
|
>
|
||||||
Loading...
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!loading && buckets.length === 0 && (
|
|
||||||
|
{!loading && buckets.length === 0 && searchTerm.trim() === "" && (
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<div className="d-flex justify-content-center align-items-center py-5">
|
<div
|
||||||
No Buckets Available.
|
className="d-flex justify-content-center align-items-center py-5 w-100"
|
||||||
|
style={{ marginLeft: "250px" }}
|
||||||
|
>
|
||||||
|
No buckets available.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!loading && sortedBucktesList.length === 0 && (
|
|
||||||
|
{!loading && buckets.length > 0 && sortedBucktesList.length === 0 && (
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<div className="d-flex justify-content-center align-items-center py-5">
|
<div
|
||||||
No Matching Bucket Found.
|
className="d-flex justify-content-center align-items-center py-5 w-100"
|
||||||
|
style={{ marginLeft: "250px" }}
|
||||||
|
>
|
||||||
|
No matching buckets found.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -296,29 +305,29 @@ const ManageBucket = () => {
|
|||||||
{(DirManager ||
|
{(DirManager ||
|
||||||
DirAdmin ||
|
DirAdmin ||
|
||||||
bucket?.createdBy?.id ===
|
bucket?.createdBy?.id ===
|
||||||
profile?.employeeInfo?.id) && (
|
profile?.employeeInfo?.id) && (
|
||||||
<div className="d-flex gap-2">
|
<div className="d-flex gap-2">
|
||||||
<i
|
<i
|
||||||
className="bx bx-edit bx-sm text-primary cursor-pointer"
|
className="bx bx-edit bx-sm text-primary cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
select_bucket(bucket);
|
select_bucket(bucket);
|
||||||
setAction_bucket(true);
|
setAction_bucket(true);
|
||||||
const initialSelectedEmployees = employeesList
|
const initialSelectedEmployees = employeesList
|
||||||
.filter((emp) =>
|
.filter((emp) =>
|
||||||
bucket.employeeIds?.includes(
|
bucket.employeeIds?.includes(
|
||||||
emp.employeeId
|
emp.employeeId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
.map((emp) => ({ ...emp, isActive: true }));
|
||||||
.map((emp) => ({ ...emp, isActive: true }));
|
setSelectEmployee(initialSelectedEmployees);
|
||||||
setSelectEmployee(initialSelectedEmployees);
|
}}
|
||||||
}}
|
></i>
|
||||||
></i>
|
<i
|
||||||
<i
|
className="bx bx-trash bx-sm text-danger cursor-pointer ms-0"
|
||||||
className="bx bx-trash bx-sm text-danger cursor-pointer ms-0"
|
onClick={() => setDeleteBucket(bucket?.id)}
|
||||||
onClick={() => setDeleteBucket(bucket?.id)}
|
></i>
|
||||||
></i>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
</h6>
|
</h6>
|
||||||
<h6 className="card-subtitle mb-2 text-muted text-start">
|
<h6 className="card-subtitle mb-2 text-muted text-start">
|
||||||
Contacts:{" "}
|
Contacts:{" "}
|
||||||
|
@ -114,39 +114,56 @@ const NotesDirectory = ({
|
|||||||
? contactProfile?.notes || []
|
? contactProfile?.notes || []
|
||||||
: contactNotes || [];
|
: contactNotes || [];
|
||||||
|
|
||||||
|
const hasNotes = notesToDisplay.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-start mt-10">
|
<div className="text-start mt-10">
|
||||||
<div className="d-flex align-items-center justify-content-between">
|
<div className="d-flex align-items-center justify-content-between">
|
||||||
<div className="row w-100 align-items-center">
|
<div className="row w-100 align-items-center">
|
||||||
<div className="col col-2">
|
{hasNotes && (
|
||||||
<p className="fw-semibold m-0 ms-3">Notes :</p>
|
<div className="col col-2">
|
||||||
</div>
|
<p className="fw-semibold m-0 ms-3">Notes :</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="col d-flex justify-content-end gap-2 pe-0">
|
<div className="col d-flex justify-content-end gap-2 pe-0">
|
||||||
{" "}
|
{" "}
|
||||||
<div className="d-flex align-items-center justify-content-between">
|
<div className="d-flex align-items-center justify-content-between">
|
||||||
<label
|
|
||||||
className="switch switch-primary"
|
<label
|
||||||
style={{
|
className="switch switch-primary"
|
||||||
visibility:
|
style={{
|
||||||
contactProfile?.notes?.length > 0 ||
|
fontSize: "15px",
|
||||||
contactNotes?.length > 0
|
}}
|
||||||
? "visible"
|
>
|
||||||
: "hidden",
|
<input
|
||||||
}}
|
type="checkbox"
|
||||||
>
|
className="switch-input"
|
||||||
<input
|
onChange={() => handleSwitch(!IsActive)}
|
||||||
type="checkbox"
|
checked={IsActive}
|
||||||
className="switch-input"
|
style={{
|
||||||
onChange={() => handleSwitch(!IsActive)}
|
transform: "scale(0.8)", // smaller toggle
|
||||||
value={IsActive}
|
}}
|
||||||
/>
|
/>
|
||||||
<input type="checkbox" className="switch-input" />
|
<span
|
||||||
<span className="switch-toggle-slider">
|
className="switch-toggle-slider"
|
||||||
<span className="switch-on"></span>
|
style={{
|
||||||
<span className="switch-off"></span>
|
width: "30px", // narrower slider
|
||||||
</span>
|
height: "15px", // shorter slider
|
||||||
<span className="switch-label">Include Deleted Notes</span>
|
}}
|
||||||
</label>
|
>
|
||||||
|
<span className="switch-on"></span>
|
||||||
|
<span className="switch-off"></span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="switch-label"
|
||||||
|
style={{
|
||||||
|
fontSize: "14px", // smaller label text
|
||||||
|
marginLeft: "-14px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Include Deleted Notes
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
{!showEditor && (
|
{!showEditor && (
|
||||||
<div className="d-flex justify-content-end">
|
<div className="d-flex justify-content-end">
|
||||||
@ -222,7 +239,7 @@ const NotesDirectory = ({
|
|||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: !isLoading &&
|
: !isLoading &&
|
||||||
!showEditor && (
|
!showEditor && (
|
||||||
<div className="text-center mt-5">{noNotesMessage}</div>
|
<div className="text-center mt-5">{noNotesMessage}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,6 +11,7 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
|||||||
const [profileContactState, setProfileContactState] = useState(null);
|
const [profileContactState, setProfileContactState] = useState(null);
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
// Safely access description, defaulting to an empty string if not present
|
||||||
const description = profileContactState?.description || "";
|
const description = profileContactState?.description || "";
|
||||||
const limit = 500;
|
const limit = 500;
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
|||||||
middleName = names[1]; // This was an error in the original prompt, corrected to names[1]
|
middleName = names[1]; // This was an error in the original prompt, corrected to names[1]
|
||||||
lastName = names[names.length - 1];
|
lastName = names[names.length - 1];
|
||||||
// Reconstruct full name to be precise with spacing
|
// Reconstruct full name to be precise with spacing
|
||||||
fullName = `${firstName} ${middleName ? middleName + ' ' : ''}${lastName}`;
|
fullName = `${firstName} ${middleName ? middleName + " " : ""}${lastName}`;
|
||||||
} else {
|
} else {
|
||||||
// Fallback if no names or empty string
|
// Fallback if no names or empty string
|
||||||
firstName = "Contact";
|
firstName = "Contact";
|
||||||
@ -113,7 +114,7 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
|||||||
</span>
|
</span>
|
||||||
<i
|
<i
|
||||||
className={`bx bx-copy-alt cursor-pointer bx-xs text-start ${copiedIndex === idx ? "text-secondary" : "text-primary"
|
className={`bx bx-copy-alt cursor-pointer bx-xs text-start ${copiedIndex === idx ? "text-secondary" : "text-primary"
|
||||||
}`}
|
}`}
|
||||||
title={copiedIndex === idx ? "Copied!" : "Copy Email"}
|
title={copiedIndex === idx ? "Copied!" : "Copy Email"}
|
||||||
style={{ flexShrink: 0 }}
|
style={{ flexShrink: 0 }}
|
||||||
onClick={() => handleCopy(email.emailAddress, idx)}
|
onClick={() => handleCopy(email.emailAddress, idx)}
|
||||||
@ -292,31 +293,35 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="d-flex mb-2 align-items-start" style={{ marginLeft: "3rem" }}>
|
{description && (
|
||||||
<div className="d-flex" style={{ minWidth: "130px" }}>
|
<div
|
||||||
<span className="d-flex align-items-start">
|
className="d-flex mb-2 align-items-start"
|
||||||
<i className="bx bx-book me-1"></i>
|
style={{ marginLeft: "3rem" }}
|
||||||
<span>Description</span>
|
>
|
||||||
</span>
|
<div className="d-flex" style={{ minWidth: "130px" }}>
|
||||||
<span style={{ marginLeft: "10px" }}>:</span>
|
<span className="d-flex align-items-start">
|
||||||
</div>
|
<i className="bx bx-book me-1"></i>
|
||||||
|
<span>Description</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: "10px" }}>:</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="text-start">
|
<div className="text-start">
|
||||||
{displayText}
|
{displayText}
|
||||||
{isLong && (
|
{isLong && (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
<span
|
<span
|
||||||
onClick={toggleReadMore}
|
onClick={toggleReadMore}
|
||||||
className="text-primary mx-1 cursor-pointer"
|
className="text-primary mx-1 cursor-pointer"
|
||||||
>
|
>
|
||||||
{expanded ? "Read less" : "Read more"}
|
{expanded ? "Read less" : "Read more"}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
|
||||||
<hr className="my-1" />
|
<hr className="my-1" />
|
||||||
<NotesDirectory
|
<NotesDirectory
|
||||||
|
@ -20,58 +20,58 @@ const EmpDashboard = ({ profile }) => {
|
|||||||
<div className="col col-sm-6 pt-5">
|
<div className="col col-sm-6 pt-5">
|
||||||
<div className="card ">
|
<div className="card ">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<small className="card-text text-uppercase text-body-secondary small">
|
<small className="card-text text-uppercase text-body-secondary small text-start d-block">
|
||||||
My Projects
|
My Projects
|
||||||
</small>{" "}
|
</small>
|
||||||
<ul className="list-unstyled text-start my-3 py-1">
|
<ul className="list-unstyled text-start my-3 py-1">
|
||||||
{selectedProjectLoding && <span>Loading</span>}
|
{selectedProjectLoding && <span>Loading</span>}
|
||||||
{projectList.map((project) => (
|
{projectList.map((project) => (
|
||||||
<li
|
<li
|
||||||
className="d-flex mb-4 align-items-start flex-wrap"
|
className="d-flex mb-4 align-items-start flex-wrap"
|
||||||
key={project.id}
|
key={project.id}
|
||||||
>
|
>
|
||||||
{/* Project Info */}
|
{/* Project Info */}
|
||||||
<div className="flex-grow-1">
|
<div className="flex-grow-1">
|
||||||
<div className="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
<div className="d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
<div className="avatar flex-shrink-0 me-3">
|
<div className="avatar flex-shrink-0 me-3">
|
||||||
<span className="avatar-initial rounded bg-label-primary">
|
<span className="avatar-initial rounded bg-label-primary">
|
||||||
<i className="icon-base bx bx-buildings icon-lg"></i>
|
<i className="icon-base bx bx-buildings icon-lg"></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h6 className="mb-0">{project.projectShortName}</h6>
|
<h6 className="mb-0">{project.projectShortName}</h6>
|
||||||
<small className="text-muted">{project.projectName}</small>
|
<small className="text-muted">{project.projectName}</small>
|
||||||
<div className="label-secondary">
|
<div className="label-secondary">
|
||||||
Assigned:{" "}
|
Assigned:{" "}
|
||||||
{project.assignedDate ? (
|
{project.assignedDate ? (
|
||||||
new Date(project.assignedDate).toLocaleDateString()
|
new Date(project.assignedDate).toLocaleDateString()
|
||||||
) : (
|
) : (
|
||||||
<em>NA</em>
|
<em>NA</em>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="badge bg-label-secondary">
|
<span className="badge bg-label-secondary">
|
||||||
{project.designation}
|
{project.designation}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Dates */}
|
{/* Dates */}
|
||||||
{project.removedDate && (
|
{project.removedDate && (
|
||||||
<div className="mt-2 d-flex flex-column flex-sm-row justify-content-between">
|
<div className="mt-2 d-flex flex-column flex-sm-row justify-content-between">
|
||||||
<div className="label-secondary">
|
<div className="label-secondary">
|
||||||
Unassigned:{" "}
|
Unassigned:{" "}
|
||||||
{new Date(project.removedDate).toLocaleDateString()}
|
{new Date(project.removedDate).toLocaleDateString()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,109 +1,163 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Avatar from "../common/Avatar";
|
|
||||||
import { useProfile } from "../../hooks/useProfile";
|
import { useProfile } from "../../hooks/useProfile";
|
||||||
|
|
||||||
const EmpOverview = ({ profile }) => {
|
const EmpOverview = ({ profile }) => {
|
||||||
const { loggedInUserProfile } = useProfile();
|
const { loggedInUserProfile } = useProfile();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="row">
|
||||||
{" "}
|
<div className="col-12 mb-4">
|
||||||
<div className="row">
|
<div className="card">
|
||||||
<div className="col-12 mb-4">
|
<div className="card-body">
|
||||||
<div className="card">
|
|
||||||
<div className="card-body">
|
{/* About Heading */}
|
||||||
<small className="card-text text-uppercase text-body-secondary small">
|
<small className="card-text text-uppercase text-body-secondary small d-block text-start mb-3">
|
||||||
About
|
About
|
||||||
</small>
|
</small>
|
||||||
<ul className="list-unstyled my-3 py-1">
|
|
||||||
<li className="d-flex align-items-center mb-4">
|
{/* Full Name */}
|
||||||
<i className="icon-base bx bx-user"></i>
|
<div className="d-flex align-items-start mb-3">
|
||||||
<span className="fw-medium mx-2">Full Name:</span>{" "}
|
<span className="d-flex">
|
||||||
<span>{`${profile?.firstName} ${profile?.lastName}`}</span>
|
<i className="bx bx-user bx-xs me-2 mt-1"></i>
|
||||||
</li>
|
<span>Full Name</span>
|
||||||
<li className="d-flex align-items-center mb-4">
|
</span>
|
||||||
<i className="icon-base bx bx-check"></i>
|
<span style={{ marginLeft: "74px" }}>:</span>
|
||||||
<span className="fw-medium mx-2">Status:</span>{" "}
|
<span className="ms-5">
|
||||||
<span>Active</span>
|
{profile?.firstName || <em>NA</em>} {profile?.lastName || ""}
|
||||||
</li>
|
</span>
|
||||||
<li className="d-flex align-items-center mb-4">
|
|
||||||
<i className="icon-base bx bx-crown"></i>
|
|
||||||
<span className="fw-medium mx-2">Role:</span>{" "}
|
|
||||||
<span> {profile?.jobRole || <em>NA</em>}</span>
|
|
||||||
</li>
|
|
||||||
<li className="d-flex align-items-center mb-4">
|
|
||||||
<i className="icon-base bx bx-flag"></i>
|
|
||||||
<span className="fw-medium mx-2">Gender:</span>{" "}
|
|
||||||
<span> {profile?.gender || <em>NA</em>}</span>
|
|
||||||
</li>{" "}
|
|
||||||
<li className="d-flex align-items-center mb-2">
|
|
||||||
<i className="icon-base bx bx-calendar "></i>
|
|
||||||
<span className="fw-medium mx-2">Birth Date:</span>{" "}
|
|
||||||
<span>
|
|
||||||
{profile?.birthDate ? (
|
|
||||||
new Date(profile.birthDate).toLocaleDateString()
|
|
||||||
) : (
|
|
||||||
<em>NA</em>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li className="d-flex align-items-center mb-2">
|
|
||||||
<i className="icon-base bx bx-calendar "></i>
|
|
||||||
<span className="fw-medium mx-2">Joining Date:</span>{" "}
|
|
||||||
<span>
|
|
||||||
{profile?.joiningDate ? (
|
|
||||||
new Date(profile.joiningDate).toLocaleDateString()
|
|
||||||
) : (
|
|
||||||
<em>NA</em>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<small className="card-text text-uppercase text-body-secondary small">
|
|
||||||
Contacts
|
|
||||||
</small>
|
|
||||||
<ul className="list-unstyled my-3 py-1">
|
|
||||||
<li className="d-flex align-items-center mb-4">
|
|
||||||
<i className="icon-base bx bx-phone"></i>
|
|
||||||
<span className="fw-medium mx-2">Contact:</span>{" "}
|
|
||||||
<span> {profile?.phoneNumber || <em>NA</em>}</span>
|
|
||||||
</li>
|
|
||||||
<li className="d-flex align-items-center mb-4">
|
|
||||||
<i className="icon-base bx bx-envelope"></i>
|
|
||||||
<span className="fw-medium mx-2">Email:</span>{" "}
|
|
||||||
<a href={`mailto:${profile?.email}`}>
|
|
||||||
{" "}
|
|
||||||
{profile?.email || <em>NA</em>}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li className="d-flex align-items-center mb-4">
|
|
||||||
<i className="icon-base bx bx-user"></i>
|
|
||||||
<span className="fw-medium mx-2">
|
|
||||||
{" "}
|
|
||||||
Emergency Contact:
|
|
||||||
</span>{" "}
|
|
||||||
<span> {profile?.emergencyContactPerson || <em>NA</em>}</span>
|
|
||||||
</li>
|
|
||||||
<li className="d-flex align-items-center mb-4">
|
|
||||||
<i className="icon-base bx bx-phone"></i>
|
|
||||||
<span className="fw-medium mx-2"> Emergency Phone:</span>{" "}
|
|
||||||
<span> {profile?.emergencyPhoneNumber || <em>NA</em>}</span>
|
|
||||||
</li>
|
|
||||||
<li className="d-flex align-items-center ">
|
|
||||||
<div className="container p-0">
|
|
||||||
<div className="fw-medium text-start">
|
|
||||||
<i className="icon-base bx bx-map "></i> Address:
|
|
||||||
</div>
|
|
||||||
<div className="text-start ms-7">
|
|
||||||
{profile?.currentAddress || <em>NA</em>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Status */}
|
||||||
|
<div className="d-flex align-items-start mb-3">
|
||||||
|
<span className="d-flex">
|
||||||
|
<i className="bx bx-check bx-xs me-2 mt-1"></i>
|
||||||
|
<span>Status</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: "96px" }}>:</span>
|
||||||
|
<span className="ms-5">Active</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Role */}
|
||||||
|
<div className="d-flex align-items-start mb-3">
|
||||||
|
<span className="d-flex">
|
||||||
|
<i className="bx bx-crown bx-xs me-2 mt-1"></i>
|
||||||
|
<span>Role</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: "110px" }}>:</span>
|
||||||
|
<span className="ms-5">{profile?.jobRole || <em>NA</em>}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Gender */}
|
||||||
|
<div className="d-flex align-items-start mb-3">
|
||||||
|
<span className="d-flex">
|
||||||
|
<i className="bx bx-flag bx-xs me-2 mt-1"></i>
|
||||||
|
<span>Gender</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: "91px" }}>:</span>
|
||||||
|
<span className="ms-5">{profile?.gender || <em>NA</em>}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Birth Date */}
|
||||||
|
<div className="d-flex align-items-start mb-3">
|
||||||
|
<span className="d-flex">
|
||||||
|
<i className="bx bx-calendar bx-xs me-2 mt-1"></i>
|
||||||
|
<span>Birth Date</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: "74px" }}>:</span>
|
||||||
|
<span className="ms-5">
|
||||||
|
{profile?.birthDate
|
||||||
|
? new Date(profile.birthDate).toLocaleDateString()
|
||||||
|
: <em>NA</em>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Joining Date */}
|
||||||
|
<div className="d-flex align-items-start mb-3">
|
||||||
|
<span className="d-flex">
|
||||||
|
<i className="bx bx-calendar bx-xs me-2 mt-1"></i>
|
||||||
|
<span>Joining Date</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: "60px" }}>:</span>
|
||||||
|
<span className="ms-5">
|
||||||
|
{profile?.joiningDate
|
||||||
|
? new Date(profile.joiningDate).toLocaleDateString()
|
||||||
|
: <em>NA</em>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contacts Heading */}
|
||||||
|
<small className="card-text text-uppercase text-body-secondary small d-block text-start mb-3 mt-4">
|
||||||
|
Contacts
|
||||||
|
</small>
|
||||||
|
|
||||||
|
{/* Contact Number */}
|
||||||
|
<div className="d-flex align-items-start mb-3">
|
||||||
|
<span className="d-flex">
|
||||||
|
<i className="bx bx-phone bx-xs me-2 mt-1"></i>
|
||||||
|
<span>Contact</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: "87px" }}>:</span>
|
||||||
|
<span className="ms-5">{profile?.phoneNumber || <em>NA</em>}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Email */}
|
||||||
|
<div className="d-flex align-items-start mb-3">
|
||||||
|
<span className="d-flex" style={{ minWidth: "160px", whiteSpace: "nowrap" }}>
|
||||||
|
<i className="bx bx-envelope bx-xs me-2 mt-1"></i>
|
||||||
|
<span>Email</span>
|
||||||
|
</span>
|
||||||
|
<span className="me-5">:</span>
|
||||||
|
<span>
|
||||||
|
{profile?.email ? (
|
||||||
|
<a href={`mailto:${profile.email}`}>{profile.email}</a>
|
||||||
|
) : (
|
||||||
|
<em>NA</em>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Emergency Contact */}
|
||||||
|
<div className="d-flex align-items-start mb-3">
|
||||||
|
<span className="d-flex">
|
||||||
|
<i className="bx bx-user bx-xs me-2 mt-1"></i>
|
||||||
|
<span>Emergency Contact</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: "14px" }}>:</span>
|
||||||
|
<span className="ms-5">
|
||||||
|
{profile?.emergencyContactPerson || <em>NA</em>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Emergency Phone */}
|
||||||
|
<div className="d-flex align-items-start mb-3">
|
||||||
|
<span className="d-flex">
|
||||||
|
<i className="bx bx-phone bx-xs me-2 mt-1"></i>
|
||||||
|
<span>Emergency Phone</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: "25px" }}>:</span>
|
||||||
|
<span className="ms-5">
|
||||||
|
{profile?.emergencyPhoneNumber || <em>NA</em>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Address */}
|
||||||
|
<div className="d-flex align-items-start">
|
||||||
|
<span className="d-flex">
|
||||||
|
<i className="bx bx-map bx-xs me-2 mt-1"></i>
|
||||||
|
<span>Address</span>
|
||||||
|
</span>
|
||||||
|
<span style={{ marginLeft: "86px" }}>:</span>
|
||||||
|
<span className="ms-5">
|
||||||
|
{profile?.currentAddress || <em>NA</em>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EmpOverview;
|
export default EmpOverview;
|
||||||
|
@ -16,13 +16,13 @@ import {
|
|||||||
getCachedData,
|
getCachedData,
|
||||||
} from "../../slices/apiDataManager";
|
} from "../../slices/apiDataManager";
|
||||||
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
|
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
|
||||||
const mobileNumberRegex = /^[0-9]\d{9}$/;
|
const mobileNumberRegex = /^[0-9]\d{9}$/;
|
||||||
|
|
||||||
const ManageEmployee = ({ employeeId, onClosed,IsAllEmployee }) => {
|
const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
employee,
|
employee,
|
||||||
@ -130,12 +130,11 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
.min(1, { message: "Phone Number is required" })
|
.min(1, { message: "Phone Number is required" })
|
||||||
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
|
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
|
||||||
jobRoleId: z.string().min(1, { message: "Role is required" }),
|
jobRoleId: z.string().min(1, { message: "Role is required" }),
|
||||||
} );
|
});
|
||||||
|
|
||||||
useEffect( () =>
|
useEffect(() => {
|
||||||
{
|
|
||||||
refetch()
|
refetch()
|
||||||
},[])
|
}, [])
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -169,19 +168,19 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
});
|
});
|
||||||
|
|
||||||
const AadharNumberValue = watch("aadharNumber") || "";
|
const AadharNumberValue = watch("aadharNumber") || "";
|
||||||
|
|
||||||
const onSubmit = (data) => {
|
|
||||||
if (data.email === "") {
|
|
||||||
data.email = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateEmployee({...data,IsAllEmployee},{
|
const onSubmit = (data) => {
|
||||||
onSuccess: () => {
|
if (data.email === "") {
|
||||||
reset();
|
data.email = null;
|
||||||
onClosed();
|
}
|
||||||
},
|
|
||||||
});
|
updateEmployee({ ...data, IsAllEmployee }, {
|
||||||
};
|
onSuccess: () => {
|
||||||
|
reset();
|
||||||
|
onClosed();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -212,7 +211,7 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
phoneNumber: currentEmployee.phoneNumber || "",
|
phoneNumber: currentEmployee.phoneNumber || "",
|
||||||
jobRoleId: currentEmployee.jobRoleId?.toString() || "",
|
jobRoleId: currentEmployee.jobRoleId?.toString() || "",
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
);
|
);
|
||||||
setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0);
|
setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0);
|
||||||
setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0);
|
setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0);
|
||||||
@ -220,66 +219,82 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form onSubmit={handleSubmit( onSubmit )} className="p-sm-0 p-2">
|
<form onSubmit={handleSubmit(onSubmit)} className="p-sm-0 p-2">
|
||||||
<div className="text-center"><p className="fs-6 fw-semibold"> {employee ? "Update Employee" : "Create Employee"}</p> </div>
|
<div className="text-center"><p className="fs-6 fw-semibold"> {employee ? "Update Employee" : "Create Employee"}</p> </div>
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
{" "}
|
<div className="form-text text-start">First Name</div>
|
||||||
<div className="form-text text-start">First Name</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="FirstName"
|
|
||||||
{...register("firstName")}
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
id="firstName"
|
|
||||||
placeholder="First Name"
|
|
||||||
/>
|
|
||||||
{errors.firstName && (
|
|
||||||
<div
|
|
||||||
className="danger-text text-start"
|
|
||||||
style={{ fontSize: "12px" }}
|
|
||||||
>
|
|
||||||
{errors.firstName.message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>{" "}
|
|
||||||
<div className="col-sm-4">
|
|
||||||
<div className="form-text text-start">Middle Name</div>
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
{...register("middleName")}
|
name="firstName"
|
||||||
|
{...register("firstName", {
|
||||||
|
required: "First name is required",
|
||||||
|
pattern: {
|
||||||
|
value: /^[A-Za-z\s]+$/, // Only letters and spaces
|
||||||
|
message: "Only letters are allowed",
|
||||||
|
},
|
||||||
|
})}
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
id="middleName"
|
id="firstName"
|
||||||
placeholder="Middle Name"
|
placeholder="First Name"
|
||||||
|
onInput={(e) => {
|
||||||
|
e.target.value = e.target.value.replace(/[^A-Za-z\s]/g, "");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{errors.middleName && (
|
{errors.firstName && (
|
||||||
<div
|
<div className="danger-text text-start" style={{ fontSize: "12px" }}>
|
||||||
className="danger-text text-start "
|
{errors.firstName.message}
|
||||||
style={{ fontSize: "12px" }}
|
|
||||||
>
|
|
||||||
{errors.middleName.message}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-sm-4">
|
||||||
|
<div className="form-text text-start">Middle Name</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register("middleName", {
|
||||||
|
pattern: {
|
||||||
|
value: /^[A-Za-z\s]+$/, // Only letters and spaces
|
||||||
|
message: "Only letters are allowed",
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
id="middleName"
|
||||||
|
placeholder="Middle Name"
|
||||||
|
onInput={(e) => {
|
||||||
|
e.target.value = e.target.value.replace(/[^A-Za-z\s]/g, "");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.middleName && (
|
||||||
|
<div className="danger-text text-start " style={{ fontSize: "12px" }}>
|
||||||
|
{errors.middleName.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Last Name</div>
|
<div className="form-text text-start">Last Name</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
{...register("lastName")}
|
{...register("lastName", {
|
||||||
|
pattern: {
|
||||||
|
value: /^[A-Za-z\s]+$/, // Only letters and spaces
|
||||||
|
message: "Only letters are allowed",
|
||||||
|
},
|
||||||
|
})}
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
id="lastName"
|
id="lastName"
|
||||||
placeholder="Last Name"
|
placeholder="Last Name"
|
||||||
|
onInput={(e) => {
|
||||||
|
e.target.value = e.target.value.replace(/[^A-Za-z\s]/g, "");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{errors.lastName && (
|
{errors.lastName && (
|
||||||
<div
|
<div className="danger-text text-start" style={{ fontSize: "12px" }}>
|
||||||
className="danger-text text-start"
|
|
||||||
style={{ fontSize: "12px" }}
|
|
||||||
>
|
|
||||||
{errors.lastName.message}
|
{errors.lastName.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
@ -330,7 +345,7 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Gender</div>
|
<div className="form-text text-start">Gender</div>
|
||||||
|
|
||||||
<div className="input-group input-group-merge ">
|
<div className="input-group">
|
||||||
<select
|
<select
|
||||||
className="form-select form-select-sm "
|
className="form-select form-select-sm "
|
||||||
{...register("gender")}
|
{...register("gender")}
|
||||||
@ -357,7 +372,7 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Birth Date</div>
|
<div className="form-text text-start">Birth Date</div>
|
||||||
|
|
||||||
<div className="input-group input-group-merge ">
|
<div className="input-group">
|
||||||
<input
|
<input
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
type="date"
|
type="date"
|
||||||
@ -377,7 +392,7 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Joining Date</div>
|
<div className="form-text text-start">Joining Date</div>
|
||||||
|
|
||||||
<div className="input-group input-group-merge ">
|
<div className="input-group">
|
||||||
<input
|
<input
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
type="date"
|
type="date"
|
||||||
@ -470,7 +485,7 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Official Designation</div>
|
<div className="form-text text-start">Official Designation</div>
|
||||||
<div className="input-group input-group-merge ">
|
<div className="input-group">
|
||||||
<select
|
<select
|
||||||
className="form-select form-select-sm"
|
className="form-select form-select-sm"
|
||||||
{...register("jobRoleId")}
|
{...register("jobRoleId")}
|
||||||
|
@ -13,24 +13,23 @@ import { useSelector } from "react-redux";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { useExpenseFilter } from "../../hooks/useExpense";
|
import { useExpenseFilter } from "../../hooks/useExpense";
|
||||||
import { ExpenseFilterSkeleton } from "./ExpenseSkeleton";
|
import { ExpenseFilterSkeleton } from "./ExpenseSkeleton";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||||
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
|
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
|
||||||
const { data, isLoading,isError,error,isFetching , isFetched} = useExpenseFilter();
|
const { data, isLoading,isError,error,isFetching , isFetched} = useExpenseFilter();
|
||||||
|
|
||||||
const groupByList = useMemo(() => {
|
const groupByList = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{ id: "transactionDate", name: "Transaction Date" },
|
{ id: "transactionDate", name: "Transaction Date" },
|
||||||
{ id: "status", name: "Status" },
|
{ id: "status", name: "Status" },
|
||||||
{ id: "submittedBy", name: "Submitted By" },
|
{ id: "submittedBy", name: "Submitted By" },
|
||||||
{ id: "project", name: "Project" },
|
{ id: "project", name: "Project" },
|
||||||
{ id: "paymentMode", name: "Payment Mode" },
|
{ id: "paymentMode", name: "Payment Mode" },
|
||||||
{ id: "expensesType", name: "Expense Type" },
|
{ id: "expensesType", name: "Expense Type" },
|
||||||
{ id: "createdAt", name: "Submitted Date" }
|
{ id: "createdAt", name: "Submitted Date" }
|
||||||
].sort((a, b) => a.name.localeCompare(b.name));
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const [selectedGroup, setSelectedGroup] = useState(groupByList[0]);
|
const [selectedGroup, setSelectedGroup] = useState(groupByList[0]);
|
||||||
@ -72,6 +71,12 @@ const groupByList = useMemo(() => {
|
|||||||
closePanel();
|
closePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ✅ Close popup when navigating to another component
|
||||||
|
const location = useLocation();
|
||||||
|
useEffect(() => {
|
||||||
|
closePanel();
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
|
if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
|
||||||
if(isError && isFetched) return <div>Something went wrong Here- {error.message} </div>
|
if(isError && isFetched) return <div>Something went wrong Here- {error.message} </div>
|
||||||
return (
|
return (
|
||||||
@ -162,21 +167,21 @@ const groupByList = useMemo(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-2 text-start ">
|
<div className="mb-2 text-start ">
|
||||||
<label htmlFor="groupBySelect" className="form-label">Group By :</label>
|
<label htmlFor="groupBySelect" className="form-label">Group By :</label>
|
||||||
<select
|
<select
|
||||||
id="groupBySelect"
|
id="groupBySelect"
|
||||||
className="form-select form-select-sm"
|
className="form-select form-select-sm"
|
||||||
value={selectedGroup?.id || ""}
|
value={selectedGroup?.id || ""}
|
||||||
onChange={handleGroupChange}
|
onChange={handleGroupChange}
|
||||||
>
|
>
|
||||||
{groupByList.map((group) => (
|
{groupByList.map((group) => (
|
||||||
<option key={group.id} value={group.id}>
|
<option key={group.id} value={group.id}>
|
||||||
{group.name}
|
{group.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="d-flex justify-content-end py-3 gap-2">
|
<div className="d-flex justify-content-end py-3 gap-2">
|
||||||
<button
|
<button
|
||||||
|
@ -395,7 +395,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
<div className="row my-2">
|
<div className="row my-2">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label htmlFor="statusId" className="form-label ">
|
<label htmlFor="statusId" className="form-label ">
|
||||||
TransactionId
|
Transaction ID
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -43,8 +43,9 @@ const Header = () => {
|
|||||||
/^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
|
/^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
|
||||||
pathname
|
pathname
|
||||||
);
|
);
|
||||||
|
const isExpensePage = /^\/expenses$/.test(pathname);
|
||||||
|
|
||||||
return !(isDirectoryPath || isProfilePage);
|
return !(isDirectoryPath || isProfilePage || isExpensePage);
|
||||||
};
|
};
|
||||||
const allowedProjectStatusIds = [
|
const allowedProjectStatusIds = [
|
||||||
"603e994b-a27f-4e5d-a251-f3d69b0498ba",
|
"603e994b-a27f-4e5d-a251-f3d69b0498ba",
|
||||||
@ -475,4 +476,4 @@ const Header = () => {
|
|||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Header;
|
export default Header;
|
@ -49,6 +49,9 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
const infoRef = useRef(null);
|
const infoRef = useRef(null);
|
||||||
const infoRef1 = useRef(null);
|
const infoRef1 = useRef(null);
|
||||||
|
|
||||||
|
// State for search term
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof bootstrap !== "undefined") {
|
if (typeof bootstrap !== "undefined") {
|
||||||
if (infoRef.current) {
|
if (infoRef.current) {
|
||||||
@ -81,9 +84,11 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
recallEmployeeData,
|
recallEmployeeData,
|
||||||
} = useEmployeesAllOrByProjectId(false, selectedProject, false);
|
} = useEmployeesAllOrByProjectId(false, selectedProject, false);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { data: jobRoleData, loading } = useMaster();
|
const { loading } = useMaster();
|
||||||
|
const { data: jobRoleData } = useMaster();
|
||||||
|
|
||||||
const [selectedRole, setSelectedRole] = useState("all");
|
// Changed to an array to hold multiple selected roles
|
||||||
|
const [selectedRoles, setSelectedRoles] = useState(["all"]);
|
||||||
const [displayedSelection, setDisplayedSelection] = useState("");
|
const [displayedSelection, setDisplayedSelection] = useState("");
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -121,20 +126,79 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(changeMaster("Job Role"));
|
dispatch(changeMaster("Job Role"));
|
||||||
|
// Initial state should reflect "All Roles" selected
|
||||||
return () => setSelectedRole("all");
|
setSelectedRoles(["all"]);
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleRoleChange = (event) => {
|
// Modified handleRoleChange to handle multiple selections
|
||||||
setSelectedRole(event.target.value);
|
const handleRoleChange = (event, roleId) => {
|
||||||
|
// If 'all' is selected, clear other selections
|
||||||
|
if (roleId === "all") {
|
||||||
|
setSelectedRoles(["all"]);
|
||||||
|
} else {
|
||||||
|
setSelectedRoles((prevSelectedRoles) => {
|
||||||
|
// If "all" was previously selected, remove it
|
||||||
|
const newRoles = prevSelectedRoles.filter((role) => role !== "all");
|
||||||
|
if (newRoles.includes(roleId)) {
|
||||||
|
// If role is already selected, unselect it
|
||||||
|
return newRoles.filter((id) => id !== roleId);
|
||||||
|
} else {
|
||||||
|
// If role is not selected, add it
|
||||||
|
return [...newRoles, roleId];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredEmployees =
|
useEffect(() => {
|
||||||
selectedRole === "all"
|
// Update displayedSelection based on selectedRoles
|
||||||
? employees
|
if (selectedRoles.includes("all")) {
|
||||||
: employees?.filter(
|
setDisplayedSelection("All Roles");
|
||||||
(emp) => String(emp.jobRoleId || "") === selectedRole
|
} else if (selectedRoles.length > 0) {
|
||||||
);
|
const selectedRoleNames = selectedRoles.map(roleId => {
|
||||||
|
const role = jobRoleData?.find(r => String(r.id) === roleId);
|
||||||
|
return role ? role.name : '';
|
||||||
|
}).filter(Boolean); // Filter out empty strings for roles not found
|
||||||
|
setDisplayedSelection(selectedRoleNames.join(', '));
|
||||||
|
} else {
|
||||||
|
setDisplayedSelection("Select Roles");
|
||||||
|
}
|
||||||
|
}, [selectedRoles, jobRoleData]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleSearchChange = (event) => {
|
||||||
|
setSearchTerm(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter employees first by role, then by search term AND job role name
|
||||||
|
const filteredEmployees = employees?.filter((emp) => {
|
||||||
|
const matchesRole =
|
||||||
|
selectedRoles.includes("all") || selectedRoles.includes(String(emp.jobRoleId));
|
||||||
|
// Convert both first and last names and job role name to lowercase for case-insensitive matching
|
||||||
|
const fullName = `${emp.firstName} ${emp.lastName}`.toLowerCase();
|
||||||
|
|
||||||
|
const jobRoleName = jobRoleData?.find((role) => role.id === emp.jobRoleId)?.name?.toLowerCase() || "";
|
||||||
|
|
||||||
|
const searchLower = searchTerm.toLowerCase();
|
||||||
|
// Check if the full name OR job role name includes the search term
|
||||||
|
const matchesSearch = fullName.includes(searchLower) || jobRoleName.includes(searchLower);
|
||||||
|
return matchesRole && matchesSearch;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine unique job role IDs from the filtered employees (for dropdown options)
|
||||||
|
const uniqueJobRoleIdsInFilteredEmployees = new Set(
|
||||||
|
employees?.map(emp => emp.jobRoleId).filter(Boolean)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Filter jobRoleData to only include roles present in the uniqueJobRoleIdsInFilteredEmployees
|
||||||
|
const jobRolesForDropdown = jobRoleData?.filter(role =>
|
||||||
|
uniqueJobRoleIdsInFilteredEmployees.has(role.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate the count of selected roles for display
|
||||||
|
const selectedRolesCount = selectedRoles.includes("all")
|
||||||
|
? 0 // "All Roles" doesn't contribute to a specific count
|
||||||
|
: selectedRoles.length;
|
||||||
|
|
||||||
const onSubmit = (data) => {
|
const onSubmit = (data) => {
|
||||||
const selectedEmployeeIds = data.selectedEmployees;
|
const selectedEmployeeIds = data.selectedEmployees;
|
||||||
@ -192,118 +256,183 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
<div className="form-text text-start">
|
<div className="form-text text-start">
|
||||||
<div className="d-flex align-items-center form-text fs-7">
|
<div className="d-flex align-items-center form-text fs-7">
|
||||||
<span className="text-dark">Select Team</span>
|
<span className="text-dark">Select Team</span>
|
||||||
<div className="me-2">{displayedSelection}</div>
|
|
||||||
<a
|
|
||||||
className="dropdown-toggle hide-arrow cursor-pointer"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<i className="bx bx-filter bx-lg text-primary"></i>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<ul className="dropdown-menu p-2 text-capitalize">
|
{/* Dropdown */}
|
||||||
<li key="all">
|
<div className="dropdown position-relative d-inline-block">
|
||||||
<button
|
<a
|
||||||
type="button"
|
className={`dropdown-toggle hide-arrow cursor-pointer ${selectedRoles.includes("all") || selectedRoles.length === 0
|
||||||
className="dropdown-item py-1"
|
? "text-secondary"
|
||||||
onClick={() =>
|
: "text-primary"
|
||||||
handleRoleChange({
|
}`}
|
||||||
target: { value: "all" },
|
data-bs-toggle="dropdown"
|
||||||
})
|
role="button"
|
||||||
}
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<i className="bx bx-slider-alt ms-2"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* Badge */}
|
||||||
|
{selectedRolesCount > 0 && (
|
||||||
|
<span
|
||||||
|
className="position-absolute top-0 start-100 translate-middle badge rounded-circle bg-warning text-white"
|
||||||
|
style={{
|
||||||
|
fontSize: "0.65rem",
|
||||||
|
minWidth: "18px",
|
||||||
|
height: "18px",
|
||||||
|
padding: "0",
|
||||||
|
lineHeight: "18px",
|
||||||
|
textAlign: "center",
|
||||||
|
zIndex: 10,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
All Roles
|
{selectedRolesCount}
|
||||||
</button>
|
</span>
|
||||||
</li>
|
)}
|
||||||
{jobRoleData?.map((user) => (
|
|
||||||
<li key={user.id}>
|
{/* Dropdown Menu with Scroll */}
|
||||||
<button
|
<ul
|
||||||
type="button"
|
className="dropdown-menu p-2 text-capitalize"
|
||||||
className="dropdown-item py-1"
|
style={{ maxHeight: "300px", overflowY: "auto" }}
|
||||||
value={user.id}
|
>
|
||||||
onClick={handleRoleChange}
|
{/* All Roles */}
|
||||||
>
|
<li key="all">
|
||||||
{user.name}
|
<div className="form-check dropdown-item py-0">
|
||||||
</button>
|
<input
|
||||||
|
className="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="checkboxAllRoles"
|
||||||
|
value="all"
|
||||||
|
checked={selectedRoles.includes("all")}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleRoleChange(e, e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="form-check-label ms-2"
|
||||||
|
htmlFor="checkboxAllRoles"
|
||||||
|
>
|
||||||
|
All Roles
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
|
||||||
</ul>
|
{/* Dynamic Roles */}
|
||||||
|
{jobRolesForDropdown?.map((role) => (
|
||||||
|
<li key={role.id}>
|
||||||
|
<div className="form-check dropdown-item py-0">
|
||||||
|
<input
|
||||||
|
className="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id={`checkboxRole-${role.id}`}
|
||||||
|
value={role.id}
|
||||||
|
checked={selectedRoles.includes(String(role.id))}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleRoleChange(e, e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="form-check-label ms-2"
|
||||||
|
htmlFor={`checkboxRole-${role.id}`}
|
||||||
|
>
|
||||||
|
{role.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search Box */}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control-sm ms-auto mb-2 mt-2"
|
||||||
|
placeholder="Search employees or roles..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
style={{ maxWidth: "200px" }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="row">
|
{/* Employees list */}
|
||||||
<div className="col-12 h-sm-25 overflow-auto mt-2" style={{height:"300px"}}>
|
<div
|
||||||
{selectedRole !== "" && (
|
className="col-12 mt-2"
|
||||||
<div className="row">
|
style={{
|
||||||
{employeeLoading ? (
|
maxHeight: "280px",
|
||||||
<div className="col-12">
|
overflowY: "auto",
|
||||||
<p className="text-center">Loading employees...</p>
|
overflowX: "hidden",
|
||||||
</div>
|
}}
|
||||||
) : filteredEmployees?.length > 0 ? (
|
>
|
||||||
filteredEmployees.map((emp) => {
|
{selectedRoles?.length > 0 && (
|
||||||
const jobRole = jobRoleData?.find(
|
<div className="row">
|
||||||
(role) => role?.id === emp?.jobRoleId
|
{employeeLoading ? (
|
||||||
);
|
<div className="col-12">
|
||||||
|
<p className="text-center">Loading employees...</p>
|
||||||
|
</div>
|
||||||
|
) : filteredEmployees?.length > 0 ? (
|
||||||
|
filteredEmployees.map((emp) => {
|
||||||
|
const jobRole = jobRoleData?.find(
|
||||||
|
(role) => role?.id === emp?.jobRoleId
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={emp.id}
|
key={emp.id}
|
||||||
className="col-6 col-md-4 col-lg-3 mb-3"
|
className="col-6 col-md-4 col-lg-3 mb-3"
|
||||||
>
|
>
|
||||||
<div className="form-check d-flex align-items-start">
|
<div className="form-check d-flex align-items-start">
|
||||||
<Controller
|
<Controller
|
||||||
name="selectedEmployees"
|
name="selectedEmployees"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<input
|
<input
|
||||||
{...field}
|
{...field}
|
||||||
className="form-check-input me-1 mt-1"
|
className="form-check-input me-1 mt-1"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={`employee-${emp?.id}`}
|
id={`employee-${emp?.id}`}
|
||||||
value={emp.id}
|
value={emp.id}
|
||||||
checked={field.value?.includes(emp.id)}
|
checked={field.value?.includes(emp.id)}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
handleCheckboxChange(e, emp);
|
handleCheckboxChange(e, emp);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex-grow-1">
|
||||||
|
<p
|
||||||
|
className="mb-0"
|
||||||
|
style={{ fontSize: "13px" }}
|
||||||
|
>
|
||||||
|
{emp.firstName} {emp.lastName}
|
||||||
|
</p>
|
||||||
|
<small
|
||||||
|
className="text-muted"
|
||||||
|
style={{ fontSize: "11px" }}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<span className="placeholder-glow">
|
||||||
|
<span className="placeholder col-6"></span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
jobRole?.name || "Unknown Role"
|
||||||
)}
|
)}
|
||||||
/>
|
</small>
|
||||||
<div className="flex-grow-1">
|
|
||||||
<p
|
|
||||||
className="mb-0"
|
|
||||||
style={{ fontSize: "13px" }}
|
|
||||||
>
|
|
||||||
{emp.firstName} {emp.lastName}
|
|
||||||
</p>
|
|
||||||
<small
|
|
||||||
className="text-muted"
|
|
||||||
style={{ fontSize: "11px" }}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<span className="placeholder-glow">
|
|
||||||
<span className="placeholder col-6"></span>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
jobRole?.name || "Unknown Role"
|
|
||||||
)}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
})
|
);
|
||||||
) : (
|
})
|
||||||
<div className="col-12">
|
) : (
|
||||||
<p className="text-center">
|
<div className="col-12">
|
||||||
|
<p className="text-center">
|
||||||
No employees found for the selected role.
|
No employees found for the selected role.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -356,40 +485,15 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
|
|
||||||
<div className="col-md text-start mx-0 px-0">
|
<div className="col-md text-start mx-0 px-0">
|
||||||
<div className="form-check form-check-inline mt-3 px-1">
|
<div className="form-check form-check-inline mt-3 px-1">
|
||||||
<label
|
<label className="form-text text-dark align-items-center d-flex">
|
||||||
className="form-text text-dark align-items-center d-flex"
|
|
||||||
htmlFor="inlineCheckbox1"
|
|
||||||
>
|
|
||||||
Pending Task of Activity :
|
Pending Task of Activity :
|
||||||
<label
|
<label className="form-check-label fs-7 ms-4">
|
||||||
className="form-check-label fs-7 ms-4"
|
|
||||||
htmlFor="inlineCheckbox1"
|
|
||||||
>
|
|
||||||
<strong>
|
<strong>
|
||||||
{assignData?.workItem?.plannedWork -
|
{assignData?.workItem?.plannedWork -
|
||||||
assignData?.workItem?.completedWork}
|
assignData?.workItem?.completedWork}
|
||||||
</strong>{" "}
|
</strong>{" "}
|
||||||
<u>
|
<u>{assignData?.workItem?.activityMaster?.unitOfMeasurement}</u>
|
||||||
{
|
|
||||||
assignData?.workItem?.activityMaster
|
|
||||||
?.unitOfMeasurement
|
|
||||||
}
|
|
||||||
</u>
|
|
||||||
</label>
|
</label>
|
||||||
<div style={{ display: "flex", alignItems: "center" }}>
|
|
||||||
<div
|
|
||||||
ref={infoRef}
|
|
||||||
tabIndex="0"
|
|
||||||
className="d-flex align-items-center avatar-group justify-content-center ms-2"
|
|
||||||
data-bs-toggle="popover"
|
|
||||||
data-bs-trigger="focus"
|
|
||||||
data-bs-placement="right"
|
|
||||||
data-bs-html="true"
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
>
|
|
||||||
<i class='bx bx-info-circle bx-xs m-0 text-secondary'></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -397,10 +501,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
{/* Target for Today input and validation */}
|
{/* Target for Today input and validation */}
|
||||||
<div className="col-md text-start mx-0 px-0">
|
<div className="col-md text-start mx-0 px-0">
|
||||||
<div className="form-check form-check-inline mt-2 px-1 mb-2 text-start">
|
<div className="form-check form-check-inline mt-2 px-1 mb-2 text-start">
|
||||||
<label
|
<label className="text-dark d-flex align-items-center flex-wrap form-text">
|
||||||
className="text-dark text-start d-flex align-items-center flex-wrap form-text"
|
|
||||||
htmlFor="inlineCheckbox1"
|
|
||||||
>
|
|
||||||
<span>Target for Today</span>
|
<span>Target for Today</span>
|
||||||
<span style={{ marginLeft: "46px" }}>:</span>
|
<span style={{ marginLeft: "46px" }}>:</span>
|
||||||
</label>
|
</label>
|
||||||
@ -418,51 +519,17 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
{...field}
|
{...field}
|
||||||
id="defaultFormControlInput"
|
|
||||||
aria-describedby="defaultFormControlHelp"
|
|
||||||
/>
|
/>
|
||||||
<span style={{ paddingLeft: "6px" }}>
|
<span style={{ paddingLeft: "6px", whiteSpace: "nowrap" }}>
|
||||||
{
|
<u>{assignData?.workItem?.activityMaster?.unitOfMeasurement}</u>
|
||||||
assignData?.workItem?.workItem?.activityMaster
|
|
||||||
?.unitOfMeasurement
|
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
<div
|
|
||||||
className="flex align-items-center"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
ref={infoRef1}
|
|
||||||
tabIndex="0"
|
|
||||||
className="d-flex align-items-center avatar-group justify-content-center ms-2 cursor-pointer"
|
|
||||||
data-bs-toggle="popover"
|
|
||||||
data-bs-trigger="focus"
|
|
||||||
data-bs-placement="right"
|
|
||||||
data-bs-html="true"
|
|
||||||
>
|
|
||||||
<i class='bx bx-info-circle bx-xs m-0 text-secondary'></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{errors.plannedTask && (
|
{errors.plannedTask && (
|
||||||
<div className="danger-text mt-1">
|
<div className="danger-text mt-1">{errors.plannedTask.message}</div>
|
||||||
{errors.plannedTask.message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isHelpVisible && (
|
|
||||||
<div
|
|
||||||
className="position-absolute bg-white border p-2 rounded shadow"
|
|
||||||
style={{ zIndex: 10, marginLeft: "10px" }}
|
|
||||||
>
|
|
||||||
{/* Add your help content here */}
|
|
||||||
<p className="mb-0">
|
|
||||||
Enter the target value for today's task.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -476,12 +543,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
name="description"
|
name="description"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<textarea
|
<textarea {...field} className="form-control" rows="2" />
|
||||||
{...field}
|
|
||||||
className="form-control"
|
|
||||||
id="descriptionTextarea" // Changed id for better accessibility
|
|
||||||
rows="2"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.description && (
|
{errors.description && (
|
||||||
|
@ -14,11 +14,13 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
|||||||
import { ASSIGN_TO_PROJECT } from "../../utils/constants";
|
import { ASSIGN_TO_PROJECT } from "../../utils/constants";
|
||||||
import ConfirmModal from "../common/ConfirmModal";
|
import ConfirmModal from "../common/ConfirmModal";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import {useEmployeesByProjectAllocated, useManageProjectAllocation} from "../../hooks/useProjects";
|
import {
|
||||||
|
useEmployeesByProjectAllocated,
|
||||||
|
useManageProjectAllocation,
|
||||||
|
} from "../../hooks/useProjects";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedproject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const Teams = () =>
|
const Teams = () => {
|
||||||
{
|
|
||||||
// const {projectId} = useParams()
|
// const {projectId} = useParams()
|
||||||
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
||||||
const projectId = useSelectedproject();
|
const projectId = useSelectedproject();
|
||||||
@ -32,68 +34,72 @@ const Teams = () =>
|
|||||||
const [filteredEmployees, setFilteredEmployees] = useState([]);
|
const [filteredEmployees, setFilteredEmployees] = useState([]);
|
||||||
const [removingEmployeeId, setRemovingEmployeeId] = useState(null);
|
const [removingEmployeeId, setRemovingEmployeeId] = useState(null);
|
||||||
const [assignedLoading, setAssignedLoading] = useState(false);
|
const [assignedLoading, setAssignedLoading] = useState(false);
|
||||||
const [ activeEmployee, setActiveEmployee ] = useState( true )
|
const [activeEmployee, setActiveEmployee] = useState(true);
|
||||||
const [deleteEmployee,setDeleteEmplyee] = useState(null)
|
const [deleteEmployee, setDeleteEmplyee] = useState(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState(""); // State for search term
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const HasAssignUserPermission = useHasUserPermission( ASSIGN_TO_PROJECT );
|
const HasAssignUserPermission = useHasUserPermission(ASSIGN_TO_PROJECT);
|
||||||
const [ IsDeleteModal, setIsDeleteModal ] = useState( false )
|
const [IsDeleteModal, setIsDeleteModal] = useState(false);
|
||||||
const {projectEmployees, loading:employeeLodaing, refetch} = useEmployeesByProjectAllocated( projectId )
|
const {
|
||||||
const {
|
projectEmployees,
|
||||||
mutate: submitAllocations,
|
loading: employeeLodaing,
|
||||||
isPending,
|
refetch,
|
||||||
isSuccess,
|
} = useEmployeesByProjectAllocated(projectId);
|
||||||
isError,
|
const {
|
||||||
} = useManageProjectAllocation({
|
mutate: submitAllocations,
|
||||||
onSuccessCallback: () => {
|
isPending,
|
||||||
setRemovingEmployeeId(null);
|
isSuccess,
|
||||||
setAssignedLoading(false);
|
isError,
|
||||||
setDeleteEmplyee(null);
|
} = useManageProjectAllocation({
|
||||||
closeDeleteModal();
|
onSuccessCallback: () => {
|
||||||
},
|
setRemovingEmployeeId(null);
|
||||||
onErrorCallback: () => {
|
setAssignedLoading(false);
|
||||||
closeDeleteModal();
|
setDeleteEmplyee(null);
|
||||||
},
|
closeDeleteModal();
|
||||||
});
|
},
|
||||||
|
onErrorCallback: () => {
|
||||||
|
closeDeleteModal();
|
||||||
|
},
|
||||||
const removeAllocation = (item) => {
|
|
||||||
setRemovingEmployeeId(item.id);
|
|
||||||
|
|
||||||
submitAllocations({
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
empID: item.employeeId,
|
|
||||||
jobRoleId: item.jobRoleId,
|
|
||||||
projectId: projectId,
|
|
||||||
status: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
added: false,
|
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
const removeAllocation = (item) => {
|
||||||
|
setRemovingEmployeeId(item.id);
|
||||||
|
|
||||||
|
submitAllocations({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
empID: item.employeeId,
|
||||||
|
jobRoleId: item.jobRoleId,
|
||||||
|
projectId: projectId,
|
||||||
|
status: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
added: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleEmpAlicationFormSubmit = (allocaionObj) => {
|
const handleEmpAlicationFormSubmit = (allocaionObj) => {
|
||||||
let items = allocaionObj.map((item) => {
|
let items = allocaionObj.map((item) => {
|
||||||
return {
|
return {
|
||||||
empID: item.empID,
|
empID: item.empID,
|
||||||
jobRoleId: item.jobRoleId,
|
jobRoleId: item.jobRoleId,
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
status: true,
|
status: true,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
submitAllocations({ items, added: true });
|
submitAllocations({ items, added: true });
|
||||||
|
|
||||||
setActiveEmployee(true);
|
setActiveEmployee(true);
|
||||||
setFilteredEmployees(employees.filter((emp) => emp.isActive));
|
setFilteredEmployees(employees.filter((emp) => emp.isActive));
|
||||||
|
|
||||||
const dropdown = document.querySelector('select[name="DataTables_Table_0_length"]');
|
|
||||||
if (dropdown) dropdown.value = "true";
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const dropdown = document.querySelector(
|
||||||
|
'select[name="DataTables_Table_0_length"]'
|
||||||
|
);
|
||||||
|
if (dropdown) dropdown.value = "true";
|
||||||
|
};
|
||||||
|
|
||||||
const getRole = (jobRoleId) => {
|
const getRole = (jobRoleId) => {
|
||||||
if (loading) return "Loading...";
|
if (loading) return "Loading...";
|
||||||
@ -124,17 +130,17 @@ const {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(changeMaster("Job Role"));
|
dispatch(changeMaster("Job Role"));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ( projectEmployees )
|
if (projectEmployees) {
|
||||||
{
|
|
||||||
setEmployees(projectEmployees);
|
setEmployees(projectEmployees);
|
||||||
setFilteredEmployees( projectEmployees?.filter( ( emp ) => emp.isActive ) );
|
//setFilteredEmployees(projectEmployees?.filter((emp) => emp.isActive));
|
||||||
|
const filtered = projectEmployees.filter((emp) => emp.isActive);
|
||||||
|
setFilteredEmployees(filtered);
|
||||||
}
|
}
|
||||||
}, [projectEmployees,employeeLodaing]);
|
}, [projectEmployees, employeeLodaing]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -142,24 +148,57 @@ const {
|
|||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
const filterAndSearchEmployees = useCallback(() => {
|
||||||
|
const statusFiltered = employees.filter((emp) =>
|
||||||
|
activeEmployee ? emp.isActive : !emp.isActive
|
||||||
|
);
|
||||||
|
|
||||||
|
if (searchTerm === "") {
|
||||||
|
setFilteredEmployees(statusFiltered);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||||
|
const searchedAndFiltered = statusFiltered.filter((item) => {
|
||||||
|
const fullName =
|
||||||
|
`${item.firstName} ${item.middleName} ${item.lastName}`.toLowerCase();
|
||||||
|
const roleName = getRole(item.jobRoleId).toLowerCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
fullName.includes(lowercasedSearchTerm) ||
|
||||||
|
roleName.includes(lowercasedSearchTerm)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
setFilteredEmployees(searchedAndFiltered);
|
||||||
|
}, [employees, activeEmployee, searchTerm, getRole]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
filterAndSearchEmployees();
|
||||||
|
}, [employees, activeEmployee, searchTerm, filterAndSearchEmployees]);
|
||||||
|
|
||||||
const handleFilterEmployee = (e) => {
|
const handleFilterEmployee = (e) => {
|
||||||
const filterValue = e.target.value;
|
const filterValue = e.target.value;
|
||||||
if ( filterValue === "true" )
|
// if (filterValue === "true") {
|
||||||
{
|
// setActiveEmployee(true);
|
||||||
setActiveEmployee(true)
|
// setFilteredEmployees(employees.filter((emp) => emp.isActive));
|
||||||
setFilteredEmployees(employees.filter((emp) => emp.isActive));
|
// } else {
|
||||||
} else {
|
// setFilteredEmployees(employees.filter((emp) => !emp.isActive));
|
||||||
setFilteredEmployees( employees.filter( ( emp ) => !emp.isActive ) );
|
// setActiveEmployee(false);
|
||||||
setActiveEmployee(false)
|
// }
|
||||||
}
|
setActiveEmployee(filterValue === "true");
|
||||||
|
setSearchTerm("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteModalOpen = (item) =>
|
const handleSearch = (e) => {
|
||||||
{
|
setSearchTerm(e.target.value);
|
||||||
setDeleteEmplyee(item)
|
};
|
||||||
setIsDeleteModal(true)
|
|
||||||
}
|
const deleteModalOpen = (item) => {
|
||||||
const closeDeleteModal = ()=> setIsDeleteModal(false)
|
setDeleteEmplyee(item);
|
||||||
|
setIsDeleteModal(true);
|
||||||
|
};
|
||||||
|
const closeDeleteModal = () => setIsDeleteModal(false);
|
||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
@ -167,7 +206,7 @@ const {
|
|||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[projectId, refetch]
|
[projectId, refetch]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -175,18 +214,19 @@ const {
|
|||||||
return () => eventBus.off("assign_project_all", handler);
|
return () => eventBus.off("assign_project_all", handler);
|
||||||
}, [handler]);
|
}, [handler]);
|
||||||
|
|
||||||
const employeeHandler = useCallback(
|
const employeeHandler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if(filteredEmployees.some((item) => item.employeeId == msg.employeeId)){
|
if (filteredEmployees.some((item) => item.employeeId == msg.employeeId)) {
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
},[filteredEmployees, refetch]
|
},
|
||||||
|
[filteredEmployees, refetch]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eventBus.on("employee",employeeHandler);
|
eventBus.on("employee", employeeHandler);
|
||||||
return () => eventBus.off("employee",employeeHandler)
|
return () => eventBus.off("employee", employeeHandler);
|
||||||
},[employeeHandler])
|
}, [employeeHandler]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -208,8 +248,7 @@ const {
|
|||||||
></MapUsers>
|
></MapUsers>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{IsDeleteModal && (
|
||||||
{IsDeleteModal && (
|
|
||||||
<div
|
<div
|
||||||
className={`modal fade ${IsDeleteModal ? "show" : ""}`}
|
className={`modal fade ${IsDeleteModal ? "show" : ""}`}
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
@ -220,7 +259,6 @@ const {
|
|||||||
}}
|
}}
|
||||||
aria-hidden="false"
|
aria-hidden="false"
|
||||||
>
|
>
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
type={"delete"}
|
type={"delete"}
|
||||||
header={"Removed Employee"}
|
header={"Removed Employee"}
|
||||||
@ -235,8 +273,20 @@ const {
|
|||||||
|
|
||||||
<div className="card card-action mb-6">
|
<div className="card card-action mb-6">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="row">
|
<div className="row d-flex justify-content-between mb-4">
|
||||||
<div className="col-12 d-flex justify-content-between mb-1">
|
<div className="col-md-6 col-12 d-flex align-items-center">
|
||||||
|
<div className="dataTables_filter d-inline-flex align-items-center ms-2">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
className="form-control form-control-sm me-4"
|
||||||
|
placeholder="Search by Name or Role"
|
||||||
|
aria-controls="DataTables_Table_0"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={handleSearch}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6 col-12 d-flex justify-content-end align-items-center">
|
||||||
<div
|
<div
|
||||||
className="dataTables_length text-start py-2 px-2"
|
className="dataTables_length text-start py-2 px-2"
|
||||||
id="DataTables_Table_0_length"
|
id="DataTables_Table_0_length"
|
||||||
@ -258,7 +308,7 @@ const {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`link-button btn-sm m-1 ${
|
className={`link-button btn-primary btn-sm ${
|
||||||
HasAssignUserPermission ? "" : "d-none"
|
HasAssignUserPermission ? "" : "d-none"
|
||||||
}`}
|
}`}
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
@ -271,91 +321,99 @@ const {
|
|||||||
</div>
|
</div>
|
||||||
<div className="table-responsive text-nowrap">
|
<div className="table-responsive text-nowrap">
|
||||||
{employeeLodaing && <p>Loading..</p>}
|
{employeeLodaing && <p>Loading..</p>}
|
||||||
{!employeeLodaing && employees && employees.length > 0 && (
|
{!employeeLodaing &&
|
||||||
<table className="table ">
|
filteredEmployees &&
|
||||||
<thead>
|
filteredEmployees.length > 0 && (
|
||||||
<tr>
|
<table className="table ">
|
||||||
<th>Name</th>
|
<thead>
|
||||||
<th>Assigned Date</th>
|
<tr>
|
||||||
{!activeEmployee && <th>Release Date</th>}
|
<th>
|
||||||
<th>Project Role</th>
|
<div className="text-start ms-5">Name</div>
|
||||||
<th>Actions</th>
|
</th>
|
||||||
</tr>
|
<th>Assigned Date</th>
|
||||||
</thead>
|
{!activeEmployee && <th>Release Date</th>}
|
||||||
<tbody className="table-border-bottom-0">
|
<th>Project Role</th>
|
||||||
{filteredEmployees &&
|
<th>Actions</th>
|
||||||
filteredEmployees.map((item) => (
|
</tr>
|
||||||
<tr key={item.id}>
|
</thead>
|
||||||
<td>
|
<tbody className="table-border-bottom-0">
|
||||||
<div className="d-flex justify-content-start align-items-center">
|
{filteredEmployees &&
|
||||||
<Avatar
|
filteredEmployees.map((item) => (
|
||||||
firstName={item.firstName}
|
<tr key={item.id}>
|
||||||
lastName={item.lastName}
|
<td>
|
||||||
></Avatar>
|
<div className="d-flex justify-content-start align-items-center">
|
||||||
<div className="d-flex flex-column">
|
<Avatar
|
||||||
<a
|
firstName={item.firstName}
|
||||||
onClick={() =>
|
lastName={item.lastName}
|
||||||
navigate(`/employee/${item.employeeId}?for=attendance`)
|
></Avatar>
|
||||||
}
|
<div className="d-flex flex-column">
|
||||||
className="text-heading text-truncate cursor-pointer"
|
<a
|
||||||
>
|
onClick={() =>
|
||||||
<span className="fw-normal">
|
navigate(
|
||||||
{item.firstName} {item.middleName}{" "}
|
`/employee/${item.employeeId}?for=attendance`
|
||||||
{item.lastName}
|
)
|
||||||
</span>
|
}
|
||||||
</a>
|
className="text-heading text-truncate cursor-pointer"
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{" "}
|
|
||||||
{moment(item.allocationDate).format(
|
|
||||||
"DD-MMM-YYYY"
|
|
||||||
)}{" "}
|
|
||||||
</td>
|
|
||||||
{!activeEmployee && <td>
|
|
||||||
{item.reAllocationDate
|
|
||||||
? moment(item.reAllocationDate).format(
|
|
||||||
"DD-MMM-YYYY"
|
|
||||||
)
|
|
||||||
: "Present"}
|
|
||||||
</td>}
|
|
||||||
<td>
|
|
||||||
<span className="badge bg-label-primary me-1">
|
|
||||||
{getRole(item.jobRoleId)}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{item.isActive && (
|
|
||||||
<button
|
|
||||||
aria-label="Delete"
|
|
||||||
type="button"
|
|
||||||
title="Remove from project"
|
|
||||||
className="btn p-0 dropdown-toggle hide-arrow"
|
|
||||||
onClick={() => deleteModalOpen(item)}
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
{removingEmployeeId === item.id ? (
|
|
||||||
<div
|
|
||||||
className="spinner-border spinner-border-sm text-primary"
|
|
||||||
role="status"
|
|
||||||
>
|
>
|
||||||
<span className="visually-hidden">
|
<span className="fw-normal">
|
||||||
Loading...
|
{item.firstName} {item.middleName}{" "}
|
||||||
|
{item.lastName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</a>
|
||||||
) : (
|
</div>
|
||||||
<i className="bx bx-trash me-1 text-danger"></i>
|
</div>
|
||||||
)}
|
</td>
|
||||||
</button>
|
<td>
|
||||||
|
{" "}
|
||||||
|
{moment(item.allocationDate).format(
|
||||||
|
"DD-MMM-YYYY"
|
||||||
|
)}{" "}
|
||||||
|
</td>
|
||||||
|
{!activeEmployee && (
|
||||||
|
<td>
|
||||||
|
{item.reAllocationDate
|
||||||
|
? moment(item.reAllocationDate).format(
|
||||||
|
"DD-MMM-YYYY"
|
||||||
|
)
|
||||||
|
: "Present"}
|
||||||
|
</td>
|
||||||
)}
|
)}
|
||||||
{!item.isActive && <span>Not in project</span>}
|
<td>
|
||||||
</td>
|
<span className="badge bg-label-primary me-1">
|
||||||
</tr>
|
{getRole(item.jobRoleId)}
|
||||||
))}
|
</span>
|
||||||
</tbody>
|
</td>
|
||||||
</table>
|
<td>
|
||||||
)}
|
{item.isActive && (
|
||||||
|
<button
|
||||||
|
aria-label="Delete"
|
||||||
|
type="button"
|
||||||
|
title="Remove from project"
|
||||||
|
className="btn p-0 dropdown-toggle hide-arrow"
|
||||||
|
onClick={() => deleteModalOpen(item)}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
{removingEmployeeId === item.id ? (
|
||||||
|
<div
|
||||||
|
className="spinner-border spinner-border-sm text-primary"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
<span className="visually-hidden">
|
||||||
|
Loading...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<i className="bx bx-trash me-1 text-danger"></i>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{!item.isActive && <span>Not in project</span>}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
{!employeeLodaing && filteredEmployees.length === 0 && (
|
{!employeeLodaing && filteredEmployees.length === 0 && (
|
||||||
<div className="text-center text-muted py-3">
|
<div className="text-center text-muted py-3">
|
||||||
{activeEmployee
|
{activeEmployee
|
||||||
|
@ -31,7 +31,7 @@ const DateRangePicker = ({
|
|||||||
clickOpens: true,
|
clickOpens: true,
|
||||||
maxDate: endDate,
|
maxDate: endDate,
|
||||||
onChange: (selectedDates, dateStr) => {
|
onChange: (selectedDates, dateStr) => {
|
||||||
const [startDateString, endDateString] = dateStr.split(" To ");
|
const [startDateString, endDateString] = dateStr.split(" to ");
|
||||||
onRangeChange?.({ startDate: startDateString, endDate: endDateString });
|
onRangeChange?.({ startDate: startDateString, endDate: endDateString });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -47,18 +47,18 @@ const DateRangePicker = ({
|
|||||||
}, [onRangeChange, DateDifference, endDateMode]);
|
}, [onRangeChange, DateDifference, endDateMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`col-${sm} col-sm-${md} px-1 position-relative`}>
|
<div className={`col-${sm} col-sm-${md} px-1`}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control-sm ps-2 pe-5 me-4"
|
className="form-control form-control-sm ps-2 pe-5 me-4"
|
||||||
placeholder="From to End"
|
placeholder="From to End"
|
||||||
id="flatpickr-range"
|
id="flatpickr-range"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<i
|
<i
|
||||||
className="bx bx-calendar calendar-icon cursor-pointer position-absolute top-50 translate-middle-y "
|
className="bx bx-calendar calendar-icon cursor-pointer position-relative top-50 translate-middle-y "
|
||||||
style={{ right: "12px" }}
|
style={{ right: "22px", bottom: "-8px" }}
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -99,21 +99,22 @@ const FilterIcon = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dropdown">
|
<div className="dropdown" style={{marginLeft:"-14px"}}>
|
||||||
<a
|
<a
|
||||||
className="dropdown-toggle hide-arrow cursor-pointer"
|
className="dropdown-toggle hide-arrow cursor-pointer"
|
||||||
id="filterDropdown"
|
id="filterDropdown"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
|
{/* <i className="bx bx-slider-alt ms-1" /> */}
|
||||||
<i
|
<i
|
||||||
className="fa-solid fa-filter bx-sm"
|
className="bx bx-slider-alt"
|
||||||
style={{ color: selectedBuilding || selectedFloors.length > 0 || selectedActivities.length > 0 ? "#7161EF" : "gray" }}
|
style={{ color: selectedBuilding || selectedFloors.length > 0 || selectedActivities.length > 0 ? "#7161EF" : "gray" }}
|
||||||
></i>
|
></i>
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
<ul
|
<ul
|
||||||
className="dropdown-menu p-2"
|
className="dropdown-menu p-2 mt-2"
|
||||||
aria-labelledby="filterDropdown"
|
aria-labelledby="filterDropdown"
|
||||||
style={{ minWidth: "360px", fontSize: "13px" }}
|
style={{ minWidth: "360px", fontSize: "13px" }}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
@ -241,7 +241,7 @@ useEffect(() => {
|
|||||||
{isLoading ? "Please Wait" : "Submit"}
|
{isLoading ? "Please Wait" : "Submit"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="reset"
|
type="button"
|
||||||
className="btn btn-sm btn-label-secondary"
|
className="btn btn-sm btn-label-secondary"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
|
@ -116,7 +116,7 @@ useEffect(() => {
|
|||||||
{isLoading? "Please Wait...":"Submit"}
|
{isLoading? "Please Wait...":"Submit"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="reset"
|
type="button"
|
||||||
className="btn btn-sm btn-label-secondary "
|
className="btn btn-sm btn-label-secondary "
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
|
@ -116,7 +116,7 @@ useEffect(() => {
|
|||||||
{isLoading? "Please Wait...":"Submit"}
|
{isLoading? "Please Wait...":"Submit"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="reset"
|
type="button"
|
||||||
className="btn btn-sm btn-label-secondary "
|
className="btn btn-sm btn-label-secondary "
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
|
@ -132,7 +132,7 @@ const [descriptionLength, setDescriptionLength] = useState(data?.description?.le
|
|||||||
{isLoading? "Please Wait...":"Submit"}
|
{isLoading? "Please Wait...":"Submit"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="reset"
|
type="button"
|
||||||
className="btn btn-sm btn-label-secondary"
|
className="btn btn-sm btn-label-secondary"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
|
@ -278,7 +278,7 @@ const EditMaster = ({ master, onClose }) => {
|
|||||||
<div className="col-12 text-center">
|
<div className="col-12 text-center">
|
||||||
<button type="submit" className="btn btn-sm btn-primary me-3"> {isLoading ? "Please Wait..." : "Submit"}</button>
|
<button type="submit" className="btn btn-sm btn-primary me-3"> {isLoading ? "Please Wait..." : "Submit"}</button>
|
||||||
<button
|
<button
|
||||||
type="reset"
|
type="button"
|
||||||
className="btn btn-sm btn-label-secondary"
|
className="btn btn-sm btn-label-secondary"
|
||||||
data-bs-dismiss="modal"
|
data-bs-dismiss="modal"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { cacheData, getCachedData } from "../slices/apiDataManager";
|
import { cacheData, getCachedData, useSelectedproject } from "../slices/apiDataManager";
|
||||||
import AttendanceRepository from "../repositories/AttendanceRepository";
|
import AttendanceRepository from "../repositories/AttendanceRepository";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import showToast from "../services/toastService";
|
import showToast from "../services/toastService";
|
||||||
@ -7,111 +7,6 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { store } from "../store/store";
|
import { store } from "../store/store";
|
||||||
import { setDefaultDateRange } from "../slices/localVariablesSlice";
|
import { setDefaultDateRange } from "../slices/localVariablesSlice";
|
||||||
|
|
||||||
// export const useAttendace =(projectId)=>{
|
|
||||||
|
|
||||||
// const [attendance, setAttendance] = useState([]);
|
|
||||||
// const[loading,setLoading] = useState(true)
|
|
||||||
// const [error, setError] = useState(null);
|
|
||||||
|
|
||||||
// const fetchData = () => {
|
|
||||||
// const Attendance_cache = getCachedData("Attendance");
|
|
||||||
// if(!Attendance_cache || Attendance_cache.projectId !== projectId){
|
|
||||||
|
|
||||||
// setLoading(true);
|
|
||||||
// AttendanceRepository.getAttendance(projectId)
|
|
||||||
// .then((response) => {
|
|
||||||
// setAttendance(response.data);
|
|
||||||
// cacheData( "Attendance", {data: response.data, projectId} )
|
|
||||||
// setLoading(false)
|
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// setLoading(false)
|
|
||||||
// setError("Failed to fetch data.");
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
// setAttendance(Attendance_cache.data);
|
|
||||||
// setLoading(false)
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
|
|
||||||
// useEffect(()=>{
|
|
||||||
// if ( projectId && projectId != 1 )
|
|
||||||
// {
|
|
||||||
// fetchData(projectId);
|
|
||||||
// }
|
|
||||||
// },[projectId])
|
|
||||||
|
|
||||||
// return {attendance,loading,error,recall:fetchData}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export const useEmployeeAttendacesLog = (id) => {
|
|
||||||
// const [logs, setLogs] = useState([]);
|
|
||||||
// const [loading, setLoading] = useState(false);
|
|
||||||
// const [error, setError] = useState(null);
|
|
||||||
|
|
||||||
|
|
||||||
// const fetchData = () => {
|
|
||||||
// const AttendanceLog_cache = getCachedData("AttendanceLogs");
|
|
||||||
// if(!AttendanceLog_cache || AttendanceLog_cache.id !== id ){
|
|
||||||
// setLoading(true)
|
|
||||||
// AttendanceRepository.getAttendanceLogs(id).then((response)=>{
|
|
||||||
// setLogs(response.data)
|
|
||||||
// cacheData("AttendanceLogs", { data: response.data, id })
|
|
||||||
// setLoading(false)
|
|
||||||
// }).catch((error)=>{
|
|
||||||
// setError("Failed to fetch data.");
|
|
||||||
// setLoading(false);
|
|
||||||
// })
|
|
||||||
// }else{
|
|
||||||
|
|
||||||
// setLogs(AttendanceLog_cache.data);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (id) {
|
|
||||||
// fetchData();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }, [id]);
|
|
||||||
|
|
||||||
// return { logs, loading, error };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const useRegularizationRequests = ( projectId ) =>
|
|
||||||
// {
|
|
||||||
// const [regularizes, setregularizes] = useState([]);
|
|
||||||
// const [loading, setLoading] = useState(false);
|
|
||||||
// const [error, setError] = useState(null);
|
|
||||||
|
|
||||||
|
|
||||||
// const fetchData = () => {
|
|
||||||
// const regularizedList_cache = getCachedData("regularizedList");
|
|
||||||
// if(!regularizedList_cache || regularizedList_cache.projectId !== projectId ){
|
|
||||||
// setLoading(true)
|
|
||||||
// AttendanceRepository.getRegularizeList(projectId).then((response)=>{
|
|
||||||
// setregularizes( response.data )
|
|
||||||
// cacheData("regularizedList", { data: response.data, projectId })
|
|
||||||
// setLoading(false)
|
|
||||||
// }).catch((error)=>{
|
|
||||||
// setError("Failed to fetch data.");
|
|
||||||
// setLoading(false);
|
|
||||||
// })
|
|
||||||
// }else{
|
|
||||||
|
|
||||||
// setregularizes(regularizedList_cache.data);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (projectId) {
|
|
||||||
// fetchData();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }, [ projectId ] );
|
|
||||||
// return {regularizes,loading,error,refetch:fetchData}
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------Query-----------------------------
|
// ----------------------------Query-----------------------------
|
||||||
@ -248,7 +143,8 @@ export const useRegularizationRequests = (projectId) => {
|
|||||||
|
|
||||||
export const useMarkAttendance = () => {
|
export const useMarkAttendance = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
||||||
|
const selectedProject = useSelectedproject();
|
||||||
const selectedDateRange = useSelector((store)=>store.localVariables.defaultDateRange)
|
const selectedDateRange = useSelector((store)=>store.localVariables.defaultDateRange)
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
@ -176,8 +176,9 @@ export const useEmployeeProfile = (employeeId) => {
|
|||||||
export const useEmployeesName = (projectId, search) => {
|
export const useEmployeesName = (projectId, search) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["employees", projectId, search],
|
queryKey: ["employees", projectId, search],
|
||||||
queryFn: async() => await EmployeeRepository.getEmployeeName(projectId, search),
|
queryFn: async () =>
|
||||||
|
await EmployeeRepository.getEmployeeName(projectId, search),
|
||||||
|
|
||||||
staleTime: 5 * 60 * 1000, // Optional: cache for 5 minutes
|
staleTime: 5 * 60 * 1000, // Optional: cache for 5 minutes
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -194,7 +195,6 @@ export const useEmployeesNameByProject = (projectId) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Mutation------------------------------------------------------------------
|
// Mutation------------------------------------------------------------------
|
||||||
|
|
||||||
export const useUpdateEmployee = () => {
|
export const useUpdateEmployee = () => {
|
||||||
@ -232,36 +232,84 @@ export const useUpdateEmployee = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// export const useSuspendEmployee = ({ setIsDeleteModalOpen, setemployeeLodaing }) => {
|
||||||
|
// const queryClient = useQueryClient();
|
||||||
|
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
||||||
|
// return useMutation({
|
||||||
|
// mutationFn: (id) => {
|
||||||
|
// setemployeeLodaing(true);
|
||||||
|
// return EmployeeRepository.deleteEmployee(id);
|
||||||
|
// },
|
||||||
|
|
||||||
|
// onSuccess: () => {
|
||||||
|
|
||||||
|
// // queryClient.invalidateQueries( ['allEmployee',false]);
|
||||||
|
// queryClient.invalidateQueries( {queryKey: [ 'projectEmployees' ]} );
|
||||||
|
// queryClient.invalidateQueries( {queryKey:[ 'employeeListByProject' ,selectedProject]} );
|
||||||
|
// showToast("Employee deleted successfully.", "success");
|
||||||
|
// setIsDeleteModalOpen(false);
|
||||||
|
// },
|
||||||
|
|
||||||
|
// onError: (error) => {
|
||||||
|
// const message =
|
||||||
|
// error.response?.data?.message ||
|
||||||
|
// error.message ||
|
||||||
|
// "An unexpected error occurred";
|
||||||
|
// showToast(message, "error");
|
||||||
|
// setIsDeleteModalOpen(false);
|
||||||
|
// },
|
||||||
|
|
||||||
|
// onSettled: () => {
|
||||||
|
// setemployeeLodaing(false);
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// Manage Role
|
||||||
|
|
||||||
export const useSuspendEmployee = ({
|
export const useSuspendEmployee = ({
|
||||||
setIsDeleteModalOpen,
|
setIsDeleteModalOpen,
|
||||||
setemployeeLodaing,
|
setemployeeLodaing,
|
||||||
}) => {
|
}) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const selectedProject = useSelector(
|
const selectedProjectId = useSelector(
|
||||||
(store) => store.localVariables.projectId
|
(store) => store.localVariables.projectId
|
||||||
);
|
);
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id) => {
|
// Expect both employeeId and active status
|
||||||
|
mutationFn: async ({ employeeId, active }) => {
|
||||||
setemployeeLodaing(true);
|
setemployeeLodaing(true);
|
||||||
return EmployeeRepository.deleteEmployee(id);
|
return await EmployeeRepository.deleteEmployee(employeeId, active);
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess: () => {
|
onSuccess: (_, { employeeId, active }) => {
|
||||||
// queryClient.invalidateQueries( ['allEmployee',false]);
|
const message =
|
||||||
queryClient.invalidateQueries({ queryKey: ["projectEmployees"] });
|
active === false
|
||||||
queryClient.invalidateQueries({
|
? "Employee suspended successfully."
|
||||||
queryKey: ["employeeListByProject", selectedProject],
|
: "Employee reactivated successfully.";
|
||||||
});
|
|
||||||
showToast("Employee deleted successfully.", "success");
|
showToast(message, "success");
|
||||||
setIsDeleteModalOpen(false);
|
setIsDeleteModalOpen(false);
|
||||||
|
|
||||||
|
// Invalidate relevant queries
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["employee", employeeId] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["allEmployees"] });
|
||||||
|
|
||||||
|
if (selectedProjectId) {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["projectEmployees", selectedProjectId],
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
const message =
|
showToast(
|
||||||
error.response?.data?.message ||
|
error.response?.data?.message ||
|
||||||
error.message ||
|
error.message ||
|
||||||
"An unexpected error occurred";
|
"An unexpected error occurred",
|
||||||
showToast(message, "error");
|
"error"
|
||||||
|
);
|
||||||
setIsDeleteModalOpen(false);
|
setIsDeleteModalOpen(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -271,7 +319,6 @@ export const useSuspendEmployee = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Manage Role
|
|
||||||
|
|
||||||
export const useUpdateEmployeeRoles = ({
|
export const useUpdateEmployeeRoles = ({
|
||||||
onClose,
|
onClose,
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
clearCacheKey,
|
clearCacheKey,
|
||||||
getCachedData,
|
getCachedData,
|
||||||
getCachedProfileData,
|
getCachedProfileData,
|
||||||
|
useSelectedproject,
|
||||||
} from "../../slices/apiDataManager";
|
} from "../../slices/apiDataManager";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import AttendanceLog from "../../components/Activities/AttendcesLogs";
|
import AttendanceLog from "../../components/Activities/AttendcesLogs";
|
||||||
@ -25,9 +26,11 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||||||
const AttendancePage = () => {
|
const AttendancePage = () => {
|
||||||
const [activeTab, setActiveTab] = useState("all");
|
const [activeTab, setActiveTab] = useState("all");
|
||||||
const [ShowPending, setShowPending] = useState(false);
|
const [ShowPending, setShowPending] = useState(false);
|
||||||
|
const [searchTerm, setSearchTerm] = useState(""); // 🔹 New state for search
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const loginUser = getCachedProfileData();
|
const loginUser = getCachedProfileData();
|
||||||
const selectedProject = useSelector((store) => store.localVariables.projectId);
|
// const selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||||
|
const selectedProject = useSelectedproject();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [attendances, setAttendances] = useState();
|
const [attendances, setAttendances] = useState();
|
||||||
@ -69,17 +72,18 @@ const AttendancePage = () => {
|
|||||||
setIsCreateModalOpen(false);
|
setIsCreateModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggle = (event) => {
|
|
||||||
setShowOnlyCheckout(event.target.checked);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modelConfig !== null) {
|
if (modelConfig !== null) {
|
||||||
openModel();
|
openModel();
|
||||||
}
|
}
|
||||||
}, [modelConfig, isCreateModalOpen]);
|
}, [modelConfig, isCreateModalOpen]);
|
||||||
|
|
||||||
|
// Handler to change tab and reset search term
|
||||||
|
const handleTabChange = (tabName) => {
|
||||||
|
setActiveTab(tabName);
|
||||||
|
setSearchTerm(""); // Reset search term when tab changes
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isCreateModalOpen && modelConfig && (
|
{isCreateModalOpen && modelConfig && (
|
||||||
@ -91,11 +95,11 @@ const AttendancePage = () => {
|
|||||||
{(modelConfig?.action === 0 ||
|
{(modelConfig?.action === 0 ||
|
||||||
modelConfig?.action === 1 ||
|
modelConfig?.action === 1 ||
|
||||||
modelConfig?.action === 2) && (
|
modelConfig?.action === 2) && (
|
||||||
<CheckCheckOutmodel
|
<CheckCheckOutmodel
|
||||||
modeldata={modelConfig}
|
modeldata={modelConfig}
|
||||||
closeModal={closeModal}
|
closeModal={closeModal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* For view logs */}
|
{/* For view logs */}
|
||||||
{modelConfig?.action === 6 && (
|
{modelConfig?.action === 6 && (
|
||||||
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
|
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
|
||||||
@ -120,8 +124,8 @@ const AttendancePage = () => {
|
|||||||
type="button"
|
type="button"
|
||||||
className={`nav-link ${
|
className={`nav-link ${
|
||||||
activeTab === "all" ? "active" : ""
|
activeTab === "all" ? "active" : ""
|
||||||
} fs-6`}
|
} fs-6`}
|
||||||
onClick={() => setActiveTab("all")}
|
onClick={() => handleTabChange("all")}
|
||||||
data-bs-toggle="tab"
|
data-bs-toggle="tab"
|
||||||
data-bs-target="#navs-top-home"
|
data-bs-target="#navs-top-home"
|
||||||
>
|
>
|
||||||
@ -133,8 +137,8 @@ const AttendancePage = () => {
|
|||||||
type="button"
|
type="button"
|
||||||
className={`nav-link ${
|
className={`nav-link ${
|
||||||
activeTab === "logs" ? "active" : ""
|
activeTab === "logs" ? "active" : ""
|
||||||
} fs-6`}
|
} fs-6`}
|
||||||
onClick={() => setActiveTab("logs")}
|
onClick={() => handleTabChange("logs")}
|
||||||
data-bs-toggle="tab"
|
data-bs-toggle="tab"
|
||||||
data-bs-target="#navs-top-profile"
|
data-bs-target="#navs-top-profile"
|
||||||
>
|
>
|
||||||
@ -146,40 +150,60 @@ const AttendancePage = () => {
|
|||||||
type="button"
|
type="button"
|
||||||
className={`nav-link ${
|
className={`nav-link ${
|
||||||
activeTab === "regularization" ? "active" : ""
|
activeTab === "regularization" ? "active" : ""
|
||||||
} fs-6`}
|
} fs-6`}
|
||||||
onClick={() => setActiveTab("regularization")}
|
onClick={() => handleTabChange("regularization")}
|
||||||
data-bs-toggle="tab"
|
data-bs-toggle="tab"
|
||||||
data-bs-target="#navs-top-messages"
|
data-bs-target="#navs-top-messages"
|
||||||
>
|
>
|
||||||
Regularization
|
Regularization
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
{/* 🔹 Search box placed after Regularization tab */}
|
||||||
|
<li className="nav-item ms-auto me-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control-sm mt-1"
|
||||||
|
placeholder="Search Employee..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
style={{ minWidth: "200px" }}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3">
|
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3">
|
||||||
{selectedProject ? (
|
{selectedProject ? (
|
||||||
<>
|
<>
|
||||||
{activeTab === "all" && (
|
{activeTab === "all" && (
|
||||||
<div className="tab-pane fade show active py-0">
|
<div className="tab-pane fade show active py-0">
|
||||||
<Attendance handleModalData={handleModalData} getRole={getRole} />
|
<Attendance
|
||||||
</div>
|
handleModalData={handleModalData}
|
||||||
)}
|
getRole={getRole}
|
||||||
{activeTab === "logs" && (
|
searchTerm={searchTerm}
|
||||||
<div className="tab-pane fade show active py-0">
|
/>
|
||||||
<AttendanceLog handleModalData={handleModalData} />
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
{activeTab === "logs" && (
|
||||||
{activeTab === "regularization" && DoRegularized && (
|
<div className="tab-pane fade show active py-0">
|
||||||
<div className="tab-pane fade show active py-0">
|
<AttendanceLog
|
||||||
<Regularization />
|
handleModalData={handleModalData}
|
||||||
</div>
|
searchTerm={searchTerm}
|
||||||
)}
|
/>
|
||||||
</>
|
</div>
|
||||||
) : (
|
)}
|
||||||
<div className="py-2">
|
{activeTab === "regularization" && DoRegularized && (
|
||||||
<small className="py-2">Please Select Project!</small>
|
<div className="tab-pane fade show active py-0">
|
||||||
</div>
|
<Regularization searchTerm={searchTerm} />
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="py-2">
|
||||||
|
<small>Please Select Project!</small>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,11 +16,13 @@ import SubTask from "../../components/Activities/SubTask";
|
|||||||
import {formatNumber} from "../../utils/dateUtils";
|
import {formatNumber} from "../../utils/dateUtils";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants";
|
import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants";
|
||||||
|
import { useSelectedproject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const DailyTask = () => {
|
const DailyTask = () => {
|
||||||
const selectedProject = useSelector(
|
// const selectedProject = useSelector(
|
||||||
(store) => store.localVariables.projectId
|
// (store) => store.localVariables.projectId
|
||||||
);
|
// );
|
||||||
|
const selectedProject = useSelectedproject();
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||||
|
|
||||||
@ -201,7 +203,7 @@ const DailyTask = () => {
|
|||||||
<div className="card card-action mb-6 ">
|
<div className="card card-action mb-6 ">
|
||||||
<div className="card-body p-1 p-sm-2">
|
<div className="card-body p-1 p-sm-2">
|
||||||
<div className="row d-flex justify-content-between align-items-center">
|
<div className="row d-flex justify-content-between align-items-center">
|
||||||
<div className="col-md-12 d-flex gap-3 align-items-center col-12 text-start mb-2 mb-md-0">
|
<div className="col-md-12 d-flex align-items-center col-12 text-start mb-2 mb-md-0">
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
onRangeChange={setDateRange}
|
onRangeChange={setDateRange}
|
||||||
endDateMode="today"
|
endDateMode="today"
|
||||||
|
@ -4,12 +4,14 @@ import InfraPlanning from "../../components/Activities/InfraPlanning";
|
|||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
|
import { useSelectedproject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
|
|
||||||
const TaskPlannng = () => {
|
const TaskPlannng = () => {
|
||||||
const selectedProject = useSelector(
|
// const selectedProject = useSelector(
|
||||||
(store) => store.localVariables.projectId
|
// (store) => store.localVariables.projectId
|
||||||
);
|
// );
|
||||||
|
const selectedProject = useSelectedproject();
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||||
|
@ -38,7 +38,7 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
const [IsDeleting, setDeleting] = useState(false);
|
const [IsDeleting, setDeleting] = useState(false);
|
||||||
const [openBucketModal, setOpenBucketModal] = useState(false);
|
const [openBucketModal, setOpenBucketModal] = useState(false);
|
||||||
const [notes, setNotes] = useState([]);
|
const [notes, setNotes] = useState([]);
|
||||||
const [filterAppliedNotes, setFilterAppliedNotes] = useState([]);
|
const [filterAppliedNotes, setFilterAppliedNotes] = useState([]);
|
||||||
// const [selectedOrgs, setSelectedOrgs] = useState([]);
|
// const [selectedOrgs, setSelectedOrgs] = useState([]);
|
||||||
|
|
||||||
// ✅ Changed to an array for multiple selections
|
// ✅ Changed to an array for multiple selections
|
||||||
@ -260,7 +260,7 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
}, [prefernceContacts]);
|
}, [prefernceContacts]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={IsPage ? "container-fluid":""}>
|
<div className={IsPage ? "container-fluid" : ""}>
|
||||||
{IsPage && (
|
{IsPage && (
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
@ -353,7 +353,7 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
setOpenBucketModal={setOpenBucketModal}
|
setOpenBucketModal={setOpenBucketModal}
|
||||||
contactsToExport={contacts}
|
contactsToExport={contacts}
|
||||||
notesToExport={notes}
|
notesToExport={notes}
|
||||||
selectedNoteNames={selectedNoteNames}
|
selectedNoteNames={selectedNoteNames}
|
||||||
setSelectedNoteNames={setSelectedNoteNames}
|
setSelectedNoteNames={setSelectedNoteNames}
|
||||||
notesForFilter={notes}
|
notesForFilter={notes}
|
||||||
setFilterAppliedNotes={setFilterAppliedNotes}
|
setFilterAppliedNotes={setFilterAppliedNotes}
|
||||||
@ -361,23 +361,9 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-minHeight mt-0">
|
<div className="card-minHeight mt-0">
|
||||||
{(viewType === "card" || viewType === "list" || viewType === "notes") && (
|
|
||||||
<div className="d-flex flex-column justify-content-center align-items-center text-center">
|
|
||||||
{!loading && (viewType === "card" || viewType === "list") && contacts?.length === 0 && (
|
|
||||||
<p className="mt-10">No contact found</p>
|
|
||||||
)}
|
|
||||||
{!loading &&
|
|
||||||
(viewType === "card" || viewType === "list") &&
|
|
||||||
contacts?.length > 0 &&
|
|
||||||
currentItems.length === 0 && (
|
|
||||||
<p className="mt-10">No matching contact found</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{viewType === "list" && (
|
{viewType === "list" && (
|
||||||
<div className="card cursor-pointer mt-5">
|
<div className="card cursor-pointer mt-3">
|
||||||
<div className="card-body p-2 pb-1">
|
<div className="card-body p-2 pb-1" style={{ minHeight: "200px" }}>
|
||||||
<DirectoryListTableHeader>
|
<DirectoryListTableHeader>
|
||||||
{!loading &&
|
{!loading &&
|
||||||
currentItems.map((contact) => (
|
currentItems.map((contact) => (
|
||||||
@ -394,12 +380,22 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</DirectoryListTableHeader>
|
</DirectoryListTableHeader>
|
||||||
|
|
||||||
|
{/* Empty state AFTER list */}
|
||||||
|
{!loading && contacts?.length === 0 && (
|
||||||
|
<p className="mt-3 ms-3 text-muted" >No contact found</p>
|
||||||
|
)}
|
||||||
|
{!loading &&
|
||||||
|
contacts?.length > 0 &&
|
||||||
|
currentItems.length === 0 && (
|
||||||
|
<p className="mt-3 ms-3 text-muted">No matching contact found</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{viewType === "card" && (
|
{viewType === "card" && (
|
||||||
<div className="row mt-4">
|
<div className="row mt-10">
|
||||||
{!loading &&
|
{!loading &&
|
||||||
currentItems.map((contact) => (
|
currentItems.map((contact) => (
|
||||||
<div
|
<div
|
||||||
@ -418,6 +414,16 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{/* Empty state for card view */}
|
||||||
|
{!loading && contacts?.length === 0 && (
|
||||||
|
<p className="mt-3 ms-3 text-muted">No contact found</p>
|
||||||
|
)}
|
||||||
|
{!loading &&
|
||||||
|
contacts?.length > 0 &&
|
||||||
|
currentItems.length === 0 && (
|
||||||
|
<p className="mt-3 ms-3 text-muted">No matching contact found</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -428,7 +434,7 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
setNotesForFilter={setNotes}
|
setNotesForFilter={setNotes}
|
||||||
searchText={searchText}
|
searchText={searchText}
|
||||||
setIsOpenModalNote={setIsOpenModalNote}
|
setIsOpenModalNote={setIsOpenModalNote}
|
||||||
filterAppliedNotes={filterAppliedNotes}
|
filterAppliedNotes={filterAppliedNotes}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -464,7 +470,10 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<li className={`page-item ${currentPage === totalPages ? "disabled" : ""}`}>
|
<li
|
||||||
|
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
onClick={() => paginate(currentPage + 1)}
|
onClick={() => paginate(currentPage + 1)}
|
||||||
|
@ -3,7 +3,7 @@ import IconButton from "../../components/common/IconButton";
|
|||||||
|
|
||||||
const DirectoryListTableHeader = ({ children }) => {
|
const DirectoryListTableHeader = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<div className="table-responsive text-nowrap py-2">
|
<div className="table-responsive text-nowrap py-2" style={{ minHeight: "80px"}}>
|
||||||
<table className="table px-2">
|
<table className="table px-2">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -318,54 +318,60 @@ const DirectoryPageHeader = ({
|
|||||||
whiteSpace: "normal"
|
whiteSpace: "normal"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="d-flex gap-3">
|
{allCreators.length === 0 && filteredOrganizations.length === 0 ? (
|
||||||
{/* Created By */}
|
<div className="text-center text-muted py-5">
|
||||||
<div style={{ flexBasis: "30%", maxHeight: "260px", overflowY: "auto" }}>
|
No filter found
|
||||||
<div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}>
|
|
||||||
<p className="text-muted mb-2 pt-2">Created By</p>
|
|
||||||
</div>
|
|
||||||
{allCreators.map((name, idx) => (
|
|
||||||
<div className="form-check mb-1" key={`creator-${idx}`}>
|
|
||||||
<input
|
|
||||||
className="form-check-input form-check-input-sm"
|
|
||||||
type="checkbox"
|
|
||||||
id={`creator-${idx}`}
|
|
||||||
checked={selectedCreators.includes(name)}
|
|
||||||
onChange={() => handleToggleCreator(name)}
|
|
||||||
style={{ width: "1rem", height: "1rem" }}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label text-nowrap" htmlFor={`creator-${idx}`}>
|
|
||||||
{name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
{/* Organization */}
|
<div className="d-flex gap-3">
|
||||||
<div style={{ maxHeight: "260px", overflowY: "auto", overflowX: "hidden", }}>
|
{/* Created By */}
|
||||||
<div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}>
|
<div style={{ flexBasis: "30%", maxHeight: "260px", overflowY: "auto" }}>
|
||||||
<p className="text-muted mb-2 pt-2">Organization</p>
|
<div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}>
|
||||||
</div>
|
<p className="text-muted mb-2 pt-2">Created By</p>
|
||||||
{filteredOrganizations.map((org, idx) => (
|
|
||||||
<div className="form-check mb-1" key={`org-${idx}`}>
|
|
||||||
<input
|
|
||||||
className="form-check-input form-check-input-sm"
|
|
||||||
type="checkbox"
|
|
||||||
id={`org-${idx}`}
|
|
||||||
checked={selectedOrgs.includes(org)}
|
|
||||||
onChange={() => handleToggleOrg(org)}
|
|
||||||
style={{ width: "1rem", height: "1rem" }}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label text-nowrap" htmlFor={`org-${idx}`}>
|
|
||||||
{org}
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
{allCreators.map((name, idx) => (
|
||||||
</div>
|
<div className="form-check mb-1" key={`creator-${idx}`}>
|
||||||
</div>
|
<input
|
||||||
|
className="form-check-input form-check-input-sm"
|
||||||
|
type="checkbox"
|
||||||
|
id={`creator-${idx}`}
|
||||||
|
checked={selectedCreators.includes(name)}
|
||||||
|
onChange={() => handleToggleCreator(name)}
|
||||||
|
style={{ width: "1rem", height: "1rem" }}
|
||||||
|
/>
|
||||||
|
<label className="form-check-label text-nowrap" htmlFor={`creator-${idx}`}>
|
||||||
|
{name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Organization */}
|
||||||
|
<div style={{ maxHeight: "260px", overflowY: "auto", overflowX: "hidden" }}>
|
||||||
|
<div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}>
|
||||||
|
<p className="text-muted mb-2 pt-2">Organization</p>
|
||||||
|
</div>
|
||||||
|
{filteredOrganizations.map((org, idx) => (
|
||||||
|
<div className="form-check mb-1" key={`org-${idx}`}>
|
||||||
|
<input
|
||||||
|
className="form-check-input form-check-input-sm"
|
||||||
|
type="checkbox"
|
||||||
|
id={`org-${idx}`}
|
||||||
|
checked={selectedOrgs.includes(org)}
|
||||||
|
onChange={() => handleToggleOrg(org)}
|
||||||
|
style={{ width: "1rem", height: "1rem" }}
|
||||||
|
/>
|
||||||
|
<label className="form-check-label text-nowrap" htmlFor={`org-${idx}`}>
|
||||||
|
{org}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Sticky Footer Buttons */}
|
{/* Sticky Footer Buttons */}
|
||||||
<div
|
<div
|
||||||
className="d-flex justify-content-end gap-2 p-2 "
|
className="d-flex justify-content-end gap-2 p-2 "
|
||||||
@ -440,54 +446,74 @@ const DirectoryPageHeader = ({
|
|||||||
<ul className="dropdown-menu p-3" style={{ width: "700px" }}>
|
<ul className="dropdown-menu p-3" style={{ width: "700px" }}>
|
||||||
<p className="text-muted m-0 h6">Filter by</p>
|
<p className="text-muted m-0 h6">Filter by</p>
|
||||||
|
|
||||||
<div className="d-flex flex-nowrap">
|
{filteredBuckets.length === 0 && filteredCategories.length === 0 ? (
|
||||||
<div className="mt-1 me-4" style={{ flexBasis: "50%" }}>
|
<div className="text-center text-muted py-5">
|
||||||
<p className="text-small mb-1">Buckets</p>
|
No filter found
|
||||||
<div className="d-flex flex-wrap">
|
</div>
|
||||||
{filteredBuckets.map(({ id, name }) => (
|
) : (
|
||||||
<div className="form-check me-3 mb-1" style={{ minWidth: "calc(50% - 15px)" }} key={id}>
|
<div className="d-flex flex-nowrap">
|
||||||
<input
|
<div className="mt-1 me-4" style={{ flexBasis: "50%" }}>
|
||||||
className="form-check-input form-check-input-sm"
|
<p className="text-small mb-1">Buckets</p>
|
||||||
type="checkbox"
|
<div className="d-flex flex-wrap">
|
||||||
id={`bucket-${id}`}
|
{filteredBuckets.map(({ id, name }) => (
|
||||||
checked={tempSelectedBucketIds.includes(id)}
|
<div
|
||||||
onChange={() => handleTempBucketChange(id)}
|
className="form-check me-3 mb-1"
|
||||||
style={{ width: "1rem", height: "1rem" }}
|
style={{ minWidth: "calc(50% - 15px)" }}
|
||||||
/>
|
key={id}
|
||||||
<label className="form-check-label text-nowrap text-small" htmlFor={`bucket-${id}`}>
|
>
|
||||||
{name}
|
<input
|
||||||
</label>
|
className="form-check-input form-check-input-sm"
|
||||||
</div>
|
type="checkbox"
|
||||||
))}
|
id={`bucket-${id}`}
|
||||||
|
checked={tempSelectedBucketIds.includes(id)}
|
||||||
|
onChange={() => handleTempBucketChange(id)}
|
||||||
|
style={{ width: "1rem", height: "1rem" }}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="form-check-label text-nowrap text-small"
|
||||||
|
htmlFor={`bucket-${id}`}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-1" style={{ flexBasis: "50%" }}>
|
||||||
|
<p className="text-small mb-1">Categories</p>
|
||||||
|
<div className="d-flex flex-wrap">
|
||||||
|
{filteredCategories.map(({ id, name }) => (
|
||||||
|
<div
|
||||||
|
className="form-check me-3 mb-1"
|
||||||
|
style={{ minWidth: "calc(50% - 15px)" }}
|
||||||
|
key={id}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="form-check-input form-check-input-sm"
|
||||||
|
type="checkbox"
|
||||||
|
id={`cat-${id}`}
|
||||||
|
checked={tempSelectedCategoryIds.includes(id)}
|
||||||
|
onChange={() => handleTempCategoryChange(id)}
|
||||||
|
style={{ width: "1rem", height: "1rem" }}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="form-check-label text-nowrap text-small"
|
||||||
|
htmlFor={`cat-${id}`}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1" style={{ flexBasis: "50%" }}>
|
)}
|
||||||
<p className="text-small mb-1">Categories</p>
|
|
||||||
<div className="d-flex flex-wrap">
|
|
||||||
{filteredCategories.map(({ id, name }) => (
|
|
||||||
<div className="form-check me-3 mb-1" style={{ minWidth: "calc(50% - 15px)" }} key={id}>
|
|
||||||
<input
|
|
||||||
className="form-check-input form-check-input-sm"
|
|
||||||
type="checkbox"
|
|
||||||
id={`cat-${id}`}
|
|
||||||
checked={tempSelectedCategoryIds.includes(id)}
|
|
||||||
onChange={() => handleTempCategoryChange(id)}
|
|
||||||
style={{ width: "1rem", height: "1rem" }}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label text-nowrap text-small" htmlFor={`cat-${id}`}>
|
|
||||||
{name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="d-flex justify-content-end gap-2 mt-1">
|
<div className="d-flex justify-content-end gap-2 mt-1">
|
||||||
<button
|
<button
|
||||||
className="btn btn-xs btn-secondary"
|
className="btn btn-xs btn-secondary"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
// e.stopPropagation();
|
|
||||||
clearFilter();
|
clearFilter();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -503,6 +529,7 @@ const DirectoryPageHeader = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,11 +19,17 @@ const LoginPage = () => {
|
|||||||
|
|
||||||
const loginSchema = IsLoginWithOTP
|
const loginSchema = IsLoginWithOTP
|
||||||
? z.object({
|
? z.object({
|
||||||
username: z.string().email({ message: "Valid email required" }),
|
username: z.string()
|
||||||
|
.trim()
|
||||||
|
.email({ message: "Valid email required" }),
|
||||||
})
|
})
|
||||||
: z.object({
|
: z.object({
|
||||||
username: z.string().email({ message: "Valid email required" }),
|
username: z.string()
|
||||||
password: z.string().min(1, { message: "Password required" }),
|
.trim()
|
||||||
|
.email({ message: "Valid email required" }),
|
||||||
|
password: z.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, { message: "Password required" }),
|
||||||
rememberMe: z.boolean(),
|
rememberMe: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -41,20 +47,24 @@ const LoginPage = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const username = data.username.trim();
|
||||||
|
const password = data.password?.trim();
|
||||||
|
|
||||||
if (!IsLoginWithOTP) {
|
if (!IsLoginWithOTP) {
|
||||||
const userCredential = {
|
const userCredential = {
|
||||||
username: data.username,
|
username,
|
||||||
password: data.password,
|
password,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await AuthRepository.login(userCredential);
|
const response = await AuthRepository.login(userCredential);
|
||||||
localStorage.setItem("jwtToken", response.data.token);
|
localStorage.setItem("jwtToken", response.data.token);
|
||||||
localStorage.setItem("refreshToken", response.data.refreshToken);
|
localStorage.setItem("refreshToken", response.data.refreshToken);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
navigate("/dashboard");
|
navigate("/dashboard");
|
||||||
} else {
|
} else {
|
||||||
await AuthRepository.sendOTP({ email: data.username });
|
await AuthRepository.sendOTP({ email: username });
|
||||||
showToast("OTP has been sent to your email.", "success");
|
showToast("OTP has been sent to your email.", "success");
|
||||||
localStorage.setItem("otpUsername", data.username);
|
localStorage.setItem("otpUsername", username);
|
||||||
localStorage.setItem("otpSentTime", now.toString());
|
localStorage.setItem("otpSentTime", now.toString());
|
||||||
navigate("/auth/login-otp");
|
navigate("/auth/login-otp");
|
||||||
}
|
}
|
||||||
@ -64,6 +74,7 @@ const LoginPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const otpSentTime = localStorage.getItem("otpSentTime");
|
const otpSentTime = localStorage.getItem("otpSentTime");
|
||||||
if (
|
if (
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
VIEW_ALL_EMPLOYEES,
|
VIEW_ALL_EMPLOYEES,
|
||||||
VIEW_TEAM_MEMBERS,
|
VIEW_TEAM_MEMBERS,
|
||||||
} from "../../utils/constants";
|
} from "../../utils/constants";
|
||||||
import { clearCacheKey } from "../../slices/apiDataManager";
|
import { clearCacheKey, useSelectedproject } from "../../slices/apiDataManager";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp
|
import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp
|
||||||
import {
|
import {
|
||||||
@ -38,9 +38,10 @@ import usePagination from "../../hooks/usePagination";
|
|||||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
|
|
||||||
const EmployeeList = () => {
|
const EmployeeList = () => {
|
||||||
const selectedProjectId = useSelector(
|
// const selectedProjectId = useSelector(
|
||||||
(store) => store.localVariables.projectId
|
// (store) => store.localVariables.projectId
|
||||||
);
|
// );
|
||||||
|
const selectedProjectId = useSelectedproject();
|
||||||
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -176,12 +177,10 @@ const EmployeeList = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading && Array.isArray(employees)) {
|
if (!loading && Array.isArray(employees)) {
|
||||||
const sorted = [...employees].sort((a, b) => {
|
const sorted = [...employees].sort((a, b) => {
|
||||||
const nameA = `${a.firstName || ""}${a.middleName || ""}${
|
const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || ""
|
||||||
a.lastName || ""
|
}`.toLowerCase();
|
||||||
}`.toLowerCase();
|
const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || ""
|
||||||
const nameB = `${b.firstName || ""}${b.middleName || ""}${
|
}`.toLowerCase();
|
||||||
b.lastName || ""
|
|
||||||
}`.toLowerCase();
|
|
||||||
return nameA?.localeCompare(nameB);
|
return nameA?.localeCompare(nameB);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -274,12 +273,21 @@ const EmployeeList = () => {
|
|||||||
>
|
>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
type={"delete"}
|
type={"delete"}
|
||||||
header={"Suspend Employee"}
|
header={
|
||||||
message={"Are you sure you want delete?"}
|
selectedEmpFordelete?.isActive
|
||||||
onSubmit={suspendEmployee}
|
? "Suspend Employee"
|
||||||
|
: "Reactivate Employee"
|
||||||
|
}
|
||||||
|
message={`Are you sure you want to ${selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
|
||||||
|
} this employee?`}
|
||||||
|
onSubmit={() =>
|
||||||
|
suspendEmployee({
|
||||||
|
employeeId: selectedEmpFordelete.id,
|
||||||
|
active: !selectedEmpFordelete.isActive,
|
||||||
|
})
|
||||||
|
}
|
||||||
onClose={() => setIsDeleteModalOpen(false)}
|
onClose={() => setIsDeleteModalOpen(false)}
|
||||||
loading={employeeLodaing}
|
loading={employeeLodaing}
|
||||||
paramData={selectedEmpFordelete}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -479,9 +487,7 @@ const EmployeeList = () => {
|
|||||||
aria-label="User: activate to sort column ascending"
|
aria-label="User: activate to sort column ascending"
|
||||||
aria-sort="descending"
|
aria-sort="descending"
|
||||||
>
|
>
|
||||||
<div className="text-start ms-5">
|
<div className="text-start ms-5">Designation</div>
|
||||||
Official Designation
|
|
||||||
</div>
|
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<th
|
<th
|
||||||
@ -505,9 +511,8 @@ const EmployeeList = () => {
|
|||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
className={`sorting_disabled ${
|
className={`sorting_disabled ${!Manage_Employee && "d-none"
|
||||||
!Manage_Employee && "d-none"
|
}`}
|
||||||
}`}
|
|
||||||
rowSpan="1"
|
rowSpan="1"
|
||||||
colSpan="1"
|
colSpan="1"
|
||||||
style={{ width: "50px" }}
|
style={{ width: "50px" }}
|
||||||
@ -527,9 +532,9 @@ const EmployeeList = () => {
|
|||||||
)}
|
)}
|
||||||
{/* Conditional messages for no data or no search results */}
|
{/* Conditional messages for no data or no search results */}
|
||||||
{!loading &&
|
{!loading &&
|
||||||
displayData?.length === 0 &&
|
displayData?.length === 0 &&
|
||||||
searchText &&
|
searchText &&
|
||||||
!showAllEmployees ? (
|
!showAllEmployees ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={8}>
|
<td colSpan={8}>
|
||||||
<small className="muted">
|
<small className="muted">
|
||||||
@ -539,8 +544,8 @@ const EmployeeList = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
) : null}
|
) : null}
|
||||||
{!loading &&
|
{!loading &&
|
||||||
displayData?.length === 0 &&
|
displayData?.length === 0 &&
|
||||||
(!searchText || showAllEmployees) ? (
|
(!searchText || showAllEmployees) ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={8}
|
colSpan={8}
|
||||||
@ -632,47 +637,56 @@ const EmployeeList = () => {
|
|||||||
<i className="bx bx-dots-vertical-rounded bx-md"></i>
|
<i className="bx bx-dots-vertical-rounded bx-md"></i>
|
||||||
</button>
|
</button>
|
||||||
<div className="dropdown-menu dropdown-menu-end">
|
<div className="dropdown-menu dropdown-menu-end">
|
||||||
|
{/* View always visible */}
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() => navigate(`/employee/${item.id}`)}
|
||||||
navigate(`/employee/${item.id}`)
|
|
||||||
}
|
|
||||||
className="dropdown-item py-1"
|
className="dropdown-item py-1"
|
||||||
>
|
>
|
||||||
<i className="bx bx-detail bx-sm"></i> View
|
<i className="bx bx-detail bx-sm"></i> View
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
className="dropdown-item py-1"
|
{/* If ACTIVE employee */}
|
||||||
onClick={() => {
|
{item.isActive && (
|
||||||
handleEmployeeModel(item.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i className="bx bx-edit bx-sm"></i> Edit
|
|
||||||
</button>
|
|
||||||
{!item.isSystem && (
|
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="dropdown-item py-1"
|
className="dropdown-item py-1"
|
||||||
onClick={() =>
|
onClick={() => handleEmployeeModel(item.id)}
|
||||||
handleOpenDelete(item.id)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<i className="bx bx-task-x bx-sm"></i>{" "}
|
<i className="bx bx-edit bx-sm"></i> Edit
|
||||||
Suspend
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* Suspend only when active */}
|
||||||
|
{item.isActive && (
|
||||||
|
<button
|
||||||
|
className="dropdown-item py-1"
|
||||||
|
onClick={() => handleOpenDelete(item)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-task-x bx-sm"></i> Suspend
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="dropdown-item py-1"
|
className="dropdown-item py-1"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#managerole-modal"
|
data-bs-target="#managerole-modal"
|
||||||
onClick={() =>
|
onClick={() => setEmpForManageRole(item.id)}
|
||||||
setEmpForManageRole(item.id)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<i className="bx bx-cog bx-sm"></i>{" "}
|
<i className="bx bx-cog bx-sm"></i> Manage Role
|
||||||
Manage Role
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* If INACTIVE employee AND inactive toggle is ON */}
|
||||||
|
{!item.isActive && showInactive && (
|
||||||
|
<button
|
||||||
|
className="dropdown-item py-1"
|
||||||
|
onClick={() => handleOpenDelete(item)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-refresh bx-sm me-1"></i> Re-activate
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -689,9 +703,8 @@ const EmployeeList = () => {
|
|||||||
<nav aria-label="Page">
|
<nav aria-label="Page">
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
<li
|
<li
|
||||||
className={`page-item ${
|
className={`page-item ${currentPage === 1 ? "disabled" : ""
|
||||||
currentPage === 1 ? "disabled" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link btn-xs"
|
className="page-link btn-xs"
|
||||||
@ -704,9 +717,8 @@ const EmployeeList = () => {
|
|||||||
{[...Array(totalPages)]?.map((_, index) => (
|
{[...Array(totalPages)]?.map((_, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className={`page-item ${
|
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
||||||
currentPage === index + 1 ? "active" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
@ -718,9 +730,8 @@ const EmployeeList = () => {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<li
|
<li
|
||||||
className={`page-item ${
|
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||||
currentPage === totalPages ? "disabled" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
|
@ -10,7 +10,7 @@ const EmployeeRepository = {
|
|||||||
updateEmployee: (id, data) => api.put(`/users/${id}`, data),
|
updateEmployee: (id, data) => api.put(`/users/${id}`, data),
|
||||||
// deleteEmployee: ( id ) => api.delete( `/users/${ id }` ),
|
// deleteEmployee: ( id ) => api.delete( `/users/${ id }` ),
|
||||||
getEmployeeProfile: (id) => api.get(`/api/employee/profile/get/${id}`),
|
getEmployeeProfile: (id) => api.get(`/api/employee/profile/get/${id}`),
|
||||||
deleteEmployee: (id) => api.delete(`/api/employee/${id}`),
|
deleteEmployee: (id,active) => api.delete(`/api/employee/${id}?active=${active}`),
|
||||||
getEmployeeName: (projectId, search) => {
|
getEmployeeName: (projectId, search) => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user