Compare commits

..

No commits in common. "3559b0a01c50068fbf90f9f838743ae6e6d92fc3" and "edebc2e471d31adb973b3d25b72ea4f1cd91b480" have entirely different histories.

38 changed files with 1127 additions and 968 deletions

View File

@ -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)}
>
&laquo;
</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)}
>
&raquo;
</button>
</li>
</ul>
</nav>
)} )}
</> </>
) : ( ) : (

View File

@ -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)}
>
&laquo;
</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)}
>
&raquo;
</button>
</li>
</ul>
</nav>
)} )}
</> </>
); );

View File

@ -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

View File

@ -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)}
>
&laquo;
</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)}
>
&raquo;
</button>
</li>
</ul>
</nav>
)} )}
</div> </div>
); );

View File

@ -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>
))} ))}
@ -152,4 +202,4 @@ const AttendanceOverview = () => {
); );
}; };
export default AttendanceOverview; export default AttendanceOverview;

View File

@ -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);
@ -44,4 +43,4 @@ const Dashboard = () => {
); );
}; };
export default Dashboard; export default Dashboard;

View File

@ -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 && (

View File

@ -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>

View File

@ -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>

View File

@ -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>
); );
}; };

View File

@ -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 */}

View File

@ -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}

View File

@ -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"

View File

@ -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>
</> </>
); );

View File

@ -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">

View File

@ -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>

View File

@ -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,
}; };

View File

@ -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">
@ -409,7 +410,7 @@ const ViewExpense = ({ ExpenseId }) => {
<EmployeeSearchInput <EmployeeSearchInput
control={control} control={control}
name="reimburseById" name="reimburseById"
projectId={null} projectId={null}
/> />
</div> </div>
</div> </div>

View File

@ -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>

View File

@ -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>

View File

@ -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}
&laquo; />
</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))}
>
&laquo;
</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} &raquo;
</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))
}
>
&raquo;
</button>
</li>
</ul>
</nav>
)}
</div>
);
};
export default ProjectCardView; export default ProjectCardView

View File

@ -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>

View File

@ -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 () => {

View File

@ -4,26 +4,21 @@ 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 = [],
label = "Select options", label = "Select options",
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) => {

View File

@ -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"

View File

@ -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----------------------------

View File

@ -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();
}, },

View File

@ -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) => {

View File

@ -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();

View File

@ -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}

View File

@ -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">

View File

@ -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>

View File

@ -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,16 +204,22 @@ 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]
); );
useEffect(() => { useEffect(() => {
@ -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>
) : ( ) : (

View File

@ -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>

View File

@ -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 ? (

View File

@ -22,7 +22,7 @@ export function startSignalR(loggedUser) {
accessTokenFactory: () => jwtToken, accessTokenFactory: () => jwtToken,
transport: signalR.HttpTransportType.LongPolling, transport: signalR.HttpTransportType.LongPolling,
withCredentials: false, withCredentials: false,
}) })
.withAutomaticReconnect() .withAutomaticReconnect()
.build(); .build();
const todayDate = new Date(); const todayDate = new Date();
@ -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();

View File

@ -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();
} }

View File

@ -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";