Compare commits
No commits in common. "3559b0a01c50068fbf90f9f838743ae6e6d92fc3" and "edebc2e471d31adb973b3d25b72ea4f1cd91b480" have entirely different histories.
3559b0a01c
...
edebc2e471
@ -11,7 +11,6 @@ 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";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import Pagination from "../common/Pagination";
|
|
||||||
|
|
||||||
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, includeInactive, date }) => {
|
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, includeInactive, date }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@ -109,8 +108,6 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
|
|||||||
return () => eventBus.off("employee", employeeHandler);
|
return () => eventBus.off("employee", employeeHandler);
|
||||||
}, [employeeHandler]);
|
}, [employeeHandler]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@ -141,7 +138,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
|
|||||||
<tr className="border-top-1">
|
<tr className="border-top-1">
|
||||||
<th colSpan={2}>Name</th>
|
<th colSpan={2}>Name</th>
|
||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
{/* <th>Organization</th> */}
|
<th>Organization</th>
|
||||||
<th>
|
<th>
|
||||||
<i className="bx bxs-down-arrow-alt text-success"></i>
|
<i className="bx bxs-down-arrow-alt text-success"></i>
|
||||||
Check-In
|
Check-In
|
||||||
@ -190,7 +187,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>{item.jobRoleName}</td>
|
<td>{item.jobRoleName}</td>
|
||||||
{/* <td>{item.organizationName || "--"}</td> */}
|
<td>{item.organizationName || "--"}</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{item.checkInTime
|
{item.checkInTime
|
||||||
@ -229,12 +226,46 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
|
|||||||
|
|
||||||
|
|
||||||
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
|
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
|
||||||
|
<nav aria-label="Page ">
|
||||||
<Pagination
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
currentPage={currentPage}
|
<li
|
||||||
totalPages={totalPages}
|
className={`page-item ${currentPage === 1 ? "disabled" : ""
|
||||||
onPageChange={paginate}
|
}`}
|
||||||
/>
|
>
|
||||||
|
<button
|
||||||
|
className="page-link btn-xs"
|
||||||
|
onClick={() => paginate(currentPage - 1)}
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{[...Array(totalPages)].map((_, index) => (
|
||||||
|
<li
|
||||||
|
key={index}
|
||||||
|
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="page-link "
|
||||||
|
onClick={() => paginate(index + 1)}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
<li
|
||||||
|
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="page-link "
|
||||||
|
onClick={() => paginate(currentPage + 1)}
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import AttendanceRepository from "../../repositories/AttendanceRepository";
|
|||||||
import { useAttendancesLogs } from "../../hooks/useAttendance";
|
import { useAttendancesLogs } from "../../hooks/useAttendance";
|
||||||
import { queryClient } from "../../layouts/AuthLayout";
|
import { queryClient } from "../../layouts/AuthLayout";
|
||||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||||
import Pagination from "../common/Pagination";
|
|
||||||
|
|
||||||
const usePagination = (data, itemsPerPage) => {
|
const usePagination = (data, itemsPerPage) => {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
@ -85,50 +84,56 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
|
|||||||
dateRange.endDate,
|
dateRange.endDate,
|
||||||
organizationId
|
organizationId
|
||||||
);
|
);
|
||||||
const filtering = useCallback((dataToFilter) => {
|
const filtering = (data) => {
|
||||||
const filteredData = showPending
|
const filteredData = showPending
|
||||||
? dataToFilter.filter((item) => item.checkOutTime === null)
|
? data.filter((item) => item.checkOutTime === null)
|
||||||
: dataToFilter;
|
: data;
|
||||||
|
|
||||||
const group1 = filteredData
|
const group1 = filteredData
|
||||||
.filter((d) => d.activity === 1 && isSameDay(d.checkInTime))
|
.filter((d) => d.activity === 1 && isSameDay(d.checkInTime))
|
||||||
.sort(sortByName);
|
.sort(sortByName);
|
||||||
const group2 = filteredData
|
const group2 = filteredData
|
||||||
.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime))
|
.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime))
|
||||||
.sort(sortByName);
|
.sort(sortByName);
|
||||||
const group3 = filteredData
|
const group3 = filteredData
|
||||||
.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime))
|
.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime))
|
||||||
.sort(sortByName);
|
.sort(sortByName);
|
||||||
const group4 = filteredData.filter(
|
const group4 = filteredData.filter(
|
||||||
(d) => d.activity === 4 && isBeforeToday(d.checkOutTime)
|
(d) => d.activity === 4 && isBeforeToday(d.checkOutTime)
|
||||||
);
|
);
|
||||||
const group5 = filteredData
|
const group5 = filteredData
|
||||||
.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime))
|
.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime))
|
||||||
.sort(sortByName);
|
.sort(sortByName);
|
||||||
const group6 = filteredData
|
const group6 = filteredData
|
||||||
.filter((d) => d.activity === 5)
|
.filter((d) => d.activity === 5)
|
||||||
.sort(sortByName);
|
.sort(sortByName);
|
||||||
|
|
||||||
const sortedList = [...group1, ...group2, ...group3, ...group4, ...group5, ...group6];
|
const sortedList = [
|
||||||
|
...group1,
|
||||||
|
...group2,
|
||||||
|
...group3,
|
||||||
|
...group4,
|
||||||
|
...group5,
|
||||||
|
...group6,
|
||||||
|
];
|
||||||
|
|
||||||
// Group by date
|
// Group by date
|
||||||
const groupedByDate = sortedList.reduce((acc, item) => {
|
const groupedByDate = sortedList.reduce((acc, item) => {
|
||||||
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
|
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
|
||||||
if (date) {
|
if (date) {
|
||||||
acc[date] = acc[date] || [];
|
acc[date] = acc[date] || [];
|
||||||
acc[date].push(item);
|
acc[date].push(item);
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const sortedDates = Object.keys(groupedByDate).sort(
|
const sortedDates = Object.keys(groupedByDate).sort(
|
||||||
(a, b) => new Date(b) - new Date(a)
|
(a, b) => new Date(b) - new Date(a)
|
||||||
);
|
);
|
||||||
|
|
||||||
const finalData = sortedDates.flatMap((date) => groupedByDate[date]);
|
|
||||||
setProcessedData(finalData);
|
|
||||||
}, [showPending]);
|
|
||||||
|
|
||||||
|
const finalData = sortedDates.flatMap((date) => groupedByDate[date]);
|
||||||
|
setProcessedData(finalData);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
filtering(data);
|
filtering(data);
|
||||||
@ -280,7 +285,7 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
|
|||||||
Name
|
Name
|
||||||
</th>
|
</th>
|
||||||
<th className="border-top-1">Date</th>
|
<th className="border-top-1">Date</th>
|
||||||
{/* <th>Organization</th> */}
|
<th>Organization</th>
|
||||||
<th>
|
<th>
|
||||||
<i className="bx bxs-down-arrow-alt text-success"></i> Check-In
|
<i className="bx bxs-down-arrow-alt text-success"></i> Check-In
|
||||||
</th>
|
</th>
|
||||||
@ -339,7 +344,7 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
|
|||||||
attendance.checkInTime || attendance.checkOutTime
|
attendance.checkInTime || attendance.checkOutTime
|
||||||
).format("DD-MMM-YYYY")}
|
).format("DD-MMM-YYYY")}
|
||||||
</td>
|
</td>
|
||||||
{/* <td>{attendance.organizationName || "--"}</td> */}
|
<td>{attendance.organizationName || "--"}</td>
|
||||||
<td>{convertShortTime(attendance.checkInTime)}</td>
|
<td>{convertShortTime(attendance.checkInTime)}</td>
|
||||||
<td>
|
<td>
|
||||||
{attendance.checkOutTime
|
{attendance.checkOutTime
|
||||||
@ -373,11 +378,45 @@ const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{filteredSearchData.length > ITEMS_PER_PAGE && (
|
{filteredSearchData.length > ITEMS_PER_PAGE && (
|
||||||
<Pagination
|
<nav aria-label="Page ">
|
||||||
currentPage={currentPage}
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
totalPages={totalPages}
|
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
||||||
onPageChange={paginate}
|
<button
|
||||||
/>
|
className="page-link btn-xs"
|
||||||
|
onClick={() => paginate(currentPage - 1)}
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{Array.from({ length: totalPages }, (_, i) => i + 1).map(
|
||||||
|
(pageNumber) => (
|
||||||
|
<li
|
||||||
|
key={pageNumber}
|
||||||
|
className={`page-item ${currentPage === pageNumber ? "active" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="page-link"
|
||||||
|
onClick={() => paginate(pageNumber)}
|
||||||
|
>
|
||||||
|
{pageNumber}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<li
|
||||||
|
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="page-link"
|
||||||
|
onClick={() => paginate(currentPage + 1)}
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@ -9,7 +9,6 @@ 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";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
|
||||||
|
|
||||||
const createSchema = (modeldata) => {
|
const createSchema = (modeldata) => {
|
||||||
return z
|
return z
|
||||||
@ -20,36 +19,31 @@ const createSchema = (modeldata) => {
|
|||||||
.max(200, "Description should be less than 200 characters")
|
.max(200, "Description should be less than 200 characters")
|
||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.refine(
|
.refine((data) => {
|
||||||
(data) => {
|
if (modeldata?.checkInTime && !modeldata?.checkOutTime) {
|
||||||
if (modeldata?.checkInTime && !modeldata?.checkOutTime) {
|
const checkIn = new Date(modeldata.checkInTime);
|
||||||
const checkIn = new Date(modeldata.checkInTime);
|
const [time, modifier] = data.markTime.split(" ");
|
||||||
const [time, modifier] = data.markTime.split(" ");
|
const [hourStr, minuteStr] = time.split(":");
|
||||||
const [hourStr, minuteStr] = time.split(":");
|
let hour = parseInt(hourStr, 10);
|
||||||
let hour = parseInt(hourStr, 10);
|
const minute = parseInt(minuteStr, 10);
|
||||||
const minute = parseInt(minuteStr, 10);
|
|
||||||
|
|
||||||
if (modifier === "PM" && hour !== 12) hour += 12;
|
if (modifier === "PM" && hour !== 12) hour += 12;
|
||||||
if (modifier === "AM" && hour === 12) hour = 0;
|
if (modifier === "AM" && hour === 12) hour = 0;
|
||||||
|
|
||||||
const checkOut = new Date(checkIn);
|
const checkOut = new Date(checkIn);
|
||||||
checkOut.setHours(hour, minute, 0, 0);
|
checkOut.setHours(hour, minute, 0, 0);
|
||||||
|
|
||||||
return checkOut >= checkIn;
|
return checkOut >= checkIn;
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
message: "Checkout time must be later than check-in time",
|
|
||||||
path: ["markTime"],
|
|
||||||
}
|
}
|
||||||
);
|
return true;
|
||||||
|
}, {
|
||||||
|
message: "Checkout time must be later than check-in time",
|
||||||
|
path: ["markTime"],
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
|
const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
|
||||||
const [currentProject, setCurrentProject] = useState(null);
|
|
||||||
const projectId = useSelectedProject();
|
const projectId = useSelectedProject();
|
||||||
const { projectNames, loading } = useProjectName();
|
|
||||||
const { mutate: MarkAttendance } = useMarkAttendance();
|
const { mutate: MarkAttendance } = useMarkAttendance();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const coords = usePositionTracker();
|
const coords = usePositionTracker();
|
||||||
@ -89,7 +83,7 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
|
|||||||
Id: modeldata?.id || null,
|
Id: modeldata?.id || null,
|
||||||
comment: data.description,
|
comment: data.description,
|
||||||
employeeID: modeldata.employeeId,
|
employeeID: modeldata.employeeId,
|
||||||
projectId: projectId,
|
// projectId: projectId,
|
||||||
date: new Date().toISOString(),
|
date: new Date().toISOString(),
|
||||||
markTime: data.markTime,
|
markTime: data.markTime,
|
||||||
latitude: coords.latitude.toString(),
|
latitude: coords.latitude.toString(),
|
||||||
@ -101,24 +95,17 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
|
|||||||
closeModal();
|
closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (projectId && projectNames) {
|
|
||||||
setCurrentProject(
|
|
||||||
projectNames?.find((project) => project.id === projectId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [projectNames, projectId, loading]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="col-12 d-flex justify-content-center">
|
<div className="col-12 d-flex justify-content-center">
|
||||||
<label className="fs-5 text-dark text-center">
|
<label className="fs-5 text-dark text-center">
|
||||||
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
||||||
? `Check out for ${currentProject?.name}`
|
? "Check-out :"
|
||||||
: `Check In for ${currentProject?.name}`}
|
: "Check-in :"}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="col-6 col-md-6 text-start">
|
<div className="col-6 col-md-6 text-start">
|
||||||
<label className="form-label" htmlFor="checkInDate">
|
<label className="form-label" htmlFor="checkInDate">
|
||||||
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import usePagination from "../../hooks/usePagination";
|
|||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager";
|
import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import Pagination from "../common/Pagination";
|
|
||||||
|
|
||||||
const Regularization = ({ handleRequest, searchTerm,projectId, organizationId, IncludeInActive }) => {
|
const Regularization = ({ handleRequest, searchTerm,projectId, organizationId, IncludeInActive }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@ -129,7 +128,7 @@ const Regularization = ({ handleRequest, searchTerm,projectId, organizationId, I
|
|||||||
<tr>
|
<tr>
|
||||||
<th colSpan={2}>Name</th>
|
<th colSpan={2}>Name</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
{/* <th>Organization</th> */}
|
<th>Organization</th>
|
||||||
<th>
|
<th>
|
||||||
<i className="bx bxs-down-arrow-alt text-success"></i>Check-In
|
<i className="bx bxs-down-arrow-alt text-success"></i>Check-In
|
||||||
</th>
|
</th>
|
||||||
@ -159,7 +158,7 @@ const Regularization = ({ handleRequest, searchTerm,projectId, organizationId, I
|
|||||||
</td>
|
</td>
|
||||||
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
|
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
|
||||||
|
|
||||||
{/* <td>{att.organizationName || "--"}</td> */}
|
<td>{att.organizationName || "--"}</td>
|
||||||
|
|
||||||
<td>{convertShortTime(att.checkInTime)}</td>
|
<td>{convertShortTime(att.checkInTime)}</td>
|
||||||
<td>
|
<td>
|
||||||
@ -190,11 +189,43 @@ const Regularization = ({ handleRequest, searchTerm,projectId, organizationId, I
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!loading && totalPages > 1 && (
|
{!loading && totalPages > 1 && (
|
||||||
<Pagination
|
<nav aria-label="Page ">
|
||||||
currentPage={currentPage}
|
<ul className="pagination pagination-sm justify-content-end py-1 mt-3">
|
||||||
totalPages={totalPages}
|
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
||||||
onPageChange={paginate}
|
<button
|
||||||
/>
|
className="page-link btn-xs"
|
||||||
|
onClick={() => paginate(currentPage - 1)}
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{[...Array(totalPages)].map((_, index) => (
|
||||||
|
<li
|
||||||
|
key={index}
|
||||||
|
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="page-link "
|
||||||
|
onClick={() => paginate(index + 1)}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
<li
|
||||||
|
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="page-link "
|
||||||
|
onClick={() => paginate(currentPage + 1)}
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import ReactApexChart from "react-apexcharts";
|
|||||||
import { useAttendanceOverviewData } from "../../hooks/useDashboard_Data";
|
import { useAttendanceOverviewData } from "../../hooks/useDashboard_Data";
|
||||||
import flatColors from "../Charts/flatColor";
|
import flatColors from "../Charts/flatColor";
|
||||||
import ChartSkeleton from "../Charts/Skelton";
|
import ChartSkeleton from "../Charts/Skelton";
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
|
||||||
|
|
||||||
const formatDate = (dateStr) => {
|
const formatDate = (dateStr) => {
|
||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
@ -17,17 +16,14 @@ const formatDate = (dateStr) => {
|
|||||||
const AttendanceOverview = () => {
|
const AttendanceOverview = () => {
|
||||||
const [dayRange, setDayRange] = useState(7);
|
const [dayRange, setDayRange] = useState(7);
|
||||||
const [view, setView] = useState("chart");
|
const [view, setView] = useState("chart");
|
||||||
const selectedProject = useSelectedProject()
|
|
||||||
const { data: attendanceOverviewData, isLoading, isError, error } = useAttendanceOverviewData(
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
selectedProject,
|
const { attendanceOverviewData, loading, error } = useAttendanceOverviewData(
|
||||||
|
projectId,
|
||||||
dayRange
|
dayRange
|
||||||
);
|
);
|
||||||
|
|
||||||
const { tableData, roles, dates } = useMemo(() => {
|
const { tableData, roles, dates } = useMemo(() => {
|
||||||
if (!attendanceOverviewData || attendanceOverviewData.length === 0) {
|
|
||||||
return { tableData: [], roles: [], dates: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
|
|
||||||
attendanceOverviewData.forEach((entry) => {
|
attendanceOverviewData.forEach((entry) => {
|
||||||
@ -40,8 +36,7 @@ const AttendanceOverview = () => {
|
|||||||
...new Set(attendanceOverviewData.map((e) => e.role.trim())),
|
...new Set(attendanceOverviewData.map((e) => e.role.trim())),
|
||||||
];
|
];
|
||||||
const sortedDates = [...map.keys()];
|
const sortedDates = [...map.keys()];
|
||||||
|
const data = sortedDates.map((date) => {
|
||||||
const tableData = sortedDates.map((date) => {
|
|
||||||
const row = { date };
|
const row = { date };
|
||||||
uniqueRoles.forEach((role) => {
|
uniqueRoles.forEach((role) => {
|
||||||
row[role] = map.get(date)?.[role] ?? 0;
|
row[role] = map.get(date)?.[role] ?? 0;
|
||||||
@ -49,8 +44,12 @@ const AttendanceOverview = () => {
|
|||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { tableData, roles: uniqueRoles, dates: sortedDates };
|
return {
|
||||||
}, [attendanceOverviewData,isLoading,selectedProject,dayRange]);
|
tableData: data,
|
||||||
|
roles: uniqueRoles,
|
||||||
|
dates: sortedDates,
|
||||||
|
};
|
||||||
|
}, [attendanceOverviewData]);
|
||||||
|
|
||||||
const chartSeries = roles.map((role) => ({
|
const chartSeries = roles.map((role) => ({
|
||||||
name: role,
|
name: role,
|
||||||
@ -64,21 +63,41 @@ const AttendanceOverview = () => {
|
|||||||
height: 400,
|
height: 400,
|
||||||
toolbar: { show: false },
|
toolbar: { show: false },
|
||||||
},
|
},
|
||||||
plotOptions: { bar: { borderRadius: 2, columnWidth: "60%" } },
|
plotOptions: {
|
||||||
xaxis: { categories: tableData.map((row) => row.date) },
|
bar: {
|
||||||
|
borderRadius: 2,
|
||||||
|
columnWidth: "60%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: tableData.map((row) => row.date),
|
||||||
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
show: true,
|
show: true,
|
||||||
axisBorder: { show: true, color: "#78909C" },
|
axisBorder: {
|
||||||
axisTicks: { show: true, color: "#78909C", width: 6 },
|
show: true,
|
||||||
|
color: "#78909C",
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
},
|
||||||
|
axisTicks: {
|
||||||
|
show: true,
|
||||||
|
borderType: "solid",
|
||||||
|
color: "#78909C",
|
||||||
|
width: 6,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: "bottom",
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
opacity: 1,
|
||||||
},
|
},
|
||||||
legend: { position: "bottom" },
|
|
||||||
fill: { opacity: 1 },
|
|
||||||
colors: roles.map((_, i) => flatColors[i % flatColors.length]),
|
colors: roles.map((_, i) => flatColors[i % flatColors.length]),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) return <div>Loading...</div>;
|
|
||||||
if (isError) return <p className="text-danger">{error.message}</p>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-4 rounded shadow d-flex flex-column">
|
<div className="bg-white p-4 rounded shadow d-flex flex-column">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@ -98,14 +117,18 @@ const AttendanceOverview = () => {
|
|||||||
<option value={30}>Last 30 Days</option>
|
<option value={30}>Last 30 Days</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm p-1 ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`}
|
className={`btn btn-sm p-1 ${
|
||||||
|
view === "chart" ? "btn-primary" : "btn-outline-primary"
|
||||||
|
}`}
|
||||||
onClick={() => setView("chart")}
|
onClick={() => setView("chart")}
|
||||||
title="Chart View"
|
title="Chart View"
|
||||||
>
|
>
|
||||||
<i className="bx bx-bar-chart-alt-2"></i>
|
<i className="bx bx-bar-chart-alt-2"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm p-1 ${view === "table" ? "btn-primary" : "btn-outline-primary"}`}
|
className={`btn btn-sm p-1 ${
|
||||||
|
view === "table" ? "btn-primary" : "btn-outline-primary"
|
||||||
|
}`}
|
||||||
onClick={() => setView("table")}
|
onClick={() => setView("table")}
|
||||||
title="Table View"
|
title="Table View"
|
||||||
>
|
>
|
||||||
@ -116,30 +139,57 @@ const AttendanceOverview = () => {
|
|||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-grow-1 d-flex align-items-center justify-content-center">
|
<div className="flex-grow-1 d-flex align-items-center justify-content-center">
|
||||||
{view === "chart" ? (
|
{loading ? (
|
||||||
|
<ChartSkeleton />
|
||||||
|
) : error ? (
|
||||||
|
<p className="text-danger">{error}</p>
|
||||||
|
) : view === "chart" ? (
|
||||||
<div className="w-100">
|
<div className="w-100">
|
||||||
<ReactApexChart options={chartOptions} series={chartSeries} type="bar" height={400} />
|
<ReactApexChart
|
||||||
|
options={chartOptions}
|
||||||
|
series={chartSeries}
|
||||||
|
type="bar"
|
||||||
|
height={400}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="table-responsive w-100" style={{ maxHeight: "350px", overflowY: "auto" }}>
|
<div
|
||||||
|
className="table-responsive w-100"
|
||||||
|
style={{ maxHeight: "350px", overflowY: "auto" }}
|
||||||
|
>
|
||||||
<table className="table table-bordered table-sm text-start align-middle mb-0">
|
<table className="table table-bordered table-sm text-start align-middle mb-0">
|
||||||
<thead className="table-light" style={{ position: "sticky", top: 0, zIndex: 1 }}>
|
<thead
|
||||||
|
className="table-light"
|
||||||
|
style={{ position: "sticky", top: 0, zIndex: 1 }}
|
||||||
|
>
|
||||||
<tr>
|
<tr>
|
||||||
<th style={{ background: "#f8f9fa", textTransform: "none" }}>Role</th>
|
<th style={{ background: "#f8f9fa", textTransform: "none" }}>
|
||||||
|
Role
|
||||||
|
</th>
|
||||||
{dates.map((date, idx) => (
|
{dates.map((date, idx) => (
|
||||||
<th key={idx} style={{ background: "#f8f9fa", textTransform: "none" }}>
|
<th
|
||||||
|
key={idx}
|
||||||
|
style={{ background: "#f8f9fa", textTransform: "none" }}
|
||||||
|
>
|
||||||
{date}
|
{date}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{roles.map((role) => (
|
{roles.map((role) => (
|
||||||
<tr key={role}>
|
<tr key={role}>
|
||||||
<td>{role}</td>
|
<td>{role}</td>
|
||||||
{tableData.map((row, idx) => {
|
{tableData.map((row, idx) => {
|
||||||
const value = row[role];
|
const value = row[role];
|
||||||
return <td key={idx} style={value > 0 ? { backgroundColor: "#d5d5d5" } : {}}>{value}</td>;
|
const cellStyle =
|
||||||
|
value > 0 ? { backgroundColor: "#d5d5d5" } : {};
|
||||||
|
return (
|
||||||
|
<td key={idx} style={cellStyle}>
|
||||||
|
{value}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
// import {
|
// import {
|
||||||
// useDashboardProjectsCardData,
|
// useDashboardProjectsCardData,
|
||||||
// useDashboardTeamsCardData,
|
// useDashboardTeamsCardData,
|
||||||
@ -17,10 +17,9 @@ import AttendanceOverview from "./AttendanceChart";
|
|||||||
import ExpenseChartDesign2 from "./ExpenseChartDesign2";
|
import ExpenseChartDesign2 from "./ExpenseChartDesign2";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
|
// const { projectsCardData } = useDashboardProjectsCardData();
|
||||||
// const { projectsCardData } = useDashboardProjectsCardData();
|
// const { teamsCardData } = useDashboardTeamsCardData();
|
||||||
// const { teamsCardData } = useDashboardTeamsCardData();
|
// const { tasksCardData } = useDashboardTasksCardData();
|
||||||
// const { tasksCardData } = useDashboardTasksCardData();
|
|
||||||
|
|
||||||
// Get the selected project ID from Redux store
|
// Get the selected project ID from Redux store
|
||||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
|
|||||||
@ -86,8 +86,6 @@ const ListViewContact = ({ data, Pagination, isLoading }) => {
|
|||||||
ActiveInActive({ contactId: contactId, contactStatus: !showActive });
|
ActiveInActive({ contactId: contactId, contactStatus: !showActive });
|
||||||
};
|
};
|
||||||
|
|
||||||
if(isLoading) return <Loader/>
|
|
||||||
if(!data|| data.length === 0)return <div className="text-center py-12">No Contact Found</div>
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
@ -105,7 +103,11 @@ const ListViewContact = ({ data, Pagination, isLoading }) => {
|
|||||||
className="card-datatable table-responsive"
|
className="card-datatable table-responsive"
|
||||||
id="horizontal-example"
|
id="horizontal-example"
|
||||||
>
|
>
|
||||||
|
{isLoading && (
|
||||||
|
<div>
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{data && (
|
{data && (
|
||||||
<div className="dataTables_wrapper no-footer mx-5 pb-2">
|
<div className="dataTables_wrapper no-footer mx-5 pb-2">
|
||||||
<table className="table dataTable text-nowrap">
|
<table className="table dataTable text-nowrap">
|
||||||
@ -122,7 +124,7 @@ const ListViewContact = ({ data, Pagination, isLoading }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{Array.isArray(data) && data.length > 0 && (
|
{Array.isArray(data) && data.length > 0 ? (
|
||||||
data.map((row, i) => (
|
data.map((row, i) => (
|
||||||
<tr
|
<tr
|
||||||
key={i}
|
key={i}
|
||||||
@ -182,7 +184,16 @@ const ListViewContact = ({ data, Pagination, isLoading }) => {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
) }
|
) : (
|
||||||
|
<tr style={{ height: "200px" }}>
|
||||||
|
<td
|
||||||
|
colSpan={contactList.length + 1}
|
||||||
|
className="text-center align-middle border-0"
|
||||||
|
>
|
||||||
|
No contacts found
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{Pagination && (
|
{Pagination && (
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import Label from "../common/Label";
|
|||||||
const ManageContact = ({ contactId, closeModal }) => {
|
const ManageContact = ({ contactId, closeModal }) => {
|
||||||
// fetch master data
|
// fetch master data
|
||||||
const { buckets, loading: bucketsLoaging } = useBuckets();
|
const { buckets, loading: bucketsLoaging } = useBuckets();
|
||||||
const { data: projects, loading: projectLoading } = useProjects();
|
const { data:projects, loading: projectLoading } = useProjects();
|
||||||
const { contactCategory, loading: contactCategoryLoading } =
|
const { contactCategory, loading: contactCategoryLoading } =
|
||||||
useContactCategory();
|
useContactCategory();
|
||||||
const { organizationList } = useOrganization();
|
const { organizationList } = useOrganization();
|
||||||
@ -205,14 +205,13 @@ const ManageContact = ({ contactId, closeModal }) => {
|
|||||||
<Label htmlFor={"organization"} required>
|
<Label htmlFor={"organization"} required>
|
||||||
Organization
|
Organization
|
||||||
</Label>
|
</Label>
|
||||||
<InputSuggestions
|
<InputSuggestions
|
||||||
organizationList={organizationList}
|
organizationList={organizationList}
|
||||||
value={watch("organization") || ""}
|
value={watch("organization") || ""}
|
||||||
onChange={(val) =>
|
onChange={(val) => setValue("organization", val, { shouldValidate: true })}
|
||||||
setValue("organization", val, { shouldValidate: true })
|
error={errors.organization?.message}
|
||||||
}
|
/>
|
||||||
error={errors.organization?.message}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -395,7 +394,6 @@ const ManageContact = ({ contactId, closeModal }) => {
|
|||||||
labelKey="name"
|
labelKey="name"
|
||||||
valueKey="id"
|
valueKey="id"
|
||||||
IsLoading={projectLoading}
|
IsLoading={projectLoading}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
{errors.projectIds && (
|
{errors.projectIds && (
|
||||||
<small className="danger-text">{errors.projectIds.message}</small>
|
<small className="danger-text">{errors.projectIds.message}</small>
|
||||||
@ -410,7 +408,6 @@ const ManageContact = ({ contactId, closeModal }) => {
|
|||||||
label="Tags"
|
label="Tags"
|
||||||
options={contactTags}
|
options={contactTags}
|
||||||
isRequired={true}
|
isRequired={true}
|
||||||
require
|
|
||||||
/>
|
/>
|
||||||
{errors.tags && (
|
{errors.tags && (
|
||||||
<small className="danger-text">{errors.tags.message}</small>
|
<small className="danger-text">{errors.tags.message}</small>
|
||||||
@ -420,7 +417,7 @@ const ManageContact = ({ contactId, closeModal }) => {
|
|||||||
{/* Buckets */}
|
{/* Buckets */}
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-12 mt-1 text-start">
|
<div className="col-md-12 mt-1 text-start">
|
||||||
<Label required>Select Bucket</Label>
|
<label className="form-label ">Select Bucket</label>
|
||||||
<ul className="d-flex flex-wrap px-1 list-unstyled mb-0">
|
<ul className="d-flex flex-wrap px-1 list-unstyled mb-0">
|
||||||
{bucketsLoaging && <p>Loading...</p>}
|
{bucketsLoaging && <p>Loading...</p>}
|
||||||
{buckets?.map((item) => (
|
{buckets?.map((item) => (
|
||||||
@ -453,7 +450,7 @@ const ManageContact = ({ contactId, closeModal }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Address + Description */}
|
{/* Address + Description */}
|
||||||
<div className="col-12 text-start mb-2">
|
<div className="col-12 text-start">
|
||||||
<label className="form-label">Address</label>
|
<label className="form-label">Address</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
@ -462,7 +459,7 @@ const ManageContact = ({ contactId, closeModal }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 text-start">
|
<div className="col-12 text-start">
|
||||||
<Label required>Description</Label>
|
<label className="form-label">Description</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
rows="2"
|
rows="2"
|
||||||
@ -482,13 +479,10 @@ const ManageContact = ({ contactId, closeModal }) => {
|
|||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className="btn btn-sm btn-primary" type="submit" disabled={isPending}>
|
||||||
className="btn btn-sm btn-primary"
|
|
||||||
type="submit"
|
|
||||||
disabled={isPending}
|
|
||||||
>
|
|
||||||
{isPending ? "Please Wait..." : "Submit"}
|
{isPending ? "Please Wait..." : "Submit"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|||||||
@ -189,12 +189,12 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
|||||||
<div className="d-flex justify-content-end py-3 gap-2">
|
<div className="d-flex justify-content-end py-3 gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-label-secondary btn-sm"
|
className="btn btn-label-secondary btn-xs"
|
||||||
onClick={onClear}
|
onClick={onClear}
|
||||||
>
|
>
|
||||||
Clear
|
Clear
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="btn btn-primary btn-sm">
|
<button type="submit" className="btn btn-primary btn-xs">
|
||||||
Apply
|
Apply
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -17,56 +17,54 @@ const SkeletonCell = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const DocumentTableSkeleton = ({ rows = 10 }) => {
|
export const DocumentTableSkeleton = ({ rows = 5 }) => {
|
||||||
return (
|
return (
|
||||||
<table className="card-body table border-top dataTable no-footer dtr-column text-nowrap">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="text-start">Name</th>
|
|
||||||
<th className="text-start">Document Type</th>
|
|
||||||
<th className="text-start">Uploaded By</th>
|
|
||||||
<th className="text-center">Uploaded on</th>
|
|
||||||
<th className="text-center">Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
<table className="card-body table border-top dataTable no-footer dtr-column text-nowrap">
|
||||||
{[...Array(rows)].map((_, idx) => (
|
<thead>
|
||||||
<tr key={idx} className={idx % 2 === 0 ? "odd" : "even"}>
|
<tr>
|
||||||
{/* Name */}
|
<th className="text-start">Name</th>
|
||||||
<td className="text-start">
|
<th className="text-start">Document Type</th>
|
||||||
<SkeletonCell width="120px" height={16} />
|
<th className="text-start">Uploaded By</th>
|
||||||
</td>
|
<th className="text-center">Uploaded on</th>
|
||||||
|
<th className="text-center">Status</th>
|
||||||
{/* Document Type */}
|
|
||||||
<td className="text-start">
|
|
||||||
<SkeletonCell width="100px" height={16} />
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{/* Uploaded By (Avatar + Name) */}
|
|
||||||
<td className="text-start">
|
|
||||||
<div className="d-flex align-items-center gap-2">
|
|
||||||
<SkeletonCell
|
|
||||||
width="30px"
|
|
||||||
height={30}
|
|
||||||
className="rounded-circle"
|
|
||||||
/>
|
|
||||||
<SkeletonCell width="80px" height={16} />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{/* Uploaded on */}
|
|
||||||
<td className="text-center">
|
|
||||||
<SkeletonCell width="80px" height={16} />
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{/* Status */}
|
|
||||||
<td className="text-center">
|
|
||||||
<SkeletonCell width="70px" height={20} className="rounded" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
|
||||||
</table>
|
<tbody>
|
||||||
|
{[...Array(rows)].map((_, idx) => (
|
||||||
|
<tr key={idx} className={idx % 2 === 0 ? "odd" : "even"}>
|
||||||
|
{/* Name */}
|
||||||
|
<td className="text-start">
|
||||||
|
<SkeletonCell width="120px" height={16} />
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Document Type */}
|
||||||
|
<td className="text-start">
|
||||||
|
<SkeletonCell width="100px" height={16} />
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Uploaded By (Avatar + Name) */}
|
||||||
|
<td className="text-start">
|
||||||
|
<div className="d-flex align-items-center gap-2">
|
||||||
|
<SkeletonCell width="30px" height={30} className="rounded-circle" />
|
||||||
|
<SkeletonCell width="80px" height={16} />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Uploaded on */}
|
||||||
|
<td className="text-center">
|
||||||
|
<SkeletonCell width="80px" height={16} />
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Status */}
|
||||||
|
<td className="text-center">
|
||||||
|
<SkeletonCell width="70px" height={20} className="rounded" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -117,7 +117,7 @@ const Documents = ({ Document_Entity, Entity }) => {
|
|||||||
}, [Document_Entity]);
|
}, [Document_Entity]);
|
||||||
return (
|
return (
|
||||||
<DocumentContext.Provider value={contextValues}>
|
<DocumentContext.Provider value={contextValues}>
|
||||||
<div className="mt-2">
|
<div className="mt-5">
|
||||||
<div className="card page-min-h d-flex p-2">
|
<div className="card page-min-h d-flex p-2">
|
||||||
<div className="row align-items-center">
|
<div className="row align-items-center">
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
|
|||||||
@ -82,9 +82,9 @@ const DocumentsList = ({
|
|||||||
if (isLoading || isFetching) return <DocumentTableSkeleton />;
|
if (isLoading || isFetching) return <DocumentTableSkeleton />;
|
||||||
if (isError)
|
if (isError)
|
||||||
return <div>Error: {error?.message || "Something went wrong"}</div>;
|
return <div>Error: {error?.message || "Something went wrong"}</div>;
|
||||||
if (isInitialEmpty) return <div className="py-12 my-12">No documents found yet.</div>;
|
if (isInitialEmpty) return <div>No documents found yet.</div>;
|
||||||
if (isSearchEmpty) return <div className="py-12 my-12">No results found for "{debouncedSearch}"</div>;
|
if (isSearchEmpty) return <div>No results found for "{debouncedSearch}"</div>;
|
||||||
if (isFilterEmpty) return <div className="py-12 my-12">No documents match your filter.</div>;
|
if (isFilterEmpty) return <div>No documents match your filter.</div>;
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
ActiveInActive(
|
ActiveInActive(
|
||||||
@ -180,10 +180,10 @@ const DocumentsList = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="table-responsive p-2">
|
<div className="table-responsive">
|
||||||
<table className="table border-top dataTable text-nowrap">
|
<table className="table border-top dataTable text-nowrap">
|
||||||
<thead className="">
|
<thead>
|
||||||
<tr className="py-2 ">
|
<tr className="shadow-sm">
|
||||||
{DocumentColumns.map((col) => (
|
{DocumentColumns.map((col) => (
|
||||||
<th key={col.key} className={`sorting ${col.align}`}>
|
<th key={col.key} className={`sorting ${col.align}`}>
|
||||||
{col.label}
|
{col.label}
|
||||||
|
|||||||
@ -131,7 +131,7 @@ const EmpAttendance = ({ employee }) => {
|
|||||||
<AttendLogs Id={attendanceId} />
|
<AttendLogs Id={attendanceId} />
|
||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
<div className="card px-4 mt-2 py-2 page-min-h ">
|
<div className="card px-4 mt-5 py-2 " style={{ minHeight: "500px" }}>
|
||||||
<div
|
<div
|
||||||
className="dataTables_length text-start py-2 d-flex justify-content-between "
|
className="dataTables_length text-start py-2 d-flex justify-content-between "
|
||||||
id="DataTables_Table_0_length"
|
id="DataTables_Table_0_length"
|
||||||
|
|||||||
@ -12,11 +12,11 @@ const EmpDashboard = ({ profile }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12 col-sm-6 pt-2">
|
<div className="col-12 col-sm-6 pt-5">
|
||||||
{" "}
|
{" "}
|
||||||
<EmpOverview profile={profile}></EmpOverview>
|
<EmpOverview profile={profile}></EmpOverview>
|
||||||
</div>
|
</div>
|
||||||
{/* <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 text-start d-block">
|
<small className="card-text text-uppercase text-body-secondary small text-start d-block">
|
||||||
@ -29,6 +29,7 @@ const EmpDashboard = ({ profile }) => {
|
|||||||
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 */}
|
||||||
<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">
|
||||||
@ -69,6 +70,7 @@ const EmpDashboard = ({ profile }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Dates */}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -77,7 +79,7 @@ const EmpDashboard = ({ profile }) => {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> */}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const EmployeeNav = ({ onPillClick, activePill }) => {
|
|||||||
icon: "bx bx-file",
|
icon: "bx bx-file",
|
||||||
label: "Documents",
|
label: "Documents",
|
||||||
},
|
},
|
||||||
// { key: "activities", icon: "bx bx-grid-alt", label: "Activities" },
|
{ key: "activities", icon: "bx bx-grid-alt", label: "Activities" },
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
return (
|
return (
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
|
|||||||
@ -117,7 +117,6 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
|||||||
endField="endDate"
|
endField="endDate"
|
||||||
resetSignal={resetKey}
|
resetSignal={resetKey}
|
||||||
defaultRange={false}
|
defaultRange={false}
|
||||||
maxDate={new Date()}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { localToUtc } from "../../utils/appUtils";
|
|
||||||
|
|
||||||
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
||||||
const ALLOWED_TYPES = [
|
const ALLOWED_TYPES = [
|
||||||
@ -18,12 +17,15 @@ export const ExpenseSchema = (expenseTypes) => {
|
|||||||
.min(1, { message: "Expense type is required" }),
|
.min(1, { message: "Expense type is required" }),
|
||||||
paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
|
paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
|
||||||
paidById: z.string().min(1, { message: "Employee name is required" }),
|
paidById: z.string().min(1, { message: "Employee name is required" }),
|
||||||
transactionDate: z.string().min(1, { message: "Date is required" }),
|
transactionDate: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "Date is required" })
|
||||||
|
,
|
||||||
transactionId: z.string().optional(),
|
transactionId: z.string().optional(),
|
||||||
description: z.string().min(1, { message: "Description is required" }),
|
description: z.string().min(1, { message: "Description is required" }),
|
||||||
location: z.string().min(1, { message: "Location is required" }),
|
location: z.string().min(1, { message: "Location is required" }),
|
||||||
supplerName: z.string().min(1, { message: "Supplier name is required" }),
|
supplerName: z.string().min(1, { message: "Supplier name is required" }),
|
||||||
gstNumber: z.string().optional(),
|
gstNumber :z.string().optional(),
|
||||||
amount: z.coerce
|
amount: z.coerce
|
||||||
.number({
|
.number({
|
||||||
invalid_type_error: "Amount is required and must be a number",
|
invalid_type_error: "Amount is required and must be a number",
|
||||||
@ -52,6 +54,8 @@ export const ExpenseSchema = (expenseTypes) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.nonempty({ message: "At least one file attachment is required" }),
|
.nonempty({ message: "At least one file attachment is required" }),
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@ -64,14 +68,9 @@ export const ExpenseSchema = (expenseTypes) => {
|
|||||||
path: ["paidById"],
|
path: ["paidById"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
const expenseType = expenseTypes.find(
|
const expenseType = expenseTypes.find((et) => et.id === data.expensesTypeId);
|
||||||
(et) => et.id === data.expensesTypeId
|
if (expenseType?.noOfPersonsRequired && (!data.noOfPersons || data.noOfPersons < 1)) {
|
||||||
);
|
|
||||||
if (
|
|
||||||
expenseType?.noOfPersonsRequired &&
|
|
||||||
(!data.noOfPersons || data.noOfPersons < 1)
|
|
||||||
) {
|
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
message: "No. of Persons is required and must be at least 1",
|
message: "No. of Persons is required and must be at least 1",
|
||||||
@ -93,14 +92,12 @@ export const defaultExpense = {
|
|||||||
supplerName: "",
|
supplerName: "",
|
||||||
amount: "",
|
amount: "",
|
||||||
noOfPersons: "",
|
noOfPersons: "",
|
||||||
gstNumber: "",
|
gstNumber:"",
|
||||||
billAttachments: [],
|
billAttachments: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExpenseActionScheam = (
|
|
||||||
isReimbursement = false,
|
export const ExpenseActionScheam = (isReimbursement = false) => {
|
||||||
transactionDate
|
|
||||||
) => {
|
|
||||||
return z
|
return z
|
||||||
.object({
|
.object({
|
||||||
comment: z.string().min(1, { message: "Please leave comment" }),
|
comment: z.string().min(1, { message: "Please leave comment" }),
|
||||||
@ -125,15 +122,6 @@ export const ExpenseActionScheam = (
|
|||||||
message: "Reimburse Date is required",
|
message: "Reimburse Date is required",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// let reimburse_Date = localToUtc(data.reimburseDate);
|
|
||||||
// if (transactionDate > reimburse_Date) {
|
|
||||||
// ctx.addIssue({
|
|
||||||
// code: z.ZodIssueCode.custom,
|
|
||||||
// path: ["reimburseDate"],
|
|
||||||
// message:
|
|
||||||
// "Reimburse Date must be greater than or equal to Expense created Date",
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
if (!data.reimburseById) {
|
if (!data.reimburseById) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
@ -145,7 +133,7 @@ export const ExpenseActionScheam = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultActionValues = {
|
export const defaultActionValues = {
|
||||||
comment: "",
|
comment: "",
|
||||||
statusId: "",
|
statusId: "",
|
||||||
|
|
||||||
@ -154,6 +142,8 @@ export const defaultActionValues = {
|
|||||||
reimburseById: null,
|
reimburseById: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const SearchSchema = z.object({
|
export const SearchSchema = z.object({
|
||||||
projectIds: z.array(z.string()).optional(),
|
projectIds: z.array(z.string()).optional(),
|
||||||
statusIds: z.array(z.string()).optional(),
|
statusIds: z.array(z.string()).optional(),
|
||||||
@ -173,3 +163,4 @@ export const defaultFilter = {
|
|||||||
startDate: null,
|
startDate: null,
|
||||||
endDate: null,
|
endDate: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { useForm } from "react-hook-form";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema";
|
import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema";
|
||||||
import { useExpenseContext } from "../../pages/Expense/ExpensePage";
|
import { useExpenseContext } from "../../pages/Expense/ExpensePage";
|
||||||
import { getColorNameFromHex, getIconByFileType, localToUtc } from "../../utils/appUtils";
|
import { getColorNameFromHex, getIconByFileType } from "../../utils/appUtils";
|
||||||
import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton";
|
import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import {
|
import {
|
||||||
@ -38,7 +38,7 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
const IsReview = useHasUserPermission(REVIEW_EXPENSE);
|
const IsReview = useHasUserPermission(REVIEW_EXPENSE);
|
||||||
const [imageLoaded, setImageLoaded] = useState({});
|
const [imageLoaded, setImageLoaded] = useState({});
|
||||||
const { setDocumentView } = useExpenseContext();
|
const { setDocumentView } = useExpenseContext();
|
||||||
const ActionSchema = ExpenseActionScheam(IsPaymentProcess,data?.createdAt) ?? z.object({});
|
const ActionSchema = ExpenseActionScheam(IsPaymentProcess) ?? z.object({});
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -91,7 +91,9 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
const onSubmit = (formData) => {
|
const onSubmit = (formData) => {
|
||||||
const Payload = {
|
const Payload = {
|
||||||
...formData,
|
...formData,
|
||||||
reimburseDate:localToUtc(formData.reimburseDate),
|
reimburseDate: moment
|
||||||
|
.utc(formData.reimburseDate, "DD-MM-YYYY")
|
||||||
|
.toISOString(),
|
||||||
expenseId: ExpenseId,
|
expenseId: ExpenseId,
|
||||||
comment: formData.comment,
|
comment: formData.comment,
|
||||||
};
|
};
|
||||||
@ -395,8 +397,7 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
name="reimburseDate"
|
name="reimburseDate"
|
||||||
control={control}
|
control={control}
|
||||||
minDate={data?.createdAt}
|
minDate={data?.transactionDate}
|
||||||
maxDate={new Date()}
|
|
||||||
/>
|
/>
|
||||||
{errors.reimburseDate && (
|
{errors.reimburseDate && (
|
||||||
<small className="danger-text">
|
<small className="danger-text">
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useCallback, useEffect, useState,useMemo } from "react";
|
|
||||||
import getGreetingMessage from "../../utils/greetingHandler";
|
import getGreetingMessage from "../../utils/greetingHandler";
|
||||||
import {
|
import {
|
||||||
cacheData,
|
cacheData,
|
||||||
@ -15,103 +14,118 @@ import { useLocation, useNavigate, useParams } from "react-router-dom";
|
|||||||
import Avatar from "../../components/common/Avatar";
|
import Avatar from "../../components/common/Avatar";
|
||||||
import { useChangePassword } from "../Context/ChangePasswordContext";
|
import { useChangePassword } from "../Context/ChangePasswordContext";
|
||||||
import { useProjectModal, useProjects } from "../../hooks/useProjects";
|
import { useProjectModal, useProjects } from "../../hooks/useProjects";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { ALLOW_PROJECTSTATUS_ID, MANAGE_PROJECT, UUID_REGEX } from "../../utils/constants";
|
import { MANAGE_PROJECT } from "../../utils/constants";
|
||||||
import { useAuthModal, useLogout } from "../../hooks/useAuth";
|
import { useAuthModal, useLogout } from "../../hooks/useAuth";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { profile } = useProfile();
|
const { profile } = useProfile();
|
||||||
const { data: masterData } = useMaster();
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const { data, loading } = useMaster();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { openModal } = useProjectModal();
|
|
||||||
const { mutate: logout, isPending: logouting } = useLogout();
|
|
||||||
const { onOpen } = useAuthModal();
|
const { onOpen } = useAuthModal();
|
||||||
const { openChangePassword } = useChangePassword();
|
|
||||||
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
||||||
|
const { mutate: logout, isPending: logouting } = useLogout();
|
||||||
|
const { openModal } = useProjectModal();
|
||||||
|
|
||||||
const pathname = location.pathname;
|
const isDashboardPath =
|
||||||
|
/^\/dashboard$/.test(location.pathname) || /^\/$/.test(location.pathname);
|
||||||
|
const isProjectPath = /^\/projects$/.test(location.pathname);
|
||||||
|
|
||||||
// ======= MEMO CHECKS =======
|
const showProjectDropdown = (pathname) => {
|
||||||
const isDashboardPath = pathname === "/" || pathname === "/dashboard";
|
const isDirectoryPath = /^\/directory$/.test(pathname);
|
||||||
const isProjectPath = pathname === "/projects";
|
|
||||||
const isDirectory = pathname === "/directory";
|
|
||||||
const isEmployeeList = pathname === "/employees";
|
|
||||||
const isExpense = pathname === "/expenses";
|
|
||||||
const isEmployeeProfile = UUID_REGEX.test(pathname);
|
|
||||||
|
|
||||||
const hideDropPaths =
|
// const isProfilePage = /^\/employee$/.test(location.pathname);
|
||||||
isDirectory || isEmployeeList || isExpense || isEmployeeProfile;
|
const isProfilePage =
|
||||||
|
/^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
|
||||||
|
pathname
|
||||||
|
);
|
||||||
|
const isExpensePage = /^\/expenses$/.test(pathname);
|
||||||
|
|
||||||
const showProjectDropdown = !hideDropPaths;
|
return !(isDirectoryPath || isProfilePage || isExpensePage);
|
||||||
|
};
|
||||||
|
const allowedProjectStatusIds = [
|
||||||
|
"603e994b-a27f-4e5d-a251-f3d69b0498ba",
|
||||||
|
"cdad86aa-8a56-4ff4-b633-9c629057dfef",
|
||||||
|
"b74da4c2-d07e-46f2-9919-e75e49b12731",
|
||||||
|
];
|
||||||
|
|
||||||
// ===== Project Names & Selected Project =====
|
|
||||||
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
|
||||||
const selectedProject = useSelectedProject();
|
|
||||||
|
|
||||||
const projectsForDropdown = useMemo(
|
|
||||||
() =>
|
|
||||||
isDashboardPath
|
|
||||||
? projectNames
|
|
||||||
: projectNames?.filter((project) =>
|
|
||||||
ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId)
|
|
||||||
),
|
|
||||||
[projectNames, isDashboardPath]
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentProjectDisplayName = useMemo(() => {
|
|
||||||
if (projectLoading) return "Loading...";
|
|
||||||
if (!projectNames?.length) return "No Projects Assigned";
|
|
||||||
if (projectNames.length === 1) return projectNames[0].name;
|
|
||||||
|
|
||||||
const selectedObj = projectNames.find((p) => p.id === selectedProject);
|
|
||||||
return selectedObj
|
|
||||||
? selectedObj.name
|
|
||||||
: projectNames[0]?.name || "No Projects Assigned";
|
|
||||||
}, [projectLoading, projectNames, selectedProject]);
|
|
||||||
|
|
||||||
// ===== Role Helper =====
|
|
||||||
const getRole = (roles, joRoleId) => {
|
const getRole = (roles, joRoleId) => {
|
||||||
if (!Array.isArray(roles)) return "User";
|
if (!Array.isArray(roles)) return "User";
|
||||||
return roles.find((r) => r.id === joRoleId)?.name || "User";
|
let role = roles.find((role) => role.id === joRoleId);
|
||||||
|
return role ? role.name : "User";
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== Navigate to Profile =====
|
const handleProfilePage = () => {
|
||||||
const handleProfilePage = () =>
|
|
||||||
navigate(`/employee/${profile?.employeeInfo?.id}`);
|
navigate(`/employee/${profile?.employeeInfo?.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||||
|
|
||||||
|
const selectedProject = useSelectedProject();
|
||||||
|
|
||||||
|
const projectsForDropdown = isDashboardPath
|
||||||
|
? projectNames
|
||||||
|
: projectNames?.filter((project) =>
|
||||||
|
allowedProjectStatusIds.includes(project.projectStatusId)
|
||||||
|
);
|
||||||
|
|
||||||
|
let currentProjectDisplayName;
|
||||||
|
if (projectLoading) {
|
||||||
|
currentProjectDisplayName = "Loading...";
|
||||||
|
} else if (!projectNames || projectNames.length === 0) {
|
||||||
|
currentProjectDisplayName = "No Projects Assigned";
|
||||||
|
} else if (projectNames.length === 1) {
|
||||||
|
currentProjectDisplayName = projectNames[0].name;
|
||||||
|
} else {
|
||||||
|
if (selectedProject === null) {
|
||||||
|
currentProjectDisplayName = "All Projects";
|
||||||
|
} else {
|
||||||
|
const selectedProjectObj = projectNames.find(
|
||||||
|
(p) => p?.id === selectedProject
|
||||||
|
);
|
||||||
|
currentProjectDisplayName = selectedProjectObj
|
||||||
|
? selectedProjectObj.name
|
||||||
|
: "All Projects";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { openChangePassword } = useChangePassword();
|
||||||
|
|
||||||
// ===== Set default project on load =====
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
projectNames?.length &&
|
projectNames &&
|
||||||
|
projectNames.length > 0 &&
|
||||||
selectedProject === undefined &&
|
selectedProject === undefined &&
|
||||||
!getCachedData("hasReceived")
|
!getCachedData("hasReceived")
|
||||||
) {
|
) {
|
||||||
if (projectNames.length === 1) {
|
if (projectNames.length === 1) {
|
||||||
dispatch(setProjectId(projectNames[0].id || null));
|
dispatch(setProjectId(projectNames[0]?.id || null));
|
||||||
} else {
|
} else {
|
||||||
if (isDashboardPath) {
|
if (isDashboardPath) {
|
||||||
dispatch(setProjectId(null));
|
dispatch(setProjectId(null));
|
||||||
} else {
|
} else {
|
||||||
const firstAllowed = projectNames.find((project) =>
|
const firstAllowedProject = projectNames.find((project) =>
|
||||||
ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId)
|
allowedProjectStatusIds.includes(project.projectStatusId)
|
||||||
);
|
);
|
||||||
dispatch(setProjectId(firstAllowed?.id || null));
|
dispatch(setProjectId(firstAllowedProject?.id || null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [projectNames, selectedProject, dispatch, isDashboardPath]);
|
}, [projectNames, selectedProject, dispatch, isDashboardPath]);
|
||||||
|
|
||||||
// ===== Event Handlers =====
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
async (data) => {
|
async (data) => {
|
||||||
if (!HasManageProjectPermission) {
|
if (!HasManageProjectPermission) {
|
||||||
await fetchData();
|
await fetchData();
|
||||||
if (data.projectIds?.includes(selectedProject)) {
|
const projectExist = data.projectIds.some(
|
||||||
|
(item) => item === selectedProject
|
||||||
|
);
|
||||||
|
if (projectExist) {
|
||||||
cacheData("hasReceived", false);
|
cacheData("hasReceived", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,15 +135,14 @@ const Header = () => {
|
|||||||
|
|
||||||
const newProjectHandler = useCallback(
|
const newProjectHandler = useCallback(
|
||||||
async (msg) => {
|
async (msg) => {
|
||||||
if (
|
if (HasManageProjectPermission && msg.keyword === "Create_Project") {
|
||||||
msg.keyword === "Create_Project" ||
|
await fetchData();
|
||||||
projectNames?.some((p) => p.id === msg.response?.id)
|
} else if (projectNames?.some((item) => item.id === msg.response.id)) {
|
||||||
) {
|
|
||||||
await fetchData();
|
await fetchData();
|
||||||
cacheData("hasReceived", false);
|
|
||||||
}
|
}
|
||||||
|
cacheData("hasReceived", false);
|
||||||
},
|
},
|
||||||
[projectNames, fetchData]
|
[HasManageProjectPermission, projectNames, fetchData]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -146,10 +159,10 @@ const Header = () => {
|
|||||||
};
|
};
|
||||||
}, [handler, newProjectHandler]);
|
}, [handler, newProjectHandler]);
|
||||||
|
|
||||||
// ===== Project Change =====
|
const handleProjectChange = (project) => {
|
||||||
const handleProjectChange = (projectId) => {
|
dispatch(setProjectId(project));
|
||||||
dispatch(setProjectId(projectId));
|
|
||||||
if (isProjectPath && projectId !== null) {
|
if (isProjectPath && project !== null) {
|
||||||
navigate("/projects/details");
|
navigate("/projects/details");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -173,7 +186,7 @@ const Header = () => {
|
|||||||
className="navbar-nav-right d-flex align-items-center justify-content-between"
|
className="navbar-nav-right d-flex align-items-center justify-content-between"
|
||||||
id="navbar-collapse"
|
id="navbar-collapse"
|
||||||
>
|
>
|
||||||
{showProjectDropdown && (
|
{showProjectDropdown(location.pathname) && (
|
||||||
<div className="align-items-center">
|
<div className="align-items-center">
|
||||||
<i className="rounded-circle bx bx-building-house bx-sm-lg bx-md me-2"></i>
|
<i className="rounded-circle bx bx-building-house bx-sm-lg bx-md me-2"></i>
|
||||||
<div className="btn-group">
|
<div className="btn-group">
|
||||||
@ -199,6 +212,16 @@ const Header = () => {
|
|||||||
className="dropdown-menu"
|
className="dropdown-menu"
|
||||||
style={{ overflow: "auto", maxHeight: "300px" }}
|
style={{ overflow: "auto", maxHeight: "300px" }}
|
||||||
>
|
>
|
||||||
|
{isDashboardPath && (
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => handleProjectChange(null)}
|
||||||
|
>
|
||||||
|
All Projects
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
{[...projectsForDropdown]
|
{[...projectsForDropdown]
|
||||||
.sort((a, b) => a?.name?.localeCompare(b.name))
|
.sort((a, b) => a?.name?.localeCompare(b.name))
|
||||||
.map((project) => (
|
.map((project) => (
|
||||||
@ -224,16 +247,16 @@ const Header = () => {
|
|||||||
|
|
||||||
<ul className="navbar-nav flex-row align-items-center ms-md-auto">
|
<ul className="navbar-nav flex-row align-items-center ms-md-auto">
|
||||||
{/* {HasManageProjectPermission && ( */}
|
{/* {HasManageProjectPermission && ( */}
|
||||||
<li className="nav-item navbar-dropdown dropdown-user dropdown">
|
<li className="nav-item navbar-dropdown dropdown-user dropdown">
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => openModal(null)}
|
onClick={() => openModal(null)}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
<span className="d-none d-md-inline-block">Create Project</span>
|
<span className="d-none d-md-inline-block">Create Project</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{/* )} */}
|
{/* )} */}
|
||||||
<li className="nav-item navbar-dropdown dropdown-user dropdown">
|
<li className="nav-item navbar-dropdown dropdown-user dropdown">
|
||||||
<a
|
<a
|
||||||
@ -266,7 +289,7 @@ const Header = () => {
|
|||||||
{profile?.employeeInfo?.firstName}
|
{profile?.employeeInfo?.firstName}
|
||||||
</span>
|
</span>
|
||||||
<small className="text-muted">
|
<small className="text-muted">
|
||||||
{getRole(masterData, profile?.employeeInfo?.joRoleId)}
|
{getRole(data, profile?.employeeInfo?.joRoleId)}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -66,7 +66,7 @@ const ProjectCard = ({ project }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`ms-auto `}>
|
<div className={`ms-auto ${!ManageProject && "d-none"}`}>
|
||||||
<div className="dropdown z-2">
|
<div className="dropdown z-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -106,6 +106,12 @@ const ProjectCard = ({ project }) => {
|
|||||||
<span className="align-left">Modify</span>
|
<span className="align-left">Modify</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li onClick={handleViewActivities}>
|
||||||
|
<a className="dropdown-item">
|
||||||
|
<i className="bx bx-task me-2"></i>
|
||||||
|
<span className="align-left">Activities</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,62 +1,70 @@
|
|||||||
import React from "react";
|
import React from 'react'
|
||||||
import { useProjects } from "../../hooks/useProjects";
|
import { useProjects } from '../../hooks/useProjects'
|
||||||
import Loader from "../common/Loader";
|
import Loader from '../common/Loader'
|
||||||
import ProjectCard from "./ProjectCard";
|
import ProjectCard from './ProjectCard'
|
||||||
|
|
||||||
|
const ProjectCardView = ({currentItems,setCurrentPage,totalPages }) => {
|
||||||
|
|
||||||
|
|
||||||
const ProjectCardView = ({ currentItems, setCurrentPage, totalPages }) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="row page-min-h">
|
|
||||||
{currentItems.length === 0 && (
|
|
||||||
<p className="text-center text-muted">No projects found.</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentItems.map((project) => (
|
<div className="row page-min-h">
|
||||||
<ProjectCard key={project.id} project={project} />
|
|
||||||
))}
|
|
||||||
|
|
||||||
{totalPages > 1 && (
|
{ currentItems.length === 0 && (
|
||||||
<nav>
|
<p className="text-center text-muted">No projects found.</p>
|
||||||
<ul className="pagination pagination-sm justify-content-end py-2">
|
)}
|
||||||
<li className={`page-item ${currentPage === 1 && "disabled"}`}>
|
|
||||||
<button
|
{currentItems.map((project) => (
|
||||||
className="page-link"
|
<ProjectCard
|
||||||
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
key={project.id}
|
||||||
>
|
project={project}
|
||||||
«
|
/>
|
||||||
</button>
|
))}
|
||||||
</li>
|
|
||||||
{[...Array(totalPages)].map((_, i) => (
|
|
||||||
|
{ totalPages > 1 && (
|
||||||
|
<nav>
|
||||||
|
<ul className="pagination pagination-sm justify-content-end py-2">
|
||||||
|
<li className={`page-item ${currentPage === 1 && "disabled"}`}>
|
||||||
|
<button
|
||||||
|
className="page-link"
|
||||||
|
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{[...Array(totalPages)].map((_, i) => (
|
||||||
|
<li
|
||||||
|
key={i}
|
||||||
|
className={`page-item ${currentPage === i + 1 && "active"}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="page-link"
|
||||||
|
onClick={() => setCurrentPage(i + 1)}
|
||||||
|
>
|
||||||
|
{i + 1}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
<li
|
<li
|
||||||
key={i}
|
className={`page-item ${currentPage === totalPages && "disabled"
|
||||||
className={`page-item ${currentPage === i + 1 && "active"}`}
|
}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
onClick={() => setCurrentPage(i + 1)}
|
onClick={() =>
|
||||||
|
setCurrentPage((p) => Math.min(totalPages, p + 1))
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{i + 1}
|
»
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
</ul>
|
||||||
<li
|
</nav>
|
||||||
className={`page-item ${
|
)}
|
||||||
currentPage === totalPages && "disabled"
|
</div>
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="page-link"
|
|
||||||
onClick={() =>
|
|
||||||
setCurrentPage((p) => Math.min(totalPages, p + 1))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
»
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProjectCardView;
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectCardView
|
||||||
|
|||||||
@ -26,8 +26,9 @@ const ProjectListView = ({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setMangeProject } = useProjectContext();
|
const { setMangeProject } = useProjectContext();
|
||||||
// const { data, isLoading, isError, error } = useProjects();
|
// const { data, isLoading, isError, error } = useProjects();
|
||||||
|
|
||||||
// check Permissions
|
// check Permissions
|
||||||
// const canManageProject = useHasUserPermission(MANAGE_PROJECT);
|
const canManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||||
|
|
||||||
const projectColumns = [
|
const projectColumns = [
|
||||||
{
|
{
|
||||||
@ -124,15 +125,11 @@ const ProjectListView = ({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// const handleViewActivities = (project) => {
|
const handleViewActivities = (project) => {
|
||||||
// dispatch(setProjectId(project));
|
|
||||||
// navigate(`/activities/records?project=${project}`);
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleMoveDetails = (project) => {
|
|
||||||
dispatch(setProjectId(project));
|
dispatch(setProjectId(project));
|
||||||
navigate("/projects/details");
|
navigate(`/activities/records?project=${project}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card page-min-h py-4 px-6 shadow-sm">
|
<div className="card page-min-h py-4 px-6 shadow-sm">
|
||||||
<table className="table table-hover align-middle m-0">
|
<table className="table table-hover align-middle m-0">
|
||||||
@ -161,7 +158,11 @@ const ProjectListView = ({
|
|||||||
: project[col.key] || "N/A"}
|
: project[col.key] || "N/A"}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
<td className={`mx-2 ${"d-sm-table-cell"}`}>
|
<td
|
||||||
|
className={`mx-2 ${
|
||||||
|
canManageProject ? "d-sm-table-cell" : "d-none"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<div className="dropdown z-2">
|
<div className="dropdown z-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -179,7 +180,7 @@ const ProjectListView = ({
|
|||||||
></i>
|
></i>
|
||||||
</button>
|
</button>
|
||||||
<ul className="dropdown-menu dropdown-menu-end">
|
<ul className="dropdown-menu dropdown-menu-end">
|
||||||
<li onClick={() => handleMoveDetails(project.id)}>
|
<li>
|
||||||
<a
|
<a
|
||||||
aria-label="click to View details"
|
aria-label="click to View details"
|
||||||
className="dropdown-item cursor-pointer"
|
className="dropdown-item cursor-pointer"
|
||||||
@ -203,12 +204,12 @@ const ProjectListView = ({
|
|||||||
<span className="align-left">Modify</span>
|
<span className="align-left">Modify</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/* <li onClick={() => handleViewActivities(project.id)}>
|
<li onClick={() => handleViewActivities(project.id)}>
|
||||||
<a className="dropdown-item cursor-pointer">
|
<a className="dropdown-item cursor-pointer">
|
||||||
<i className="bx bx-task me-2"></i>
|
<i className="bx bx-task me-2"></i>
|
||||||
<span className="align-left">Activities</span>
|
<span className="align-left">Activities</span>
|
||||||
</a>
|
</a>
|
||||||
</li> */}
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -8,14 +8,12 @@ import { orgSize, reference } from "../../utils/constants";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { useGlobalServices } from "../../hooks/masterHook/useMaster";
|
import { useGlobalServices } from "../../hooks/masterHook/useMaster";
|
||||||
import SelectMultiple from "../common/SelectMultiple";
|
import SelectMultiple from "../common/SelectMultiple";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
||||||
const { data, isError, isLoading: industryLoading } = useIndustries();
|
const { data, isError, isLoading: industryLoading } = useIndustries();
|
||||||
const [logoPreview, setLogoPreview] = useState(null);
|
const [logoPreview, setLogoPreview] = useState(null);
|
||||||
const [logoName, setLogoName] = useState("");
|
const [logoName, setLogoName] = useState("");
|
||||||
const { data: services, isLoading: serviceLoading } = useGlobalServices();
|
const { data: services, isLoading: serviceLoading } = useGlobalServices();
|
||||||
const navigate = useNavigate()
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
control,
|
control,
|
||||||
@ -31,8 +29,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
|||||||
error,
|
error,
|
||||||
isPending,
|
isPending,
|
||||||
} = useCreateTenant(() => {
|
} = useCreateTenant(() => {
|
||||||
// onNext()
|
onNext()
|
||||||
navigate("/tenants");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleNext = async () => {
|
const handleNext = async () => {
|
||||||
|
|||||||
@ -4,9 +4,6 @@ import { createPortal } from "react-dom";
|
|||||||
import "./MultiSelectDropdown.css";
|
import "./MultiSelectDropdown.css";
|
||||||
import Label from "./Label";
|
import Label from "./Label";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const SelectMultiple = ({
|
const SelectMultiple = ({
|
||||||
name,
|
name,
|
||||||
options = [],
|
options = [],
|
||||||
@ -14,16 +11,14 @@ const SelectMultiple = ({
|
|||||||
labelKey = "name",
|
labelKey = "name",
|
||||||
valueKey = "id",
|
valueKey = "id",
|
||||||
placeholder = "Please select...",
|
placeholder = "Please select...",
|
||||||
IsLoading = false,
|
IsLoading = false,required = false
|
||||||
required = false,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { setValue, watch, register } = useFormContext();
|
const { setValue, watch,register } = useFormContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
register(name, { value: [] });
|
register(name, { value: [] });
|
||||||
}, [register, name]);
|
}, [register, name]);
|
||||||
|
|
||||||
const selectedValues = watch(name) || [];
|
const selectedValues = watch(name) || [];
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
@ -65,20 +60,18 @@ const SelectMultiple = ({
|
|||||||
const updated = selectedValues.includes(value)
|
const updated = selectedValues.includes(value)
|
||||||
? selectedValues.filter((v) => v !== value)
|
? selectedValues.filter((v) => v !== value)
|
||||||
: [...selectedValues, value];
|
: [...selectedValues, value];
|
||||||
|
|
||||||
setValue(name, updated, { shouldValidate: true });
|
setValue(name, updated, { shouldValidate: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredOptions = (options || []).filter((item) => {
|
const filteredOptions = (options || []).filter((item) => {
|
||||||
const label = getLabel(item);
|
const label = getLabel(item);
|
||||||
return typeof label === "string" && label.toLowerCase().includes(searchText.toLowerCase());
|
return (
|
||||||
});
|
typeof label === "string" &&
|
||||||
|
label.toLowerCase().includes(searchText.toLowerCase())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Sort filtered options in ascending order
|
|
||||||
const sortedOptions = filteredOptions.sort((a, b) => {
|
|
||||||
const labelA = getLabel(a).toString().toLowerCase();
|
|
||||||
const labelB = getLabel(b).toString().toLowerCase();
|
|
||||||
return labelA.localeCompare(labelB);
|
|
||||||
});
|
|
||||||
|
|
||||||
const dropdownElement = (
|
const dropdownElement = (
|
||||||
<div
|
<div
|
||||||
@ -108,7 +101,7 @@ const SelectMultiple = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{sortedOptions.map((item) => {
|
{filteredOptions.map((item) => {
|
||||||
const labelVal = getLabel(item);
|
const labelVal = getLabel(item);
|
||||||
const valueVal = item[valueKey];
|
const valueVal = item[valueKey];
|
||||||
const isChecked = selectedValues.includes(valueVal);
|
const isChecked = selectedValues.includes(valueVal);
|
||||||
@ -131,12 +124,12 @@ const SelectMultiple = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{!IsLoading && sortedOptions.length === 0 && (
|
{!IsLoading && filteredOptions.length === 0 && (
|
||||||
<div className="multi-select-dropdown-Not-found" style={{ padding: 8 }}>
|
<div className="multi-select-dropdown-Not-found" style={{ padding: 8 }}>
|
||||||
<label className="text-muted">Not Found {`'${searchText}'`}</label>
|
<label className="text-muted">Not Found {`'${searchText}'`}</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{IsLoading && sortedOptions.length === 0 && (
|
{IsLoading && filteredOptions.length === 0 && (
|
||||||
<div className="multi-select-dropdown-Not-found" style={{ padding: 8 }}>
|
<div className="multi-select-dropdown-Not-found" style={{ padding: 8 }}>
|
||||||
<label className="text-muted">Loading...</label>
|
<label className="text-muted">Loading...</label>
|
||||||
</div>
|
</div>
|
||||||
@ -147,14 +140,19 @@ const SelectMultiple = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={containerRef} className="multi-select-dropdown-container" style={{ position: "relative" }}>
|
<div ref={containerRef} className="multi-select-dropdown-container" style={{ position: "relative" }}>
|
||||||
<Label required={required}>{label}</Label>
|
<label className="form-label mb-1">{label}</label>
|
||||||
|
<Label className={name} required={required}></Label>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="multi-select-dropdown-header"
|
className="multi-select-dropdown-header"
|
||||||
onClick={() => setIsOpen((prev) => !prev)}
|
onClick={() => setIsOpen((prev) => !prev)}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<span className={selectedValues.length > 0 ? "placeholder-style-selected" : "placeholder-style"}>
|
<span
|
||||||
|
className={
|
||||||
|
selectedValues.length > 0 ? "placeholder-style-selected" : "placeholder-style"
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className="selected-badges-container">
|
<div className="selected-badges-container">
|
||||||
{selectedValues.length > 0 ? (
|
{selectedValues.length > 0 ? (
|
||||||
selectedValues.map((val) => {
|
selectedValues.map((val) => {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useFormContext, useWatch } from "react-hook-form";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Label from "./Label";
|
import Label from "./Label";
|
||||||
|
|
||||||
const TagInput = ({ label, name, placeholder, color = "#e9ecef", options = [],require = false }) => {
|
const TagInput = ({ label, name, placeholder, color = "#e9ecef", options = [] }) => {
|
||||||
const { setValue, watch } = useFormContext();
|
const { setValue, watch } = useFormContext();
|
||||||
const tags = watch(name) || [];
|
const tags = watch(name) || [];
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
@ -65,9 +65,9 @@ const handleChange = (e) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Label required={require}>
|
<label htmlFor={name} className="form-label">
|
||||||
{label}
|
{label}
|
||||||
</Label>
|
</label>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="form-control form-control-sm p-1"
|
className="form-control form-control-sm p-1"
|
||||||
|
|||||||
@ -155,32 +155,32 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => {
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
|
|
||||||
// export const useAttendanceOverviewData = (projectId, days) => {
|
export const useAttendanceOverviewData = (projectId, days) => {
|
||||||
// const [attendanceOverviewData, setAttendanceOverviewData] = useState([]);
|
const [attendanceOverviewData, setAttendanceOverviewData] = useState([]);
|
||||||
// const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
// const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// if (!projectId || !days) return;
|
if (!projectId || !days) return;
|
||||||
// const fetchAttendanceOverview = async () => {
|
const fetchAttendanceOverview = async () => {
|
||||||
// setLoading(true);
|
setLoading(true);
|
||||||
// setError("");
|
setError("");
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// const response = await GlobalRepository.getAttendanceOverview(projectId, days);
|
const response = await GlobalRepository.getAttendanceOverview(projectId, days);
|
||||||
// setAttendanceOverviewData(response.data);
|
setAttendanceOverviewData(response.data);
|
||||||
// } catch (err) {
|
} catch (err) {
|
||||||
// setError("Failed to fetch attendance overview data.");
|
setError("Failed to fetch attendance overview data.");
|
||||||
// } finally {
|
} finally {
|
||||||
// setLoading(false);
|
setLoading(false);
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
|
|
||||||
// fetchAttendanceOverview();
|
fetchAttendanceOverview();
|
||||||
// }, [projectId, days]);
|
}, [projectId, days]);
|
||||||
|
|
||||||
// return { attendanceOverviewData, loading, error };
|
return { attendanceOverviewData, loading, error };
|
||||||
// };
|
};
|
||||||
|
|
||||||
|
|
||||||
// -------------------Query----------------------------
|
// -------------------Query----------------------------
|
||||||
|
|||||||
@ -399,6 +399,7 @@ export const useUpdateBucket = (onSuccessCallBack) => {
|
|||||||
mutationFn: async ({ bucketId, BucketPayload }) =>
|
mutationFn: async ({ bucketId, BucketPayload }) =>
|
||||||
await DirectoryRepository.UpdateBuckets(bucketId, BucketPayload),
|
await DirectoryRepository.UpdateBuckets(bucketId, BucketPayload),
|
||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
|
debugger;
|
||||||
queryClient.invalidateQueries({ queryKey: ["bucketList"] });
|
queryClient.invalidateQueries({ queryKey: ["bucketList"] });
|
||||||
showToast("Bucket updated successfully", "success");
|
showToast("Bucket updated successfully", "success");
|
||||||
if (onSuccessCallBack) onSuccessCallBack();
|
if (onSuccessCallBack) onSuccessCallBack();
|
||||||
@ -463,7 +464,6 @@ export const useCreateContact = (onSuccessCallBack) => {
|
|||||||
await DirectoryRepository.CreateContact(contactPayload),
|
await DirectoryRepository.CreateContact(contactPayload),
|
||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["contacts"] });
|
queryClient.invalidateQueries({ queryKey: ["contacts"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["bucketList"] });
|
|
||||||
showToast("Contact created Successfully", "success");
|
showToast("Contact created Successfully", "success");
|
||||||
if (onSuccessCallBack) onSuccessCallBack();
|
if (onSuccessCallBack) onSuccessCallBack();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export const useUploadDocument = (onSuccessCallBack) => {
|
|||||||
DocumentRepository.uploadDocument(DocumentPayload),
|
DocumentRepository.uploadDocument(DocumentPayload),
|
||||||
onSuccess: (data, variables) => {
|
onSuccess: (data, variables) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["Document"] });
|
|
||||||
if (onSuccessCallBack) onSuccessCallBack();
|
if (onSuccessCallBack) onSuccessCallBack();
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@ -141,7 +141,7 @@ export const useUpdateDocument = (onSuccessCallBack) => {
|
|||||||
onSuccess: (data, variables) => {
|
onSuccess: (data, variables) => {
|
||||||
const { documentId } = variables;
|
const { documentId } = variables;
|
||||||
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["Document"] });
|
queryClient.invalidateQueries({ queryKey: ["Document", documentId] });
|
||||||
if (onSuccessCallBack) onSuccessCallBack();
|
if (onSuccessCallBack) onSuccessCallBack();
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@ -187,7 +187,6 @@ export const useActiveInActiveDocument = ()=>{
|
|||||||
onSuccess: (data, variables) => {
|
onSuccess: (data, variables) => {
|
||||||
const {isActive} = variables;
|
const {isActive} = variables;
|
||||||
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["Document"] });
|
|
||||||
showToast(`Document ${isActive ? "restored":"Deleted"} successfully`,"success")
|
showToast(`Document ${isActive ? "restored":"Deleted"} successfully`,"success")
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { useDispatch } from "react-redux";
|
|||||||
import { setCurrentTenant } from "../slices/globalVariablesSlice";
|
import { setCurrentTenant } from "../slices/globalVariablesSlice";
|
||||||
import { ITEMS_PER_PAGE } from "../utils/constants";
|
import { ITEMS_PER_PAGE } from "../utils/constants";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { queryClient } from "../layouts/AuthLayout";
|
|
||||||
|
|
||||||
const cleanFilter = (filter) => {
|
const cleanFilter = (filter) => {
|
||||||
const cleaned = { ...filter };
|
const cleaned = { ...filter };
|
||||||
@ -72,7 +71,6 @@ export const useSubscriptionPlan = (freq) => {
|
|||||||
// ------------Mutation---------------------
|
// ------------Mutation---------------------
|
||||||
|
|
||||||
export const useCreateTenant = (onSuccessCallback) => {
|
export const useCreateTenant = (onSuccessCallback) => {
|
||||||
const clinet = queryClient()
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (tenantPayload) => {
|
mutationFn: async (tenantPayload) => {
|
||||||
@ -89,9 +87,6 @@ export const useCreateTenant = (onSuccessCallback) => {
|
|||||||
operationMode = 2; // tenant exists but subscription not added yet
|
operationMode = 2; // tenant exists but subscription not added yet
|
||||||
}
|
}
|
||||||
|
|
||||||
clinet.invalidateQueries({queryKey:["Tenants"]})
|
|
||||||
|
|
||||||
|
|
||||||
dispatch(setCurrentTenant({ operationMode, data }));
|
dispatch(setCurrentTenant({ operationMode, data }));
|
||||||
|
|
||||||
if (onSuccessCallback) onSuccessCallback();
|
if (onSuccessCallback) onSuccessCallback();
|
||||||
|
|||||||
@ -86,11 +86,6 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
|
|||||||
<Loader />
|
<Loader />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{data?.data?.length === 0 && (<div className="py-12 ">
|
|
||||||
{searchText ? `No contact found for "${searchText}"`:"No contacts found" }
|
|
||||||
</div>)}
|
|
||||||
{data?.data?.map((contact) => (
|
{data?.data?.map((contact) => (
|
||||||
<div
|
<div
|
||||||
key={contact.id}
|
key={contact.id}
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
const [searchNote, setSearchNote] = useState("");
|
const [searchNote, setSearchNote] = useState("");
|
||||||
const [activeTab, setActiveTab] = useState("notes");
|
const [activeTab, setActiveTab] = useState("notes");
|
||||||
const { setActions } = useFab();
|
const { setActions } = useFab();
|
||||||
const [gridView, setGridView] = useState(true);
|
const [gridView, setGridView] = useState(false);
|
||||||
const [isOpenBucket, setOpenBucket] = useState(false);
|
const [isOpenBucket, setOpenBucket] = useState(false);
|
||||||
const [isManageContact, setManageContact] = useState({
|
const [isManageContact, setManageContact] = useState({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
@ -185,7 +185,14 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
value={searchContact}
|
value={searchContact}
|
||||||
onChange={(e) => setsearchContact(e.target.value)}
|
onChange={(e) => setsearchContact(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
className={`btn btn-sm p-1 ${
|
||||||
|
!gridView ? "btn-primary" : "btn-outline-primary"
|
||||||
|
}`}
|
||||||
|
onClick={() => setGridView(false)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-list-ul"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm p-1 ${
|
className={`btn btn-sm p-1 ${
|
||||||
@ -195,15 +202,6 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
>
|
>
|
||||||
<i className="bx bx-grid-alt"></i>
|
<i className="bx bx-grid-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
|
||||||
className={`btn btn-sm p-1 ${
|
|
||||||
!gridView ? "btn-primary" : "btn-outline-primary"
|
|
||||||
}`}
|
|
||||||
onClick={() => setGridView(false)}
|
|
||||||
>
|
|
||||||
<i className="bx bx-list-ul"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-check form-switch d-flex align-items-center">
|
<div className="form-check form-switch d-flex align-items-center">
|
||||||
|
|||||||
@ -60,12 +60,12 @@ const NoteFilterPanel = ({ onApply, clearFilter }) => {
|
|||||||
<div className="d-flex justify-content-end py-3 gap-2">
|
<div className="d-flex justify-content-end py-3 gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-label-secondary btn-sm"
|
className="btn btn-label-secondary btn-xs"
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
>
|
>
|
||||||
Clear
|
Clear
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="btn btn-primary btn-sm">
|
<button type="submit" className="btn btn-primary btn-xs">
|
||||||
Apply
|
Apply
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -51,7 +51,11 @@ const EmployeeList = () => {
|
|||||||
const Manage_Employee = useHasUserPermission(MANAGE_EMPLOYEES);
|
const Manage_Employee = useHasUserPermission(MANAGE_EMPLOYEES);
|
||||||
|
|
||||||
const { employees, loading, setLoading, error, recallEmployeeData } =
|
const { employees, loading, setLoading, error, recallEmployeeData } =
|
||||||
useEmployeesAllOrByProjectId(showAllEmployees, null, showInactive);
|
useEmployeesAllOrByProjectId(
|
||||||
|
showAllEmployees,
|
||||||
|
null,
|
||||||
|
showInactive
|
||||||
|
);
|
||||||
|
|
||||||
const [employeeList, setEmployeeList] = useState([]);
|
const [employeeList, setEmployeeList] = useState([]);
|
||||||
const [modelConfig, setModelConfig] = useState();
|
const [modelConfig, setModelConfig] = useState();
|
||||||
@ -200,13 +204,19 @@ const EmployeeList = () => {
|
|||||||
|
|
||||||
setCurrentPage((prevPage) => (prevPage !== 1 ? 1 : prevPage));
|
setCurrentPage((prevPage) => (prevPage !== 1 ? 1 : prevPage));
|
||||||
}
|
}
|
||||||
}, [loading, employees, showAllEmployees]);
|
}, [loading, employees, showAllEmployees]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if (employees.some((item) => item.id == msg.employeeId)) {
|
if (employees.some((item) => item.id == msg.employeeId)) {
|
||||||
setEmployeeList([]);
|
setEmployeeList([]);
|
||||||
recallEmployeeData(showInactive, null, showAllEmployees);
|
recallEmployeeData(
|
||||||
|
showInactive,
|
||||||
|
null,
|
||||||
|
showAllEmployees
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[employees, showInactive, showAllEmployees]
|
[employees, showInactive, showAllEmployees]
|
||||||
@ -279,16 +289,18 @@ const EmployeeList = () => {
|
|||||||
|
|
||||||
{ViewTeamMember ? (
|
{ViewTeamMember ? (
|
||||||
// <div className="row">
|
// <div className="row">
|
||||||
<div className="card page-min-h ">
|
<div className="card page-min-h p-1">
|
||||||
<div
|
<div className="card-datatable table-responsive pt-5 mx-5 py-10">
|
||||||
id="DataTables_Table_0_wrapper"
|
<div
|
||||||
className="dataTables_wrapper dt-bootstrap5 no-footer p-1 pt-5 mx-5 py-10"
|
id="DataTables_Table_0_wrapper"
|
||||||
>
|
className="dataTables_wrapper dt-bootstrap5 no-footer"
|
||||||
<div className="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-3">
|
style={{ width: "100%" }}
|
||||||
{/* Switches: All Employees + Inactive */}
|
>
|
||||||
<div className="d-flex flex-wrap align-items-center gap-3">
|
<div className="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-3">
|
||||||
{/* All Employees Switch */}
|
{/* Switches: All Employees + Inactive */}
|
||||||
{/* {ViewAllEmployee && (
|
<div className="d-flex flex-wrap align-items-center gap-3">
|
||||||
|
{/* All Employees Switch */}
|
||||||
|
{/* {ViewAllEmployee && (
|
||||||
<div className="form-check form-switch text-start">
|
<div className="form-check form-switch text-start">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -307,380 +319,390 @@ const EmployeeList = () => {
|
|||||||
</div>
|
</div>
|
||||||
)} */}
|
)} */}
|
||||||
|
|
||||||
{/* Show Inactive Employees Switch */}
|
{/* Show Inactive Employees Switch */}
|
||||||
{/* {showAllEmployees && ( */}
|
{/* {showAllEmployees && ( */}
|
||||||
<div className="form-check form-switch text-start">
|
<div className="form-check form-switch text-start">
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
role="switch"
|
|
||||||
id="inactiveEmployeesCheckbox"
|
|
||||||
checked={showInactive}
|
|
||||||
onChange={(e) => setShowInactive(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
className="form-check-label ms-0"
|
|
||||||
htmlFor="inactiveEmployeesCheckbox"
|
|
||||||
>
|
|
||||||
Show Inactive Employees
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{/* )} */}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right side: Search + Export + Add Employee */}
|
|
||||||
<div className="d-flex flex-wrap align-items-center justify-content-end gap-3 flex-grow-1">
|
|
||||||
{/* Search Input - ALWAYS ENABLED */}
|
|
||||||
<div className="dataTables_filter">
|
|
||||||
<label className="mb-0">
|
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="checkbox"
|
||||||
value={searchText}
|
className="form-check-input"
|
||||||
onChange={handleSearch}
|
role="switch"
|
||||||
className="form-control form-control-sm"
|
id="inactiveEmployeesCheckbox"
|
||||||
placeholder="Search Employee"
|
checked={showInactive}
|
||||||
aria-controls="DataTables_Table_0"
|
onChange={(e) => setShowInactive(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
</label>
|
<label
|
||||||
|
className="form-check-label ms-0"
|
||||||
|
htmlFor="inactiveEmployeesCheckbox"
|
||||||
|
>
|
||||||
|
Show Inactive Employees
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{/* )} */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Export Dropdown */}
|
{/* Right side: Search + Export + Add Employee */}
|
||||||
<div className="dropdown">
|
<div className="d-flex flex-wrap align-items-center justify-content-end gap-3 flex-grow-1">
|
||||||
<button
|
{/* Search Input - ALWAYS ENABLED */}
|
||||||
aria-label="Click me"
|
<div className="dataTables_filter">
|
||||||
className="btn btn-sm btn-label-secondary dropdown-toggle"
|
<label className="mb-0">
|
||||||
type="button"
|
<input
|
||||||
data-bs-toggle="dropdown"
|
type="search"
|
||||||
aria-expanded="false"
|
value={searchText}
|
||||||
>
|
onChange={handleSearch}
|
||||||
<i className="bx bx-export me-2 bx-sm"></i>Export
|
className="form-control form-control-sm"
|
||||||
</button>
|
placeholder="Search Employee"
|
||||||
<ul className="dropdown-menu">
|
aria-controls="DataTables_Table_0"
|
||||||
<li>
|
/>
|
||||||
<a
|
</label>
|
||||||
className="dropdown-item"
|
</div>
|
||||||
href="#"
|
|
||||||
onClick={() => handleExport("print")}
|
|
||||||
>
|
|
||||||
<i className="bx bx-printer me-1"></i> Print
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
className="dropdown-item"
|
|
||||||
href="#"
|
|
||||||
onClick={() => handleExport("csv")}
|
|
||||||
>
|
|
||||||
<i className="bx bx-file me-1"></i> CSV
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
className="dropdown-item"
|
|
||||||
href="#"
|
|
||||||
onClick={() => handleExport("excel")}
|
|
||||||
>
|
|
||||||
<i className="bx bxs-file-export me-1"></i> Excel
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
className="dropdown-item"
|
|
||||||
href="#"
|
|
||||||
onClick={() => handleExport("pdf")}
|
|
||||||
>
|
|
||||||
<i className="bx bxs-file-pdf me-1"></i> PDF
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Add Employee Button */}
|
{/* Export Dropdown */}
|
||||||
{Manage_Employee && (
|
<div className="dropdown">
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-primary"
|
aria-label="Click me"
|
||||||
type="button"
|
className="btn btn-sm btn-label-secondary dropdown-toggle"
|
||||||
onClick={() => handleEmployeeModel(null)}
|
type="button"
|
||||||
>
|
data-bs-toggle="dropdown"
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
aria-expanded="false"
|
||||||
<span className="d-none d-md-inline-block">
|
>
|
||||||
Add New Employee
|
<i className="bx bx-export me-2 bx-sm"></i>Export
|
||||||
</span>
|
</button>
|
||||||
</button>
|
<ul className="dropdown-menu">
|
||||||
)}
|
<li>
|
||||||
|
<a
|
||||||
|
className="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
onClick={() => handleExport("print")}
|
||||||
|
>
|
||||||
|
<i className="bx bx-printer me-1"></i> Print
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
className="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
onClick={() => handleExport("csv")}
|
||||||
|
>
|
||||||
|
<i className="bx bx-file me-1"></i> CSV
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
className="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
onClick={() => handleExport("excel")}
|
||||||
|
>
|
||||||
|
<i className="bx bxs-file-export me-1"></i> Excel
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
className="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
onClick={() => handleExport("pdf")}
|
||||||
|
>
|
||||||
|
<i className="bx bxs-file-pdf me-1"></i> PDF
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Add Employee Button */}
|
||||||
|
{Manage_Employee && (
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-primary"
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleEmployeeModel(null)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
|
<span className="d-none d-md-inline-block">
|
||||||
|
Add New Employee
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<table
|
<table
|
||||||
className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap"
|
className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap"
|
||||||
id="DataTables_Table_0"
|
id="DataTables_Table_0"
|
||||||
aria-describedby="DataTables_Table_0_info"
|
aria-describedby="DataTables_Table_0_info"
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
|
||||||
<th
|
|
||||||
className="sorting sorting_desc"
|
|
||||||
tabIndex="0"
|
|
||||||
aria-controls="DataTables_Table_0"
|
|
||||||
rowSpan="1"
|
|
||||||
colSpan="2"
|
|
||||||
aria-label="User: activate to sort column ascending"
|
|
||||||
aria-sort="descending"
|
|
||||||
>
|
|
||||||
<div className="text-start ms-6">Name</div>
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="sorting sorting_desc d-none d-sm-table-cell"
|
|
||||||
tabIndex="0"
|
|
||||||
aria-controls="DataTables_Table_0"
|
|
||||||
rowSpan="1"
|
|
||||||
colSpan="1"
|
|
||||||
aria-label="User: activate to sort column ascending"
|
|
||||||
aria-sort="descending"
|
|
||||||
>
|
|
||||||
<div className="text-start ms-5">Email</div>
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="sorting sorting_desc d-none d-sm-table-cell"
|
|
||||||
tabIndex="0"
|
|
||||||
aria-controls="DataTables_Table_0"
|
|
||||||
rowSpan="1"
|
|
||||||
colSpan="1"
|
|
||||||
aria-label="User: activate to sort column ascending"
|
|
||||||
aria-sort="descending"
|
|
||||||
>
|
|
||||||
<div className="text-start ms-5">Contact</div>
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="sorting sorting_desc d-none d-sm-table-cell"
|
|
||||||
tabIndex="0"
|
|
||||||
aria-controls="DataTables_Table_0"
|
|
||||||
rowSpan="1"
|
|
||||||
colSpan="1"
|
|
||||||
aria-label="User: activate to sort column ascending"
|
|
||||||
aria-sort="descending"
|
|
||||||
>
|
|
||||||
<div className="text-start ms-5">Designation</div>
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th
|
|
||||||
className="sorting d-none d-md-table-cell"
|
|
||||||
tabIndex="0"
|
|
||||||
aria-controls="DataTables_Table_0"
|
|
||||||
rowSpan="1"
|
|
||||||
colSpan="1"
|
|
||||||
aria-label="Plan: activate to sort column ascending"
|
|
||||||
>
|
|
||||||
Joining Date
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="sorting"
|
|
||||||
tabIndex="0"
|
|
||||||
aria-controls="DataTables_Table_0"
|
|
||||||
rowSpan="1"
|
|
||||||
colSpan="1"
|
|
||||||
aria-label="Billing: activate to sort column ascending"
|
|
||||||
>
|
|
||||||
Status
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className={`sorting_disabled ${
|
|
||||||
!Manage_Employee && "d-none"
|
|
||||||
}`}
|
|
||||||
rowSpan="1"
|
|
||||||
colSpan="1"
|
|
||||||
style={{ width: "50px" }}
|
|
||||||
aria-label="Actions"
|
|
||||||
>
|
|
||||||
Actions
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{loading && (
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={8}>
|
<th
|
||||||
<p>Loading...</p>
|
className="sorting sorting_desc"
|
||||||
</td>
|
tabIndex="0"
|
||||||
</tr>
|
aria-controls="DataTables_Table_0"
|
||||||
)}
|
rowSpan="1"
|
||||||
{/* Conditional messages for no data or no search results */}
|
colSpan="2"
|
||||||
{!loading &&
|
aria-label="User: activate to sort column ascending"
|
||||||
displayData?.length === 0 &&
|
aria-sort="descending"
|
||||||
searchText ? (
|
>
|
||||||
<tr>
|
<div className="text-start ms-6">Name</div>
|
||||||
<td colSpan={8} className="border-0">
|
</th>
|
||||||
<div className="py-12">
|
<th
|
||||||
<small className="muted">
|
className="sorting sorting_desc d-none d-sm-table-cell"
|
||||||
'{searchText}' employee not found
|
tabIndex="0"
|
||||||
</small>{" "}
|
aria-controls="DataTables_Table_0"
|
||||||
</div>
|
rowSpan="1"
|
||||||
</td>
|
colSpan="1"
|
||||||
</tr>
|
aria-label="User: activate to sort column ascending"
|
||||||
) : null}
|
aria-sort="descending"
|
||||||
{!loading &&
|
>
|
||||||
displayData?.length === 0 &&
|
<div className="text-start ms-5">Email</div>
|
||||||
(!searchText ) ? (
|
</th>
|
||||||
<tr>
|
<th
|
||||||
<td colSpan={8} className="border-0">
|
className="sorting sorting_desc d-none d-sm-table-cell"
|
||||||
<div className="py-12">{showInactive ? "No In-active Employeee Found" : "No Employeee Found" }</div>
|
tabIndex="0"
|
||||||
</td>
|
aria-controls="DataTables_Table_0"
|
||||||
</tr>
|
rowSpan="1"
|
||||||
) : null}
|
colSpan="1"
|
||||||
|
aria-label="User: activate to sort column ascending"
|
||||||
|
aria-sort="descending"
|
||||||
|
>
|
||||||
|
<div className="text-start ms-5">Contact</div>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
className="sorting sorting_desc d-none d-sm-table-cell"
|
||||||
|
tabIndex="0"
|
||||||
|
aria-controls="DataTables_Table_0"
|
||||||
|
rowSpan="1"
|
||||||
|
colSpan="1"
|
||||||
|
aria-label="User: activate to sort column ascending"
|
||||||
|
aria-sort="descending"
|
||||||
|
>
|
||||||
|
<div className="text-start ms-5">Designation</div>
|
||||||
|
</th>
|
||||||
|
|
||||||
{/* Render current items */}
|
<th
|
||||||
{currentItems &&
|
className="sorting d-none d-md-table-cell"
|
||||||
!loading &&
|
tabIndex="0"
|
||||||
currentItems.map((item) => (
|
aria-controls="DataTables_Table_0"
|
||||||
<tr className="odd" key={item.id}>
|
rowSpan="1"
|
||||||
<td className="sorting_1" colSpan={2}>
|
colSpan="1"
|
||||||
<div className="d-flex justify-content-start align-items-center user-name">
|
aria-label="Plan: activate to sort column ascending"
|
||||||
<Avatar
|
>
|
||||||
firstName={item.firstName}
|
Joining Date
|
||||||
lastName={item.lastName}
|
</th>
|
||||||
></Avatar>
|
<th
|
||||||
<div className="d-flex flex-column">
|
className="sorting"
|
||||||
<a
|
tabIndex="0"
|
||||||
onClick={() => navigate(`/employee/${item.id}`)}
|
aria-controls="DataTables_Table_0"
|
||||||
className="text-heading text-truncate cursor-pointer"
|
rowSpan="1"
|
||||||
>
|
colSpan="1"
|
||||||
<span className="fw-normal">
|
aria-label="Billing: activate to sort column ascending"
|
||||||
{item.firstName} {item.middleName}{" "}
|
>
|
||||||
{item.lastName}
|
Status
|
||||||
</span>
|
</th>
|
||||||
</a>
|
<th
|
||||||
</div>
|
className={`sorting_disabled ${
|
||||||
</div>
|
!Manage_Employee && "d-none"
|
||||||
|
}`}
|
||||||
|
rowSpan="1"
|
||||||
|
colSpan="1"
|
||||||
|
style={{ width: "50px" }}
|
||||||
|
aria-label="Actions"
|
||||||
|
>
|
||||||
|
Actions
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{loading && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={8}>
|
||||||
|
<p>Loading...</p>
|
||||||
</td>
|
</td>
|
||||||
<td className="text-start d-none d-sm-table-cell">
|
</tr>
|
||||||
{item.email ? (
|
)}
|
||||||
<span className="text-truncate">
|
{/* Conditional messages for no data or no search results */}
|
||||||
<i className="bx bxs-envelope text-primary me-2"></i>
|
{!loading &&
|
||||||
{item.email}
|
displayData?.length === 0 &&
|
||||||
</span>
|
searchText &&
|
||||||
) : (
|
!showAllEmployees ? (
|
||||||
<span className="text-truncate text-italic">-</span>
|
<tr>
|
||||||
)}
|
<td colSpan={8}>
|
||||||
|
<small className="muted">
|
||||||
|
'{searchText}' employee not found
|
||||||
|
</small>{" "}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-start d-none d-sm-table-cell">
|
</tr>
|
||||||
<span className="text-truncate">
|
) : null}
|
||||||
<i className="bx bxs-phone-call text-primary me-2"></i>
|
{!loading &&
|
||||||
{item.phoneNumber}
|
displayData?.length === 0 &&
|
||||||
</span>
|
(!searchText || showAllEmployees) ? (
|
||||||
</td>
|
<tr>
|
||||||
<td className=" d-none d-sm-table-cell text-start">
|
<td
|
||||||
<span className="text-truncate">
|
colSpan={8}
|
||||||
<i className="bx bxs-wrench text-success me-2"></i>
|
className="border-0"
|
||||||
{item.jobRole || "Not Assign Yet"}
|
>
|
||||||
</span>
|
<div className="py-1">
|
||||||
|
No Employeee Found
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<td className=" d-none d-md-table-cell">
|
{/* Render current items */}
|
||||||
{moment(item.joiningDate)?.format("DD-MMM-YYYY")}
|
{currentItems &&
|
||||||
</td>
|
!loading &&
|
||||||
<td>
|
currentItems.map((item) => (
|
||||||
{showInactive ? (
|
<tr className="odd" key={item.id}>
|
||||||
<span
|
<td className="sorting_1" colSpan={2}>
|
||||||
className="badge bg-label-danger"
|
<div className="d-flex justify-content-start align-items-center user-name">
|
||||||
text-capitalized=""
|
<Avatar
|
||||||
>
|
firstName={item.firstName}
|
||||||
Inactive
|
lastName={item.lastName}
|
||||||
</span>
|
></Avatar>
|
||||||
) : (
|
<div className="d-flex flex-column">
|
||||||
<span
|
<a
|
||||||
className="badge bg-label-success"
|
|
||||||
text-capitalized=""
|
|
||||||
>
|
|
||||||
Active
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
{Manage_Employee && (
|
|
||||||
<td className="text-end">
|
|
||||||
<div className="dropdown">
|
|
||||||
<button
|
|
||||||
className="btn btn-icon dropdown-toggle hide-arrow"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
>
|
|
||||||
<i className="bx bx-dots-vertical-rounded bx-md"></i>
|
|
||||||
</button>
|
|
||||||
<div className="dropdown-menu dropdown-menu-end">
|
|
||||||
{/* View always visible */}
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(`/employee/${item.id}`)
|
navigate(`/employee/${item.id}`)
|
||||||
}
|
}
|
||||||
className="dropdown-item py-1"
|
className="text-heading text-truncate cursor-pointer"
|
||||||
>
|
>
|
||||||
<i className="bx bx-detail bx-sm"></i> View
|
<span className="fw-normal">
|
||||||
</button>
|
{item.firstName} {item.middleName}{" "}
|
||||||
|
{item.lastName}
|
||||||
{/* If ACTIVE employee */}
|
</span>
|
||||||
{item.isActive && (
|
</a>
|
||||||
<>
|
|
||||||
<button
|
|
||||||
className="dropdown-item py-1"
|
|
||||||
onClick={() =>
|
|
||||||
handleEmployeeModel(item.id)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i className="bx bx-edit bx-sm"></i> Edit
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Suspend only when active */}
|
|
||||||
{item.isActive && (
|
|
||||||
<button
|
|
||||||
className="dropdown-item py-1"
|
|
||||||
onClick={() => handleOpenDelete(item)}
|
|
||||||
>
|
|
||||||
<i className="bx bx-task-x bx-sm"></i>{" "}
|
|
||||||
Suspend
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="dropdown-item py-1"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#managerole-modal"
|
|
||||||
onClick={() =>
|
|
||||||
setEmpForManageRole(item.id)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i className="bx bx-cog bx-sm"></i> Manage
|
|
||||||
Role
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* If INACTIVE employee AND inactive toggle is ON */}
|
|
||||||
{!item.isActive && showInactive && (
|
|
||||||
<button
|
|
||||||
className="dropdown-item py-1"
|
|
||||||
onClick={() => handleOpenDelete(item)}
|
|
||||||
>
|
|
||||||
<i className="bx bx-refresh bx-sm me-1"></i>{" "}
|
|
||||||
Re-activate
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)}
|
<td className="text-start d-none d-sm-table-cell">
|
||||||
</tr>
|
{item.email ? (
|
||||||
))}
|
<span className="text-truncate">
|
||||||
</tbody>
|
<i className="bx bxs-envelope text-primary me-2"></i>
|
||||||
</table>
|
{item.email}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-truncate text-italic">
|
||||||
|
-
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="text-start d-none d-sm-table-cell">
|
||||||
|
<span className="text-truncate">
|
||||||
|
<i className="bx bxs-phone-call text-primary me-2"></i>
|
||||||
|
{item.phoneNumber}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className=" d-none d-sm-table-cell text-start">
|
||||||
|
<span className="text-truncate">
|
||||||
|
<i className="bx bxs-wrench text-success me-2"></i>
|
||||||
|
{item.jobRole || "Not Assign Yet"}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
{displayData.length > 0 && (
|
<td className=" d-none d-md-table-cell">
|
||||||
<Pagination
|
{moment(item.joiningDate)?.format("DD-MMM-YYYY")}
|
||||||
currentPage={currentPage}
|
</td>
|
||||||
totalPages={totalPages}
|
<td>
|
||||||
onPageChange={paginate}
|
{showInactive ? (
|
||||||
/>
|
<span
|
||||||
)}
|
className="badge bg-label-danger"
|
||||||
|
text-capitalized=""
|
||||||
|
>
|
||||||
|
Inactive
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
className="badge bg-label-success"
|
||||||
|
text-capitalized=""
|
||||||
|
>
|
||||||
|
Active
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
{Manage_Employee && (
|
||||||
|
<td className="text-end">
|
||||||
|
<div className="dropdown">
|
||||||
|
<button
|
||||||
|
className="btn btn-icon dropdown-toggle hide-arrow"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
>
|
||||||
|
<i className="bx bx-dots-vertical-rounded bx-md"></i>
|
||||||
|
</button>
|
||||||
|
<div className="dropdown-menu dropdown-menu-end">
|
||||||
|
{/* View always visible */}
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
navigate(`/employee/${item.id}`)
|
||||||
|
}
|
||||||
|
className="dropdown-item py-1"
|
||||||
|
>
|
||||||
|
<i className="bx bx-detail bx-sm"></i> View
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* If ACTIVE employee */}
|
||||||
|
{item.isActive && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="dropdown-item py-1"
|
||||||
|
onClick={() =>
|
||||||
|
handleEmployeeModel(item.id)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="bx bx-edit bx-sm"></i>{" "}
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Suspend only when active */}
|
||||||
|
{item.isActive && (
|
||||||
|
<button
|
||||||
|
className="dropdown-item py-1"
|
||||||
|
onClick={() => handleOpenDelete(item)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-task-x bx-sm"></i>{" "}
|
||||||
|
Suspend
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="dropdown-item py-1"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#managerole-modal"
|
||||||
|
onClick={() =>
|
||||||
|
setEmpForManageRole(item.id)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="bx bx-cog bx-sm"></i>{" "}
|
||||||
|
Manage Role
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* If INACTIVE employee AND inactive toggle is ON */}
|
||||||
|
{!item.isActive && showInactive && (
|
||||||
|
<button
|
||||||
|
className="dropdown-item py-1"
|
||||||
|
onClick={() => handleOpenDelete(item)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-refresh bx-sm me-1"></i>{" "}
|
||||||
|
Re-activate
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{displayData.length > 0 && (
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
totalPages={totalPages}
|
||||||
|
onPageChange={paginate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -161,8 +161,8 @@ const MasterPage = () => {
|
|||||||
data={[{ label: "Home", link: "/dashboard" }, { label: "Masters" }]}
|
data={[{ label: "Home", link: "/dashboard" }, { label: "Masters" }]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="row">
|
||||||
<div className="card page-min-h">
|
<div className="card">
|
||||||
<div
|
<div
|
||||||
className="card-datatable table-responsive py-10 mx-5 "
|
className="card-datatable table-responsive py-10 mx-5 "
|
||||||
style={{ overflow: "hidden" }}
|
style={{ overflow: "hidden" }}
|
||||||
@ -221,6 +221,7 @@ const MasterPage = () => {
|
|||||||
handleModalData={handleModalData}
|
handleModalData={handleModalData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MasterContext.Provider>
|
</MasterContext.Provider>
|
||||||
|
|||||||
@ -109,11 +109,11 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
|
|||||||
{currentItems.length > 0 ? (
|
{currentItems.length > 0 ? (
|
||||||
currentItems.map((item, index) => (
|
currentItems.map((item, index) => (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td style={{ width: "20px", }} className="py-5">
|
<td style={{ width: "20px" }}>
|
||||||
<div className="py-2 px-3"><i className="bx bx-right-arrow-alt"></i></div>
|
<i className="bx bx-right-arrow-alt"></i>
|
||||||
</td>
|
</td>
|
||||||
{updatedColumns.map((col) => (
|
{updatedColumns.map((col) => (
|
||||||
<td className="text-start mx-2 p-3" key={col.key} style={{ padding: "12px 16px" }} >
|
<td className="text-start mx-2" key={col.key}>
|
||||||
{col.key === "description" ? (
|
{col.key === "description" ? (
|
||||||
item[col.key] !== undefined &&
|
item[col.key] !== undefined &&
|
||||||
item[col.key] !== null ? (
|
item[col.key] !== null ? (
|
||||||
|
|||||||
@ -32,104 +32,100 @@ export function startSignalR(loggedUser) {
|
|||||||
.toISOString()
|
.toISOString()
|
||||||
.split("T")[0];
|
.split("T")[0];
|
||||||
connection.on("NotificationEventHandler", (data) => {
|
connection.on("NotificationEventHandler", (data) => {
|
||||||
const { loggedInUserId, keyword, response, employeeList, numberOfImages } = data;
|
if (data.loggedInUserId != loggedUser?.employeeInfo.id) {
|
||||||
const loggedInId = loggedUser?.employeeInfo?.id;
|
// console.log("Notification received:", data);
|
||||||
|
// if action taken on attendance module
|
||||||
|
if (data.keyword == "Attendance") {
|
||||||
|
const checkIn = data.response.checkInTime.substring(0, 10);
|
||||||
|
if (today === checkIn) {
|
||||||
|
eventBus.emit("attendance", data);
|
||||||
|
}
|
||||||
|
var onlyDate = Number(checkIn.substring(8, 10));
|
||||||
|
|
||||||
if (loggedInUserId === loggedInId) return;
|
var afterTwoDay =
|
||||||
|
checkIn.substring(0, 8) + (onlyDate + 2).toString().padStart(2, "0");
|
||||||
|
if (
|
||||||
|
afterTwoDay <= today &&
|
||||||
|
(data.response.activity == 4 || data.response.activity == 5)
|
||||||
|
) {
|
||||||
|
eventBus.emit("regularization", data);
|
||||||
|
}
|
||||||
|
eventBus.emit("attendance_log", data);
|
||||||
|
}
|
||||||
|
if(data.keyword == "Expanse"){
|
||||||
|
queryClient.invalidateQueries({queryKey:["Expenses"]})
|
||||||
|
}
|
||||||
|
// if create or update project
|
||||||
|
if (
|
||||||
|
data.keyword == "Create_Project" ||
|
||||||
|
data.keyword == "Update_Project"
|
||||||
|
) {
|
||||||
|
// clearCacheKey("projectslist");
|
||||||
|
queryClient.invalidateQueries(['projectslist']);
|
||||||
|
eventBus.emit("project", data);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Handlers for invalidate or remove ----
|
// if assign or deassign employee to any project
|
||||||
const queryInvalidators = {
|
if (data.keyword == "Assign_Project") {
|
||||||
Expanse: () => {
|
if (
|
||||||
queryClient.invalidateQueries({ queryKey: ["Expenses"] }),
|
data.employeeList.some((item) => item === loggedUser?.employeeInfo.id)
|
||||||
queryClient.invalidateQueries({ queryKey: ["Expense"] })
|
) {
|
||||||
},
|
try {
|
||||||
Create_Project: () => queryClient.invalidateQueries(["projectslist"]),
|
cacheData("hasReceived", false);
|
||||||
Update_Project: () => queryClient.invalidateQueries(["projectslist"]),
|
eventBus.emit("assign_project_one", data);
|
||||||
Infra: () => queryClient.removeQueries({ queryKey: ["ProjectInfra"] }),
|
} catch (e) {
|
||||||
Task_Report: () => queryClient.invalidateQueries({ queryKey: ["Infra"] }),
|
// console.error("Error in cacheData:", e);
|
||||||
WorkItem: () => queryClient.invalidateQueries({ queryKey: ["WorkItems"] }),
|
}
|
||||||
Directory_Notes:()=>{
|
}
|
||||||
queryClient.invalidateQueries({queryKey:["directoryNotes"]})
|
eventBus.emit("assign_project_all", data);
|
||||||
queryClient.invalidateQueries({queryKey:["Notes"]})
|
}
|
||||||
},
|
// if created or updated infra
|
||||||
Directory_Buckets:()=>{
|
if (data.keyword == "Infra") {
|
||||||
queryClient.invalidateQueries({queryKey:["bucketList"]})
|
queryClient.removeQueries({queryKey:["ProjectInfra"]})
|
||||||
},
|
// eventBus.emit("infra", data);
|
||||||
Directory:()=>{
|
}
|
||||||
queryClient.invalidateQueries({queryKey:["contacts"]})
|
if (data.keyword == "Task_Report") {
|
||||||
queryClient.invalidateQueries({queryKey:["Contact"]})
|
queryClient.removeQueries({queryKey:["Infra"]})
|
||||||
queryClient.invalidateQueries({queryKey:["ContactProfile"]})
|
// eventBus.emit("infra", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( data.keyword == "WorkItem" )
|
||||||
|
{
|
||||||
|
queryClient.removeQueries({queryKey:["WorkItems"]})
|
||||||
|
}
|
||||||
|
|
||||||
|
// if created or updated Employee
|
||||||
|
if (data.keyword == "Employee") {
|
||||||
|
// clearCacheKey("employeeListByProject");
|
||||||
|
// clearCacheKey("allEmployeeList");
|
||||||
|
// clearCacheKey("allInactiveEmployeeList");
|
||||||
|
// clearCacheKey("employeeProfile");
|
||||||
|
clearCacheKey("Attendance");
|
||||||
|
clearCacheKey("regularizedList")
|
||||||
|
clearCacheKey("AttendanceLogs")
|
||||||
|
|
||||||
|
// ---we can do also----
|
||||||
|
// queryClient.removeQueries(['allEmployee', true]);
|
||||||
|
// but best practies is refetch
|
||||||
|
queryClient.invalidateQueries(['allEmployee', true]);
|
||||||
|
queryClient.invalidateQueries(['allEmployee', false]);
|
||||||
|
queryClient.invalidateQueries(['employeeProfile', data.response?.employeeId]);
|
||||||
|
queryClient.invalidateQueries(['employeeListByProject']); // optional if scope
|
||||||
|
eventBus.emit("employee", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.keyword == "Task_Report") {
|
||||||
|
if(data.numberOfImages > 0){
|
||||||
|
eventBus.emit("image_gallery", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.keyword == "Task_Comment") {
|
||||||
|
if(data.numberOfImages > 0){
|
||||||
|
eventBus.emit("image_gallery", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
};
|
|
||||||
|
|
||||||
// ---- Keyword based event emitters ----
|
|
||||||
const emitters = {
|
|
||||||
employee: () => eventBus.emit("employee", data),
|
|
||||||
project: () => eventBus.emit("project", data),
|
|
||||||
infra: () => eventBus.emit("infra", data),
|
|
||||||
assign_project_all: () => eventBus.emit("assign_project_all", data),
|
|
||||||
image_gallery: () => eventBus.emit("image_gallery", data),
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---- Handle Attendance ----
|
|
||||||
if (keyword === "Attendance") {
|
|
||||||
const checkIn = response.checkInTime.substring(0, 10);
|
|
||||||
if (today === checkIn) eventBus.emit("attendance", data);
|
|
||||||
|
|
||||||
const onlyDate = Number(checkIn.substring(8, 10));
|
|
||||||
const afterTwoDay =
|
|
||||||
checkIn.substring(0, 8) + (onlyDate + 2).toString().padStart(2, "0");
|
|
||||||
|
|
||||||
if (
|
|
||||||
afterTwoDay <= today &&
|
|
||||||
(response.activity === 4 || response.activity === 5)
|
|
||||||
) {
|
|
||||||
eventBus.emit("regularization", data);
|
|
||||||
}
|
|
||||||
eventBus.emit("attendance_log", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Invalidate/Remove cache by keywords ----
|
|
||||||
if (queryInvalidators[keyword]) {
|
|
||||||
queryInvalidators[keyword]();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Project creation/update ----
|
|
||||||
if (keyword === "Create_Project" || keyword === "Update_Project") {
|
|
||||||
emitters.project();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Assign/deassign project ----
|
|
||||||
if (keyword === "Assign_Project") {
|
|
||||||
if (employeeList?.includes(loggedInId)) {
|
|
||||||
try {
|
|
||||||
cacheData("hasReceived", false);
|
|
||||||
eventBus.emit("assign_project_one", data);
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
emitters.assign_project_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Employee update ----
|
|
||||||
if (keyword === "Employee") {
|
|
||||||
clearCacheKey("Attendance");
|
|
||||||
clearCacheKey("regularizedList");
|
|
||||||
clearCacheKey("AttendanceLogs");
|
|
||||||
|
|
||||||
queryClient.invalidateQueries(["allEmployee", true]);
|
|
||||||
queryClient.invalidateQueries(["allEmployee", false]);
|
|
||||||
queryClient.invalidateQueries(["employeeProfile", response?.employeeId]);
|
|
||||||
queryClient.invalidateQueries(["employeeListByProject"]);
|
|
||||||
|
|
||||||
emitters.employee();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Image related events ----
|
|
||||||
if (["Task_Report", "Task_Comment"].includes(keyword) && numberOfImages > 0) {
|
|
||||||
emitters.image_gallery();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connection
|
connection
|
||||||
.start();
|
.start();
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { parseISO, formatISO } from "date-fns";
|
import { format, parseISO } from "date-fns";
|
||||||
export const formatFileSize = (bytes) => {
|
export const formatFileSize = (bytes) => {
|
||||||
if (bytes < 1024) return bytes + " B";
|
if (bytes < 1024) return bytes + " B";
|
||||||
else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
|
else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
|
||||||
@ -72,11 +72,12 @@ export const normalizeAllowedContentTypes = (allowedContentType) => {
|
|||||||
export function localToUtc(localDateString) {
|
export function localToUtc(localDateString) {
|
||||||
if (!localDateString || typeof localDateString !== "string") return null;
|
if (!localDateString || typeof localDateString !== "string") return null;
|
||||||
|
|
||||||
|
|
||||||
const [year, month, day] = localDateString.trim().split("-");
|
const [year, month, day] = localDateString.trim().split("-");
|
||||||
|
|
||||||
if (!year || !month || !day) return null;
|
if (!year || !month || !day) return null;
|
||||||
|
|
||||||
const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0));
|
|
||||||
|
const date = new Date(Number(year), Number(month) - 1, Number(day), 0, 0, 0);
|
||||||
|
|
||||||
return isNaN(date.getTime()) ? null : date.toISOString();
|
return isNaN(date.getTime()) ? null : date.toISOString();
|
||||||
}
|
}
|
||||||
@ -1,8 +1,3 @@
|
|||||||
export const BASE_URL = process.env.VITE_BASE_URL;
|
|
||||||
|
|
||||||
// export const BASE_URL = "https://api.marcoaiot.com";
|
|
||||||
|
|
||||||
|
|
||||||
export const THRESH_HOLD = 48; // hours
|
export const THRESH_HOLD = 48; // hours
|
||||||
export const DURATION_TIME = 10; // minutes
|
export const DURATION_TIME = 10; // minutes
|
||||||
export const ITEMS_PER_PAGE = 20;
|
export const ITEMS_PER_PAGE = 20;
|
||||||
@ -145,17 +140,8 @@ export const PROJECT_STATUS = [
|
|||||||
label: "Completed",
|
label: "Completed",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
export const UUID_REGEX =
|
|
||||||
/^\/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}$/;
|
|
||||||
|
|
||||||
export const ALLOW_PROJECTSTATUS_ID = [
|
|
||||||
"603e994b-a27f-4e5d-a251-f3d69b0498ba",
|
|
||||||
"cdad86aa-8a56-4ff4-b633-9c629057dfef",
|
|
||||||
"b74da4c2-d07e-46f2-9919-e75e49b12731",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000";
|
export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000";
|
||||||
|
|
||||||
|
export const BASE_URL = process.env.VITE_BASE_URL;
|
||||||
|
|
||||||
|
// export const BASE_URL = "https://api.marcoaiot.com";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user