Compare commits
26 Commits
main
...
Issues_Oct
Author | SHA1 | Date | |
---|---|---|---|
8460460caf | |||
c8273070ac | |||
dc4e48ad3b | |||
20b508bebc | |||
05c01d1d34 | |||
073897156e | |||
3a2fcf71ee | |||
f0c6aea55d | |||
278a5651fe | |||
6fc3e674e5 | |||
9f4d82eb06 | |||
4ba0c823c0 | |||
acf6a28191 | |||
98c90f2a9b | |||
b80af5467c | |||
9592108472 | |||
3b032b7b07 | |||
b8891d403f | |||
01568db61c | |||
80a974e3be | |||
f3e05a11d6 | |||
222e6495a8 | |||
18a3b8a85b | |||
d75296ffe8 | |||
6649cab6a2 | |||
eab23389ed |
@ -280,3 +280,7 @@
|
|||||||
.w-8-xl{ width: 2rem; }
|
.w-8-xl{ width: 2rem; }
|
||||||
.w-10-xl{ width: 2.5rem; }
|
.w-10-xl{ width: 2.5rem; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cursor-not-allowed{
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
@ -126,7 +126,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
|
|||||||
checked={ShowPending}
|
checked={ShowPending}
|
||||||
onChange={(e) => setShowPending(e.target.checked)}
|
onChange={(e) => setShowPending(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label ms-0">Show Pending</label>
|
<label className="form-check-label ms-0">Pending Attendance</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{attLoading ? (
|
{attLoading ? (
|
||||||
@ -223,50 +223,6 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
|
|||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
|
|
||||||
<nav aria-label="Page ">
|
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
|
||||||
<li
|
|
||||||
className={`page-item ${currentPage === 1 ? "disabled" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="page-link btn-xs"
|
|
||||||
onClick={() => paginate(currentPage - 1)}
|
|
||||||
>
|
|
||||||
«
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{[...Array(totalPages)].map((_, index) => (
|
|
||||||
<li
|
|
||||||
key={index}
|
|
||||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="page-link "
|
|
||||||
onClick={() => paginate(index + 1)}
|
|
||||||
>
|
|
||||||
{index + 1}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
<li
|
|
||||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="page-link "
|
|
||||||
onClick={() => paginate(currentPage + 1)}
|
|
||||||
>
|
|
||||||
»
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
@ -281,6 +237,48 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
|
||||||
|
<nav aria-label="Page ">
|
||||||
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
|
<li
|
||||||
|
className={`page-item ${currentPage === 1 ? "disabled" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="page-link btn-xs"
|
||||||
|
onClick={() => paginate(currentPage - 1)}
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{[...Array(totalPages)].map((_, index) => (
|
||||||
|
<li
|
||||||
|
key={index}
|
||||||
|
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="page-link "
|
||||||
|
onClick={() => paginate(index + 1)}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
<li
|
||||||
|
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="page-link "
|
||||||
|
onClick={() => paginate(currentPage + 1)}
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ 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 { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const usePagination = (data, itemsPerPage) => {
|
const usePagination = (data, itemsPerPage) => {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
@ -38,149 +39,150 @@ const usePagination = (data, itemsPerPage) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [showPending, setShowPending] = useState(false);
|
const [showPending, setShowPending] = useState(false);
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
const yesterday = new Date();
|
const yesterday = new Date();
|
||||||
yesterday.setDate(yesterday.getDate() - 1);
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
|
||||||
const isSameDay = (dateStr) => {
|
const isSameDay = (dateStr) => {
|
||||||
if (!dateStr) return false;
|
if (!dateStr) return false;
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
d.setHours(0, 0, 0, 0);
|
d.setHours(0, 0, 0, 0);
|
||||||
return d.getTime() === today.getTime();
|
return d.getTime() === today.getTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isBeforeToday = (dateStr) => {
|
const isBeforeToday = (dateStr) => {
|
||||||
if (!dateStr) return false;
|
if (!dateStr) return false;
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
d.setHours(0, 0, 0, 0);
|
d.setHours(0, 0, 0, 0);
|
||||||
return d.getTime() < today.getTime();
|
return d.getTime() < today.getTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortByName = (a, b) => {
|
const sortByName = (a, b) => {
|
||||||
const nameA = (a.firstName + a.lastName).toLowerCase();
|
const nameA = (a.firstName + a.lastName).toLowerCase();
|
||||||
const nameB = (b.firstName + b.lastName).toLowerCase();
|
const nameB = (b.firstName + b.lastName).toLowerCase();
|
||||||
return nameA.localeCompare(nameB);
|
return nameA.localeCompare(nameB);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data = [], isLoading, error, refetch, isFetching } = useAttendancesLogs(
|
const { data = [], isLoading, error, refetch, isFetching } = useAttendancesLogs(
|
||||||
selectedProject,
|
selectedProject,
|
||||||
dateRange.startDate,
|
dateRange.startDate,
|
||||||
dateRange.endDate,
|
dateRange.endDate,
|
||||||
organizationId
|
organizationId
|
||||||
);
|
|
||||||
|
|
||||||
const processedData = useMemo(() => {
|
|
||||||
const filteredData = showPending
|
|
||||||
? data.filter((item) => item.checkOutTime === null)
|
|
||||||
: data;
|
|
||||||
|
|
||||||
const group1 = filteredData.filter((d) => d.activity === 1 && isSameDay(d.checkInTime)).sort(sortByName);
|
|
||||||
const group2 = filteredData.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)).sort(sortByName);
|
|
||||||
const group3 = filteredData.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime)).sort(sortByName);
|
|
||||||
const group4 = filteredData.filter((d) => d.activity === 4 && isBeforeToday(d.checkOutTime));
|
|
||||||
const group5 = filteredData.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime)).sort(sortByName);
|
|
||||||
const group6 = filteredData.filter((d) => d.activity === 5).sort(sortByName);
|
|
||||||
|
|
||||||
const sortedList = [...group1, ...group2, ...group3, ...group4, ...group5, ...group6];
|
|
||||||
|
|
||||||
const groupedByDate = sortedList.reduce((acc, item) => {
|
|
||||||
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
|
|
||||||
if (date) {
|
|
||||||
acc[date] = acc[date] || [];
|
|
||||||
acc[date].push(item);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a));
|
|
||||||
return sortedDates.flatMap((date) => groupedByDate[date]);
|
|
||||||
}, [data, showPending]);
|
|
||||||
|
|
||||||
const filteredSearchData = useMemo(() => {
|
|
||||||
if (!searchTerm) return processedData;
|
|
||||||
|
|
||||||
const lowercased = searchTerm.toLowerCase();
|
|
||||||
return processedData.filter((item) =>
|
|
||||||
`${item.firstName} ${item.lastName}`.toLowerCase().includes(lowercased)
|
|
||||||
);
|
);
|
||||||
}, [processedData, searchTerm]);
|
|
||||||
|
|
||||||
const {
|
const processedData = useMemo(() => {
|
||||||
currentPage,
|
const filteredData = showPending
|
||||||
totalPages,
|
? data.filter((item) => item.checkOutTime === null)
|
||||||
currentItems: paginatedAttendances,
|
: data;
|
||||||
paginate,
|
|
||||||
resetPage,
|
|
||||||
} = usePagination(filteredSearchData, 20);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const group1 = filteredData.filter((d) => d.activity === 1 && isSameDay(d.checkInTime)).sort(sortByName);
|
||||||
resetPage();
|
const group2 = filteredData.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)).sort(sortByName);
|
||||||
}, [filteredSearchData]);
|
const group3 = filteredData.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime)).sort(sortByName);
|
||||||
|
const group4 = filteredData.filter((d) => d.activity === 4 && isBeforeToday(d.checkOutTime));
|
||||||
|
const group5 = filteredData.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime)).sort(sortByName);
|
||||||
|
const group6 = filteredData.filter((d) => d.activity === 5).sort(sortByName);
|
||||||
|
|
||||||
const handler = useCallback(
|
const sortedList = [...group1, ...group2, ...group3, ...group4, ...group5, ...group6];
|
||||||
(msg) => {
|
|
||||||
const { startDate, endDate } = dateRange;
|
|
||||||
const checkIn = msg.response.checkInTime.substring(0, 10);
|
|
||||||
|
|
||||||
if (selectedProject === msg.projectId && startDate <= checkIn && checkIn <= endDate) {
|
const groupedByDate = sortedList.reduce((acc, item) => {
|
||||||
queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
|
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
|
||||||
if (!oldData) {
|
if (date) {
|
||||||
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
|
acc[date] = acc[date] || [];
|
||||||
return;
|
acc[date].push(item);
|
||||||
}
|
}
|
||||||
return oldData.map((record) =>
|
return acc;
|
||||||
record.id === msg.response.id ? { ...record, ...msg.response } : record
|
}, {});
|
||||||
);
|
|
||||||
});
|
|
||||||
resetPage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[selectedProject, dateRange, resetPage]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a));
|
||||||
eventBus.on("attendance_log", handler);
|
return sortedDates.flatMap((date) => groupedByDate[date]);
|
||||||
return () => eventBus.off("attendance_log", handler);
|
}, [data, showPending]);
|
||||||
}, [handler]);
|
|
||||||
|
|
||||||
const employeeHandler = useCallback(
|
const filteredSearchData = useMemo(() => {
|
||||||
(msg) => {
|
if (!searchTerm) return processedData;
|
||||||
const { startDate, endDate } = dateRange;
|
|
||||||
if (data.some((item) => item.employeeId == msg.employeeId)) {
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[data, refetch]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const lowercased = searchTerm.toLowerCase();
|
||||||
eventBus.on("employee", employeeHandler);
|
return processedData.filter((item) =>
|
||||||
return () => eventBus.off("employee", employeeHandler);
|
`${item.firstName} ${item.lastName}`.toLowerCase().includes(lowercased)
|
||||||
}, [employeeHandler]);
|
);
|
||||||
|
}, [processedData, searchTerm]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
currentItems: paginatedAttendances,
|
||||||
|
paginate,
|
||||||
|
resetPage,
|
||||||
|
} = usePagination(filteredSearchData, 20);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
resetPage();
|
||||||
|
}, [filteredSearchData]);
|
||||||
|
|
||||||
|
const handler = useCallback(
|
||||||
|
(msg) => {
|
||||||
|
const { startDate, endDate } = dateRange;
|
||||||
|
const checkIn = msg.response.checkInTime.substring(0, 10);
|
||||||
|
|
||||||
|
if (selectedProject === msg.projectId && startDate <= checkIn && checkIn <= endDate) {
|
||||||
|
queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
|
||||||
|
if (!oldData) {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return oldData.map((record) =>
|
||||||
|
record.id === msg.response.id ? { ...record, ...msg.response } : record
|
||||||
|
);
|
||||||
|
});
|
||||||
|
resetPage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selectedProject, dateRange, resetPage]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
eventBus.on("attendance_log", handler);
|
||||||
|
return () => eventBus.off("attendance_log", handler);
|
||||||
|
}, [handler]);
|
||||||
|
|
||||||
|
const employeeHandler = useCallback(
|
||||||
|
(msg) => {
|
||||||
|
const { startDate, endDate } = dateRange;
|
||||||
|
if (data.some((item) => item.employeeId == msg.employeeId)) {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[data, refetch]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
eventBus.on("employee", employeeHandler);
|
||||||
|
return () => eventBus.off("employee", employeeHandler);
|
||||||
|
}, [employeeHandler]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="dataTables_length text-start py-2 d-flex justify-content-between "
|
className="dataTables_length text-start py-2 d-flex flex-wrap justify-content-between"
|
||||||
id="DataTables_Table_0_length"
|
id="DataTables_Table_0_length"
|
||||||
>
|
>
|
||||||
<div className="d-flex align-items-center my-0 ">
|
<div className="d-flex flex-wrap align-items-center my-0 gap-2">
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
onRangeChange={setDateRange}
|
onRangeChange={setDateRange}
|
||||||
defaultStartDate={yesterday}
|
defaultStartDate={yesterday}
|
||||||
/>
|
/>
|
||||||
<div className="form-check form-switch text-start ms-1 ms-md-2 align-items-center mb-0">
|
<div className="form-check form-switch text-start d-flex align-items-center mb-0">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="form-check-input"
|
className="form-check-input"
|
||||||
@ -190,10 +192,11 @@ useEffect(() => {
|
|||||||
checked={showPending}
|
checked={showPending}
|
||||||
onChange={(e) => setShowPending(e.target.checked)}
|
onChange={(e) => setShowPending(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label ms-0">Show Pending</label>
|
<label className="form-check-label ms-2 mb-0">Pending Attendance</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="table-responsive text-nowrap"
|
className="table-responsive text-nowrap"
|
||||||
style={{ minHeight: "200px" }}
|
style={{ minHeight: "200px" }}
|
||||||
@ -232,9 +235,9 @@ useEffect(() => {
|
|||||||
const previousAttendance = arr[index - 1];
|
const previousAttendance = arr[index - 1];
|
||||||
const previousDate = previousAttendance
|
const previousDate = previousAttendance
|
||||||
? moment(
|
? moment(
|
||||||
previousAttendance.checkInTime ||
|
previousAttendance.checkInTime ||
|
||||||
previousAttendance.checkOutTime
|
previousAttendance.checkOutTime
|
||||||
).format("YYYY-MM-DD")
|
).format("YYYY-MM-DD")
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!previousDate || currentDate !== previousDate) {
|
if (!previousDate || currentDate !== previousDate) {
|
||||||
@ -260,7 +263,12 @@ useEffect(() => {
|
|||||||
lastName={attendance.lastName}
|
lastName={attendance.lastName}
|
||||||
/>
|
/>
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
<a href="#" className="text-heading text-truncate">
|
<a
|
||||||
|
onClick={() =>
|
||||||
|
navigate(`/employee/${attendance.employeeId}?for=attendance`)
|
||||||
|
}
|
||||||
|
className="text-heading text-truncate cursor-pointer"
|
||||||
|
>
|
||||||
<span className="fw-normal">
|
<span className="fw-normal">
|
||||||
{attendance.firstName} {attendance.lastName}
|
{attendance.firstName} {attendance.lastName}
|
||||||
</span>
|
</span>
|
||||||
@ -297,8 +305,7 @@ useEffect(() => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="my-12">
|
<div className="my-12">
|
||||||
<span className="text-secondary">
|
<span className="text-secondary">
|
||||||
No data available for the selected date range. Please Select
|
No attendance record found in selected date range.
|
||||||
another date.
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -326,9 +333,8 @@ useEffect(() => {
|
|||||||
(pageNumber) => (
|
(pageNumber) => (
|
||||||
<li
|
<li
|
||||||
key={pageNumber}
|
key={pageNumber}
|
||||||
className={`page-item ${
|
className={`page-item ${currentPage === pageNumber ? "active" : ""
|
||||||
currentPage === pageNumber ? "active" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
@ -340,9 +346,8 @@ useEffect(() => {
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
<li
|
<li
|
||||||
className={`page-item ${
|
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||||
currentPage === totalPages ? "disabled" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
|
@ -33,7 +33,7 @@ const InfraPlanning = () => {
|
|||||||
const selectedService = useCurrentService();
|
const selectedService = useCurrentService();
|
||||||
|
|
||||||
const { projectInfra, isLoading, isError, error, isFetched } =
|
const { projectInfra, isLoading, isError, error, isFetched } =
|
||||||
useProjectInfra(selectedProject, selectedService || "" );
|
useProjectInfra(selectedProject, selectedService || "");
|
||||||
|
|
||||||
const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
||||||
const canApproveTask = useHasUserPermission(APPROVE_TASK);
|
const canApproveTask = useHasUserPermission(APPROVE_TASK);
|
||||||
@ -62,9 +62,13 @@ const InfraPlanning = () => {
|
|||||||
|
|
||||||
if (isFetched && (!projectInfra || projectInfra.length === 0)) {
|
if (isFetched && (!projectInfra || projectInfra.length === 0)) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center">
|
<div
|
||||||
<p className="my-3">No Result Found</p>
|
className="text-center d-flex justify-content-center align-items-center text-muted"
|
||||||
|
style={{ minHeight: "40vh", fontSize: "0.9rem" }}
|
||||||
|
>
|
||||||
|
<p className="my-3 m-0">No Result Found</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
} from "../../slices/apiDataManager";
|
} from "../../slices/apiDataManager";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import Pagination from "../../components/common/Pagination";
|
import Pagination from "../../components/common/Pagination";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const Regularization = ({
|
const Regularization = ({
|
||||||
handleRequest,
|
handleRequest,
|
||||||
@ -26,6 +27,7 @@ const Regularization = ({
|
|||||||
// var selectedProject = useSelector((store) => store.localVariables.projectId);
|
// var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const [regularizesList, setregularizedList] = useState([]);
|
const [regularizesList, setregularizedList] = useState([]);
|
||||||
|
const navigate = useNavigate();
|
||||||
const { regularizes, loading, error, refetch } = useRegularizationRequests(
|
const { regularizes, loading, error, refetch } = useRegularizationRequests(
|
||||||
selectedProject,
|
selectedProject,
|
||||||
organizationId,
|
organizationId,
|
||||||
@ -33,9 +35,9 @@ const Regularization = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(!regularizes) return
|
if (!regularizes) return
|
||||||
if(regularizes?.length) {
|
if (regularizes?.length) {
|
||||||
setregularizedList(regularizes);
|
setregularizedList(regularizes);
|
||||||
|
|
||||||
}
|
}
|
||||||
}, [regularizes]);
|
}, [regularizes]);
|
||||||
@ -102,141 +104,106 @@ const Regularization = ({
|
|||||||
}, [employeeHandler]);
|
}, [employeeHandler]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div>
|
||||||
className="table-responsive text-nowrap pb-4"
|
<div
|
||||||
style={{ minHeight: "200px" }}
|
className="table-responsive pt-3 text-nowrap pb-4"
|
||||||
>
|
style={{ minHeight: "200px" }}
|
||||||
{loading ? (
|
>
|
||||||
<div
|
{loading ? (
|
||||||
className="d-flex justify-content-center align-items-center"
|
<div
|
||||||
style={{ height: "200px" }}
|
className="d-flex justify-content-center align-items-center"
|
||||||
>
|
style={{ height: "200px" }}
|
||||||
<p className="text-secondary">Loading...</p>
|
>
|
||||||
</div>
|
<p className="text-secondary">Loading...</p>
|
||||||
) : currentItems?.length > 0 ? (
|
</div>
|
||||||
<table className="table mb-0">
|
) : currentItems?.length > 0 ? (
|
||||||
<thead>
|
<table className="table mb-0">
|
||||||
<tr>
|
<thead>
|
||||||
<th colSpan={2}>Name</th>
|
<tr>
|
||||||
<th>Date</th>
|
<th colSpan={2}>Name</th>
|
||||||
<th>Organization</th>
|
<th>Date</th>
|
||||||
<th>
|
<th>Organization</th>
|
||||||
<i className="bx bxs-down-arrow-alt text-success"></i>Check-In
|
<th>
|
||||||
</th>
|
<i className="bx bxs-down-arrow-alt text-success"></i>Check-In
|
||||||
<th>
|
</th>
|
||||||
<i className="bx bxs-up-arrow-alt text-danger"></i>Check-Out
|
<th>
|
||||||
</th>
|
<i className="bx bxs-up-arrow-alt text-danger"></i>Check-Out
|
||||||
<th colSpan={2}>
|
</th>
|
||||||
Requested By
|
<th colSpan={2}>
|
||||||
</th>
|
Requested By
|
||||||
<th >
|
</th>
|
||||||
Requested At
|
<th >
|
||||||
</th>
|
Requested At
|
||||||
<th>Action</th>
|
</th>
|
||||||
</tr>
|
<th>Action</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{currentItems?.map((att, index) => (
|
<tbody>
|
||||||
<tr key={index}>
|
{currentItems?.map((att, index) => (
|
||||||
<td colSpan={2}>
|
<tr key={index}>
|
||||||
<div className="d-flex justify-content-start align-items-center">
|
<td colSpan={2}>
|
||||||
<Avatar firstName={att.firstName} lastName={att.lastName} />
|
<div className="d-flex justify-content-start align-items-center">
|
||||||
<div className="d-flex flex-column">
|
<Avatar firstName={att.firstName} lastName={att.lastName} />
|
||||||
<a href="#" className="text-heading text-truncate">
|
<div className="d-flex flex-column"> <a
|
||||||
|
onClick={() =>
|
||||||
|
navigate(`/employee/${att.employeeId}?for=attendance`)
|
||||||
|
}
|
||||||
|
className="text-heading text-truncate cursor-pointer" >
|
||||||
<span className="fw-normal">
|
<span className="fw-normal">
|
||||||
{att.firstName} {att.lastName}
|
{att.firstName} {att.lastName}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
|
||||||
{att.requestedAt ? convertShortTime(att.checkOutTime) : "--"}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td colSpan={2}>
|
|
||||||
{att.requestedBy ? ( <div className="d-flex justify-content-start align-items-center">
|
|
||||||
<Avatar firstName={att?.requestedBy?.firstName} lastName={att?.requestedBy?.lastName} />
|
|
||||||
<div className="d-flex flex-column">
|
|
||||||
<a href="#" className="text-heading text-truncate">
|
|
||||||
<span className="fw-normal">
|
|
||||||
{att?.requestedBy?.firstName} {att?.requestedBy?.lastName}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>):(<small>--</small>)}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
{att?.requestedAt ? formatUTCToLocalTime(att.requestedAt,true) : "--"}
|
{att.requestedAt ? convertShortTime(att.checkOutTime) : "--"}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-center ">
|
|
||||||
<RegularizationActions
|
|
||||||
attendanceData={att}
|
|
||||||
handleRequest={handleRequest}
|
|
||||||
refresh={refetch}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className="d-flex justify-content-center align-items-center"
|
|
||||||
style={{ height: "200px" }}
|
|
||||||
>
|
|
||||||
<span className="text-secondary">
|
|
||||||
{searchTerm
|
|
||||||
? "No results found for your search."
|
|
||||||
: "No Requests Found !"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* {!loading && totalPages > 1 && (
|
|
||||||
<nav aria-label="Page ">
|
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1 mt-3">
|
|
||||||
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
|
||||||
<button
|
|
||||||
className="page-link btn-xs"
|
|
||||||
onClick={() => paginate(currentPage - 1)}
|
|
||||||
>
|
|
||||||
«
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{[...Array(totalPages)].map((_, index) => (
|
|
||||||
<li
|
|
||||||
key={index}
|
|
||||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="page-link "
|
|
||||||
onClick={() => paginate(index + 1)}
|
|
||||||
>
|
|
||||||
{index + 1}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
<li
|
|
||||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="page-link "
|
|
||||||
onClick={() => paginate(currentPage + 1)}
|
|
||||||
>
|
|
||||||
»
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
)} */}
|
|
||||||
|
|
||||||
|
<td colSpan={2}>
|
||||||
|
{att.requestedBy ? (<div className="d-flex justify-content-start align-items-center">
|
||||||
|
<Avatar firstName={att?.requestedBy?.firstName} lastName={att?.requestedBy?.lastName} />
|
||||||
|
<div className="d-flex flex-column">
|
||||||
|
<a href="#" className="text-heading text-truncate">
|
||||||
|
<span className="fw-normal">
|
||||||
|
{att?.requestedBy?.firstName} {att?.requestedBy?.lastName}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>) : (<small>--</small>)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{att?.requestedAt ? formatUTCToLocalTime(att.requestedAt, true) : "--"}
|
||||||
|
</td>
|
||||||
|
<td className="text-center ">
|
||||||
|
<RegularizationActions
|
||||||
|
attendanceData={att}
|
||||||
|
handleRequest={handleRequest}
|
||||||
|
refresh={refetch}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="d-flex justify-content-center align-items-center"
|
||||||
|
style={{ height: "200px" }}
|
||||||
|
>
|
||||||
|
<span className="text-secondary">
|
||||||
|
{searchTerm
|
||||||
|
? "No results found for your search."
|
||||||
|
: "No Requests Found !"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{totalPages > 0 && (
|
{totalPages > 0 && (
|
||||||
<Pagination
|
<Pagination
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
@ -244,6 +211,7 @@ const Regularization = ({
|
|||||||
onPageChange={paginate}
|
onPageChange={paginate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -23,7 +23,7 @@ const HorizontalBarChart = ({
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-[380px] flex items-center justify-center bg-gray-100 rounded-xl">
|
<div className="w-full h-[380px] flex items-center justify-center bg-gray-100 rounded-xl">
|
||||||
<span className="text-gray-500 text-sm">Loading chart...</span>
|
<span className="text-gray-500">Loading chart...</span>
|
||||||
{/* Replace this with a skeleton or spinner if you prefer */}
|
{/* Replace this with a skeleton or spinner if you prefer */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -29,7 +29,7 @@ const TaskReportList = () => {
|
|||||||
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
|
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
|
||||||
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
|
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
|
||||||
|
|
||||||
const { service, openModal, closeModal,filter } = useDailyProgrssContext();
|
const { service, openModal, closeModal, filter } = useDailyProgrssContext();
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
const { projectNames } = useProjectName();
|
const { projectNames } = useProjectName();
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ const TaskReportList = () => {
|
|||||||
selectedProject,
|
selectedProject,
|
||||||
ITEMS_PER_PAGE,
|
ITEMS_PER_PAGE,
|
||||||
currentPage,
|
currentPage,
|
||||||
service,filter
|
service, filter
|
||||||
);
|
);
|
||||||
|
|
||||||
const ProgrssReportColumn = [
|
const ProgrssReportColumn = [
|
||||||
@ -192,109 +192,114 @@ const TaskReportList = () => {
|
|||||||
if (isLoading) return <TaskReportListSkeleton />;
|
if (isLoading) return <TaskReportListSkeleton />;
|
||||||
if (isError) return <div>Loading....</div>;
|
if (isError) return <div>Loading....</div>;
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 table-responsive text-nowrap">
|
<div>
|
||||||
<table className="table">
|
<div className="mt-2 table-responsive text-nowrap">
|
||||||
<thead>
|
<table className="table">
|
||||||
<tr>
|
<thead>
|
||||||
<th className="text-start">Activity</th>
|
|
||||||
<th>
|
|
||||||
<span>
|
|
||||||
Total Pending{" "}
|
|
||||||
<HoverPopup
|
|
||||||
title="Total Pending Task"
|
|
||||||
content={<p>This shows the total pending tasks for each activity on that date.</p>}
|
|
||||||
>
|
|
||||||
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
|
|
||||||
</HoverPopup>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<span>
|
|
||||||
Reported/Planned{" "}
|
|
||||||
<HoverPopup
|
|
||||||
title="Reported and Planned Task"
|
|
||||||
content={<p>This shows the reported versus planned tasks for each activity on that date.</p>}
|
|
||||||
>
|
|
||||||
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
|
|
||||||
</HoverPopup>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
<th>Assign Date</th>
|
|
||||||
<th>Team</th>
|
|
||||||
<th className="text-center">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{groupedTasks.length === 0 && (
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="text-center align-middle" style={{ height: "200px", borderBottom: "none" }}>
|
<th className="text-start">Activity</th>
|
||||||
No reports available
|
<th>
|
||||||
</td>
|
<span>
|
||||||
|
Total Pending{" "}
|
||||||
|
<HoverPopup
|
||||||
|
title="Total Pending Task"
|
||||||
|
content={<p>This shows the total pending tasks for each activity on that date.</p>}
|
||||||
|
>
|
||||||
|
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
|
||||||
|
</HoverPopup>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<span>
|
||||||
|
Reported/Planned{" "}
|
||||||
|
<HoverPopup
|
||||||
|
title="Reported and Planned Task"
|
||||||
|
content={<p>This shows the reported versus planned tasks for each activity on that date.</p>}
|
||||||
|
>
|
||||||
|
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
|
||||||
|
</HoverPopup>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th>Assign Date</th>
|
||||||
|
<th>Team</th>
|
||||||
|
<th className="text-center">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
</thead>
|
||||||
|
<tbody>
|
||||||
{groupedTasks.map(({ date, tasks }) => (
|
{groupedTasks.length === 0 && (
|
||||||
<React.Fragment key={date}>
|
<tr>
|
||||||
<tr className="table-row-header text-start">
|
<td colSpan={6} className="text-center align-middle" style={{ height: "200px", borderBottom: "none" }}>
|
||||||
<td colSpan={6}>
|
No reports available
|
||||||
<strong>{formatUTCToLocalTime(date)}</strong>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{tasks.map((task, idx) => (
|
)}
|
||||||
<tr key={task.id || idx}>
|
|
||||||
<td className="flex-wrap text-start">
|
{groupedTasks.map(({ date, tasks }) => (
|
||||||
<div>
|
<React.Fragment key={date}>
|
||||||
{task.workItem.activityMaster?.activityName || "No Activity Name"}
|
<tr className="table-row-header text-start">
|
||||||
</div>
|
<td colSpan={6}>
|
||||||
<div className="text-sm py-2">
|
<strong>{formatUTCToLocalTime(date)}</strong>
|
||||||
{task.workItem.workArea?.floor?.building?.name} ›{" "}
|
|
||||||
{task.workItem.workArea?.floor?.floorName} ›{" "}
|
|
||||||
{task.workItem.workArea?.areaName}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{formatNumber(task.workItem.plannedWork)}
|
|
||||||
</td>
|
|
||||||
<td>{`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}</td>
|
|
||||||
<td>{formatUTCToLocalTime(task.assignmentDate)}</td>
|
|
||||||
<td className="text-center">{renderTeamMembers(task, idx)}</td>
|
|
||||||
<td className="text-center">
|
|
||||||
<div className="d-flex justify-content-end gap-2">
|
|
||||||
{ReportTaskRights && !task.reportedDate && (
|
|
||||||
<button className="btn btn-xs btn-primary" onClick={() => openModal("report", task)}>
|
|
||||||
Report
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{ApprovedTaskRights && task.reportedDate && !task.approvedBy && (
|
|
||||||
<button
|
|
||||||
className="btn btn-xs btn-warning"
|
|
||||||
onClick={() => openModal("comments", { task, isActionAllow: true })}
|
|
||||||
>
|
|
||||||
QC
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
className="btn btn-xs btn-primary"
|
|
||||||
onClick={() => openModal("comments", { task, isActionAllow: false })}
|
|
||||||
>
|
|
||||||
Comment
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
{tasks.map((task, idx) => (
|
||||||
</React.Fragment>
|
<tr key={task.id || idx}>
|
||||||
))}
|
<td className="flex-wrap text-start">
|
||||||
</tbody>
|
<div>
|
||||||
</table>
|
{task.workItem.activityMaster?.activityName || "No Activity Name"}
|
||||||
{data?.data?.length > 0 && (
|
</div>
|
||||||
<Pagination
|
<div className="text-sm py-2">
|
||||||
currentPage={currentPage}
|
{task.workItem.workArea?.floor?.building?.name} ›{" "}
|
||||||
totalPages={data.totalPages}
|
{task.workItem.workArea?.floor?.floorName} ›{" "}
|
||||||
onPageChange={paginate}
|
{task.workItem.workArea?.areaName}
|
||||||
/>
|
</div>
|
||||||
)}
|
</td>
|
||||||
</div>
|
<td>
|
||||||
|
{formatNumber(task.workItem.plannedWork)}
|
||||||
|
</td>
|
||||||
|
<td>{`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}</td>
|
||||||
|
<td>{formatUTCToLocalTime(task.assignmentDate)}</td>
|
||||||
|
<td className="text-center">{renderTeamMembers(task, idx)}</td>
|
||||||
|
<td className="text-center">
|
||||||
|
<div className="d-flex justify-content-end gap-2">
|
||||||
|
{ReportTaskRights && !task.reportedDate && (
|
||||||
|
<button className="btn btn-xs btn-primary" onClick={() => openModal("report", task)}>
|
||||||
|
Report
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{ApprovedTaskRights && task.reportedDate && !task.approvedBy && (
|
||||||
|
<button
|
||||||
|
className="btn btn-xs btn-warning"
|
||||||
|
onClick={() => openModal("comments", { task, isActionAllow: true })}
|
||||||
|
>
|
||||||
|
QC
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="btn btn-xs btn-primary"
|
||||||
|
onClick={() => openModal("comments", { task, isActionAllow: false })}
|
||||||
|
>
|
||||||
|
Comment
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
data?.data?.length > 0 && (
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
totalPages={data.totalPages}
|
||||||
|
onPageChange={paginate}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div >
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@ import HorizontalBarChart from "../Charts/HorizontalBarChart";
|
|||||||
import { useProjects } from "../../hooks/useProjects";
|
import { useProjects } from "../../hooks/useProjects";
|
||||||
|
|
||||||
const ProjectCompletionChart = () => {
|
const ProjectCompletionChart = () => {
|
||||||
const { projects, loading } = useProjects();
|
const { data: projects = [], isLoading: loading, isError, error } = useProjects();
|
||||||
|
|
||||||
|
|
||||||
// Bar chart logic
|
// Bar chart logic
|
||||||
const projectNames = projects?.map((p) => p.name) || [];
|
const projectNames = projects?.map((p) => p.name) || [];
|
||||||
@ -11,7 +12,7 @@ const ProjectCompletionChart = () => {
|
|||||||
projects?.map((p) => {
|
projects?.map((p) => {
|
||||||
const completed = p.completedWork || 0;
|
const completed = p.completedWork || 0;
|
||||||
const planned = p.plannedWork || 1;
|
const planned = p.plannedWork || 1;
|
||||||
const percent = (completed / planned) * 100;
|
const percent = planned ? (completed / planned) * 100 : 0;
|
||||||
return Math.min(Math.round(percent), 100);
|
return Math.min(Math.round(percent), 100);
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
|
@ -160,8 +160,7 @@ const ListViewContact = ({ data, Pagination }) => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<i
|
<i
|
||||||
className={`bx ${
|
className={`bx ${isPending && activeContact === row.id
|
||||||
isPending && activeContact === row.id
|
|
||||||
? "bx-loader-alt bx-spin"
|
? "bx-loader-alt bx-spin"
|
||||||
: "bx-recycle"
|
: "bx-recycle"
|
||||||
} me-1 text-primary cursor-pointer`}
|
} me-1 text-primary cursor-pointer`}
|
||||||
@ -188,13 +187,13 @@ const ListViewContact = ({ data, Pagination }) => {
|
|||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{Pagination && (
|
|
||||||
<div className="d-flex justify-content-start">
|
|
||||||
{Pagination}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{Pagination && (
|
||||||
|
<div className="d-flex justify-content-start">
|
||||||
|
{Pagination}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useDocumentFilterEntities } from "../../hooks/useDocument";
|
import { useDocumentFilterEntities } from "../../hooks/useDocument";
|
||||||
import { FormProvider, useForm } from "react-hook-form";
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@ -9,9 +9,11 @@ import {
|
|||||||
import { DateRangePicker1 } from "../common/DateRangePicker";
|
import { DateRangePicker1 } from "../common/DateRangePicker";
|
||||||
import SelectMultiple from "../common/SelectMultiple";
|
import SelectMultiple from "../common/SelectMultiple";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
||||||
const [resetKey, setResetKey] = useState(0);
|
const [resetKey, setResetKey] = useState(0);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const { data, isError, isLoading, error } =
|
const { data, isError, isLoading, error } =
|
||||||
useDocumentFilterEntities(entityTypeId);
|
useDocumentFilterEntities(entityTypeId);
|
||||||
@ -52,6 +54,13 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
|||||||
closePanel();
|
closePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Close popup when navigating to another component
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (isLoading) return <div>Loading...</div>;
|
if (isLoading) return <div>Loading...</div>;
|
||||||
if (isError)
|
if (isError)
|
||||||
return <div>Error: {error?.message || "Something went wrong!"}</div>;
|
return <div>Error: {error?.message || "Something went wrong!"}</div>;
|
||||||
@ -63,6 +72,8 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
|||||||
documentTag = [],
|
documentTag = [],
|
||||||
} = data?.data || {};
|
} = data?.data || {};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
@ -73,18 +84,16 @@ const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
|||||||
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
className={`btn px-2 py-1 rounded-0 text-tiny ${isUploadedAt ? "active btn-secondary text-white" : ""
|
||||||
isUploadedAt ? "active btn-secondary text-white" : ""
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => setValue("isUploadedAt", true)}
|
onClick={() => setValue("isUploadedAt", true)}
|
||||||
>
|
>
|
||||||
Uploaded On
|
Uploaded On
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
className={`btn px-2 py-1 rounded-0 text-tiny ${!isUploadedAt ? "active btn-secondary text-white" : ""
|
||||||
!isUploadedAt ? "active btn-secondary text-white" : ""
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => setValue("isUploadedAt", false)}
|
onClick={() => setValue("isUploadedAt", false)}
|
||||||
>
|
>
|
||||||
Updated On
|
Updated On
|
||||||
|
@ -144,12 +144,12 @@ const Documents = ({ Document_Entity, Entity }) => {
|
|||||||
<span className="switch-off"></span>
|
<span className="switch-off"></span>
|
||||||
</span>
|
</span>
|
||||||
<span className="switch-label">
|
<span className="switch-label">
|
||||||
{isActive ? "Active" : "In-Active"}
|
{isActive ? "Active Document" : "In-Active Document"}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-6 col-md-6 col-lg-8 text-end">
|
<div className="col-12 col-md-6 col-lg-8 text-end">
|
||||||
{(isSelf || canUploadDocument) && (
|
{(isSelf || canUploadDocument) && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-primary me-3"
|
className="btn btn-sm btn-primary me-3"
|
||||||
|
@ -51,7 +51,6 @@ const EmpAttendance = () => {
|
|||||||
new Date(b?.checkInTime).getTime() - new Date(a?.checkInTime).getTime()
|
new Date(b?.checkInTime).getTime() - new Date(a?.checkInTime).getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(sorted);
|
|
||||||
|
|
||||||
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
||||||
sorted,
|
sorted,
|
||||||
|
84
src/components/Employee/handleEmployeeExport.jsx
Normal file
84
src/components/Employee/handleEmployeeExport.jsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import moment from "moment";
|
||||||
|
import { exportToExcel, exportToCSV, exportToPDF, printTable } from "../../utils/tableExportUtils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles export operations for employee data.
|
||||||
|
* @param {string} type - Export type: 'csv', 'excel', 'pdf', or 'print'
|
||||||
|
* @param {Array} employeeList - Full employee data array
|
||||||
|
* @param {Array} filteredData - Filtered employee data (if search applied)
|
||||||
|
* @param {string} searchText - Current search text (used to decide dataset)
|
||||||
|
* @param {RefObject} tableRef - Table reference (used for print)
|
||||||
|
*/
|
||||||
|
const handleEmployeeExport = (type, employeeList, filteredData, searchText, tableRef) => {
|
||||||
|
// Export full list (filtered if search applied)
|
||||||
|
const dataToExport = searchText ? filteredData : employeeList;
|
||||||
|
|
||||||
|
if (!dataToExport || dataToExport.length === 0) return;
|
||||||
|
|
||||||
|
// Map and format employee data for export
|
||||||
|
const exportData = dataToExport.map((item) => ({
|
||||||
|
"First Name": item.firstName || "",
|
||||||
|
"Middle Name": item.middleName || "",
|
||||||
|
"Last Name": item.lastName || "",
|
||||||
|
"Email": item.email || "",
|
||||||
|
"Gender": item.gender || "",
|
||||||
|
"Birth Date": item.birthdate
|
||||||
|
? moment(item.birthdate).format("DD-MMM-YYYY")
|
||||||
|
: "",
|
||||||
|
"Joining Date": item.joiningDate
|
||||||
|
? moment(item.joiningDate).format("DD-MMM-YYYY")
|
||||||
|
: "",
|
||||||
|
"Permanent Address": item.permanentAddress || "",
|
||||||
|
"Current Address": item.currentAddress || "",
|
||||||
|
"Phone Number": item.phoneNumber || "",
|
||||||
|
"Emergency Phone Number": item.emergencyPhoneNumber || "",
|
||||||
|
"Emergency Contact Person": item.emergencyContactPerson || "",
|
||||||
|
"Is Active": item.isActive ? "Active" : "Inactive",
|
||||||
|
"Job Role": item.jobRole || "",
|
||||||
|
}));
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "csv":
|
||||||
|
exportToCSV(exportData, "employees");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "excel":
|
||||||
|
exportToExcel(exportData, "employees");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "pdf":
|
||||||
|
exportToPDF(
|
||||||
|
dataToExport.map((item) => ({
|
||||||
|
Name: `${item.firstName || ""} ${item.lastName || ""}`.trim(),
|
||||||
|
Email: item.email || "",
|
||||||
|
"Phone Number": item.phoneNumber || "",
|
||||||
|
"Job Role": item.jobRole || "",
|
||||||
|
"Joining Date": item.joiningDate
|
||||||
|
? moment(item.joiningDate).format("DD-MMM-YYYY")
|
||||||
|
: "",
|
||||||
|
Gender: item.gender || "",
|
||||||
|
Status: item.isActive ? "Active" : "Inactive",
|
||||||
|
})),
|
||||||
|
"employees",
|
||||||
|
[
|
||||||
|
"Name",
|
||||||
|
"Email",
|
||||||
|
"Phone Number",
|
||||||
|
"Job Role",
|
||||||
|
"Joining Date",
|
||||||
|
"Gender",
|
||||||
|
"Status",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "print":
|
||||||
|
printTable(tableRef.current);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handleEmployeeExport;
|
@ -15,6 +15,7 @@ import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
|
|||||||
import ConfirmModal from "../common/ConfirmModal";
|
import ConfirmModal from "../common/ConfirmModal";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
||||||
const [deletingId, setDeletingId] = useState(null);
|
const [deletingId, setDeletingId] = useState(null);
|
||||||
@ -24,6 +25,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
|
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const debouncedSearch = useDebounce(searchText, 500);
|
const debouncedSearch = useDebounce(searchText, 500);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
|
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
|
||||||
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
|
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
|
||||||
@ -67,9 +69,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
key = item.status?.displayName || "Unknown";
|
key = item.status?.displayName || "Unknown";
|
||||||
break;
|
break;
|
||||||
case "submittedBy":
|
case "submittedBy":
|
||||||
key = `${item.createdBy?.firstName ?? ""} ${
|
key = `${item.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? ""
|
||||||
item.createdBy?.lastName ?? ""
|
}`.trim();
|
||||||
}`.trim();
|
|
||||||
break;
|
break;
|
||||||
case "project":
|
case "project":
|
||||||
key = item.project?.name || "Unknown Project";
|
key = item.project?.name || "Unknown Project";
|
||||||
@ -110,11 +111,11 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
label: "Submitted By",
|
label: "Submitted By",
|
||||||
align: "text-start",
|
align: "text-start",
|
||||||
getValue: (e) =>
|
getValue: (e) =>
|
||||||
`${e.createdBy?.firstName ?? ""} ${
|
`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
|
||||||
e.createdBy?.lastName ?? ""
|
}`.trim() || "N/A",
|
||||||
}`.trim() || "N/A",
|
|
||||||
customRender: (e) => (
|
customRender: (e) => (
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center cursor-pointer"
|
||||||
|
onClick={() => navigate(`/employee/${e.createdBy?.id}`)}>
|
||||||
<Avatar
|
<Avatar
|
||||||
size="xs"
|
size="xs"
|
||||||
classAvatar="m-0"
|
classAvatar="m-0"
|
||||||
@ -122,9 +123,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
lastName={e.createdBy?.lastName}
|
lastName={e.createdBy?.lastName}
|
||||||
/>
|
/>
|
||||||
<span className="text-truncate">
|
<span className="text-truncate">
|
||||||
{`${e.createdBy?.firstName ?? ""} ${
|
{`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
|
||||||
e.createdBy?.lastName ?? ""
|
}`.trim() || "N/A"}
|
||||||
}`.trim() || "N/A"}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@ -152,9 +152,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
align: "text-center",
|
align: "text-center",
|
||||||
getValue: (e) => (
|
getValue: (e) => (
|
||||||
<span
|
<span
|
||||||
className={`badge bg-label-${
|
className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"
|
||||||
getColorNameFromHex(e?.status?.color) || "secondary"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{e.status?.name || "Unknown"}
|
{e.status?.name || "Unknown"}
|
||||||
</span>
|
</span>
|
||||||
@ -299,15 +298,15 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{data?.data?.length > 0 && (
|
|
||||||
<Pagination
|
|
||||||
currentPage={currentPage}
|
|
||||||
totalPages={data.totalPages}
|
|
||||||
onPageChange={paginate}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{data?.data?.length > 0 && (
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
totalPages={data.totalPages}
|
||||||
|
onPageChange={paginate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -131,7 +131,7 @@ const OrganizationsList = ({searchText}) => {
|
|||||||
<div className="d-flex justify-content-center gap-2">
|
<div className="d-flex justify-content-center gap-2">
|
||||||
<i className="bx bx-show text-primary cursor-pointer" onClick={()=>onOpen({startStep:5,orgData:org.id,flowType:"view"})}></i>
|
<i className="bx bx-show text-primary cursor-pointer" onClick={()=>onOpen({startStep:5,orgData:org.id,flowType:"view"})}></i>
|
||||||
<i className="bx bx-edit text-secondary cursor-pointer" onClick={()=>onOpen({startStep:4,orgData:org,flowType:"edit"})}></i>
|
<i className="bx bx-edit text-secondary cursor-pointer" onClick={()=>onOpen({startStep:4,orgData:org,flowType:"edit"})}></i>
|
||||||
<i className="bx bx-trash text-danger cursor-pointer"></i>
|
<i className="bx bx-trash text-danger cursor-not-allowed"></i>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -22,9 +22,8 @@ const VieworgDataanization = ({ orgId }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-end">
|
<div className="text-end">
|
||||||
<span
|
<span
|
||||||
className={`badge bg-label-${
|
className={`badge bg-label-${data?.isActive ? "primary" : "secondary"
|
||||||
data?.isActive ? "primary" : "secondary"
|
} `}
|
||||||
} `}
|
|
||||||
>
|
>
|
||||||
{data?.isActive ? "Active" : "In-Active"}{" "}
|
{data?.isActive ? "Active" : "In-Active"}{" "}
|
||||||
</span>
|
</span>
|
||||||
@ -105,9 +104,101 @@ const VieworgDataanization = ({ orgId }) => {
|
|||||||
<div className="text-muted text-start">{data?.address}</div>
|
<div className="text-muted text-start">{data?.address}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex text-secondary mb-2">
|
<div className="col-12 mb-3">
|
||||||
{" "}
|
<div
|
||||||
<i className="bx bx-sm bx-briefcase me-1" /> Projects And Services
|
className="d-flex justify-content-between align-items-center text-secondary mb-2 cursor-pointer"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapse-projects-services"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<i className="bx bx-sm bx-briefcase me-1" /> Projects
|
||||||
|
</div>
|
||||||
|
<i className="bx bx-chevron-down me-2"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* remove "show" from className */}
|
||||||
|
<div id="collapse-projects-services" className="collapse">
|
||||||
|
{data?.projects && data.projects.length > 0 ? (
|
||||||
|
data.projects
|
||||||
|
.reduce((acc, curr) => {
|
||||||
|
const projectId = curr.project.id;
|
||||||
|
if (!acc.find((p) => p.id === projectId)) {
|
||||||
|
acc.push(curr.project);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
.map((project) => (
|
||||||
|
<div key={project.id} className="mb-2 rounded p-2">
|
||||||
|
<div
|
||||||
|
className="d-flex justify-content-between align-items-center cursor-pointer"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target={`#collapse-${project.id}`}
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<label className="form-label fw-semibold">
|
||||||
|
<i className="bx bx-buildings me-2"></i>
|
||||||
|
{project.name}
|
||||||
|
</label>
|
||||||
|
<i className="bx bx-chevron-down"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id={`collapse-${project.id}`} className="collapse mt-2 ps-5">
|
||||||
|
{data.projects
|
||||||
|
.filter((p) => p.project.id === project.id)
|
||||||
|
.map((p) => (
|
||||||
|
<div key={p.service.id} className="mb-1 text-muted">
|
||||||
|
<i className="bx bx-wrench me-2"></i>
|
||||||
|
{p.service.name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-muted fst-italic ps-2">No projects available</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Services Section */}
|
||||||
|
<div className="col-12 mb-3">
|
||||||
|
<div
|
||||||
|
className="d-flex justify-content-between align-items-center text-secondary mb-2 cursor-pointer"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapse-services"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<i className="bx bx-sm bx-cog me-1" /> Services
|
||||||
|
</div>
|
||||||
|
<i className="bx bx-chevron-down me-2"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* collapse is closed initially */}
|
||||||
|
<div id="collapse-services" className="collapse">
|
||||||
|
{data?.services && data.services.length > 0 ? (
|
||||||
|
<div className="row">
|
||||||
|
{data.services.map((service) => (
|
||||||
|
<div key={service.id} className="col-md-12 mb-3">
|
||||||
|
<div className="card h-100 shadow-sm border-0">
|
||||||
|
<div className="card-body">
|
||||||
|
<h6 className="fw-semibold mb-1">
|
||||||
|
<i className="bx bx-wrench me-1"></i>
|
||||||
|
{service.name}
|
||||||
|
</h6>
|
||||||
|
<p className="text-muted small mb-0">
|
||||||
|
{service.description || "No description available."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-muted fst-italic ps-2">No services available</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -104,7 +104,7 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-2">
|
<div className="col-6 col-md-2">
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
completedWork={formatNumber(workArea?.completedWork)}
|
completedWork={formatNumber(workArea?.completedWork)}
|
||||||
plannedWork={formatNumber(workArea?.plannedWork)}
|
plannedWork={formatNumber(workArea?.plannedWork)}
|
||||||
|
@ -201,7 +201,7 @@ const Teams = () => {
|
|||||||
className="form-check-label ms-2"
|
className="form-check-label ms-2"
|
||||||
htmlFor="activeEmployeeSwitch"
|
htmlFor="activeEmployeeSwitch"
|
||||||
>
|
>
|
||||||
{activeEmployee ? "Active Employees" : "Include Inactive Employees"}
|
{activeEmployee ? "Active Employees" : "In-active Employees"}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,13 +28,21 @@ const ImageGalleryListView = ({filter}) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data?.data?.length && !isLoading) {
|
if (!data?.data?.length && !isLoading) {
|
||||||
return (
|
return (
|
||||||
<p className="text-center text-muted mt-5">
|
<div
|
||||||
{selectedProject ? " No images match the selected filters.":"Please Select Project!"}
|
className="d-flex justify-content-center align-items-center text-muted"
|
||||||
</p>
|
style={{ minHeight: "50vh" }}
|
||||||
);
|
>
|
||||||
}
|
<span style={{ fontSize: "0.9rem" }}>
|
||||||
|
{selectedProject
|
||||||
|
? "No images match the selected filters."
|
||||||
|
: "Please Select Project!"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
@ -179,44 +179,40 @@ const AttendancePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search + Organization filter */}
|
{/* Search + Organization filter */}
|
||||||
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto d-flex gap-2 align-items-center">
|
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto">
|
||||||
{/* Organization Dropdown */}
|
<div className="row g-2">
|
||||||
<div className="row">
|
<div className="col-12 col-sm-6">
|
||||||
<div className="col-12 col-sm-6 mb-2 mb-sm-0">
|
<select
|
||||||
<select
|
className="form-select form-select-sm"
|
||||||
className="form-select form-select-sm"
|
value={appliedFilters.selectedOrganization}
|
||||||
value={appliedFilters.selectedOrganization}
|
onChange={(e) =>
|
||||||
onChange={(e) =>
|
setAppliedFilters((prev) => ({
|
||||||
setAppliedFilters((prev) => ({
|
...prev,
|
||||||
...prev,
|
selectedOrganization: e.target.value,
|
||||||
selectedOrganization: e.target.value,
|
}))
|
||||||
}))
|
}
|
||||||
}
|
disabled={orgLoading}
|
||||||
disabled={orgLoading}
|
>
|
||||||
>
|
<option value="">All Organizations</option>
|
||||||
<option value="">All Organizations</option>
|
{organizations?.map((org, ind) => (
|
||||||
{organizations?.map((org, ind) => (
|
<option key={`${org.id}-${ind}`} value={org.id}>
|
||||||
<option key={`${org.id}-${ind}`} value={org.id}>
|
{org.name}
|
||||||
{org.name}
|
</option>
|
||||||
</option>
|
))}
|
||||||
))}
|
</select>
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-sm-6">
|
<div className="col-12 col-sm-6">
|
||||||
{/* Search Input */}
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="text"
|
className="form-control form-control-sm"
|
||||||
className="form-control form-control-sm"
|
placeholder="Search Employee..."
|
||||||
placeholder="Search Employee..."
|
value={searchTerm}
|
||||||
value={searchTerm}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -224,7 +220,7 @@ const AttendancePage = () => {
|
|||||||
{selectedProject ? (
|
{selectedProject ? (
|
||||||
<>
|
<>
|
||||||
{activeTab === "all" && (
|
{activeTab === "all" && (
|
||||||
<div className="tab-pane fade show active py-0 mx-5">
|
<div className="tab-pane fade show active py-0 mx-2">
|
||||||
<Attendance
|
<Attendance
|
||||||
handleModalData={handleModalData}
|
handleModalData={handleModalData}
|
||||||
getRole={getRole}
|
getRole={getRole}
|
||||||
@ -234,7 +230,7 @@ const AttendancePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeTab === "logs" && (
|
{activeTab === "logs" && (
|
||||||
<div className="tab-pane fade show active py-0">
|
<div className="tab-pane fade p-3 show active py-0">
|
||||||
<AttendanceLog
|
<AttendanceLog
|
||||||
handleModalData={handleModalData}
|
handleModalData={handleModalData}
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
@ -243,7 +239,7 @@ const AttendancePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeTab === "regularization" && DoRegularized && (
|
{activeTab === "regularization" && DoRegularized && (
|
||||||
<div className="tab-pane fade show active py-0">
|
<div className="tab-pane fade p-3 show active py-0">
|
||||||
<Regularization
|
<Regularization
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
organizationId={appliedFilters.selectedOrganization}
|
organizationId={appliedFilters.selectedOrganization}
|
||||||
|
@ -38,7 +38,7 @@ const TaskPlanning = () => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="card py-2 ">
|
<div className="card py-2 page-min-h">
|
||||||
<div className="col-sm-4 col-md-3 col-12 px-4 py-2 text-start">
|
<div className="col-sm-4 col-md-3 col-12 px-4 py-2 text-start">
|
||||||
{data?.length === 0 ? (
|
{data?.length === 0 ? (
|
||||||
<p className="badge bg-label-secondary m-0">Service not assigned</p>
|
<p className="badge bg-label-secondary m-0">Service not assigned</p>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { FormProvider, useForm } from "react-hook-form";
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
contactsFilter,
|
contactsFilter,
|
||||||
@ -8,11 +8,14 @@ import {
|
|||||||
import { useContactFilter } from "../../hooks/useDirectory";
|
import { useContactFilter } from "../../hooks/useDirectory";
|
||||||
import { ExpenseFilterSkeleton } from "../../components/Expenses/ExpenseSkeleton";
|
import { ExpenseFilterSkeleton } from "../../components/Expenses/ExpenseSkeleton";
|
||||||
import SelectMultiple from "../../components/common/SelectMultiple";
|
import SelectMultiple from "../../components/common/SelectMultiple";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
const ContactFilterPanel = ({ onApply, clearFilter }) => {
|
const ContactFilterPanel = ({ onApply, clearFilter }) => {
|
||||||
const { data, isError, isLoading, error, isFetched, isFetching } =
|
const { data, isError, isLoading, error, isFetched, isFetching } =
|
||||||
useContactFilter();
|
useContactFilter();
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
resolver: zodResolver(contactsFilter),
|
resolver: zodResolver(contactsFilter),
|
||||||
defaultValues: defaultContactFilter,
|
defaultValues: defaultContactFilter,
|
||||||
@ -30,14 +33,24 @@ const ContactFilterPanel = ({ onApply, clearFilter }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
reset(defaultContactFilter);
|
reset(defaultContactFilter);
|
||||||
onApply(defaultContactFilter);
|
onApply(defaultContactFilter);
|
||||||
closePanel();
|
closePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
|
if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
|
||||||
if (isError && isFetched)
|
if (isError && isFetched)
|
||||||
return <div>Something went wrong Here- {error.message} </div>;
|
return <div>Something went wrong Here- {error.message} </div>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
|
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
|
||||||
|
@ -139,9 +139,8 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
<ul className="nav nav-tabs">
|
<ul className="nav nav-tabs">
|
||||||
<li className="nav-item cursor-pointer">
|
<li className="nav-item cursor-pointer">
|
||||||
<a
|
<a
|
||||||
className={`nav-link ${
|
className={`nav-link ${activeTab === "notes" ? "active" : ""
|
||||||
activeTab === "notes" ? "active" : ""
|
} fs-6`}
|
||||||
} fs-6`}
|
|
||||||
onClick={(e) => handleTabClick("notes", e)}
|
onClick={(e) => handleTabClick("notes", e)}
|
||||||
>
|
>
|
||||||
<i className="bx bx-notepad bx-sm me-1_5"></i>
|
<i className="bx bx-notepad bx-sm me-1_5"></i>
|
||||||
@ -150,9 +149,8 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
</li>
|
</li>
|
||||||
<li className="nav-item cursor-pointer">
|
<li className="nav-item cursor-pointer">
|
||||||
<a
|
<a
|
||||||
className={`nav-link ${
|
className={`nav-link ${activeTab === "contacts" ? "active" : ""
|
||||||
activeTab === "contacts" ? "active" : ""
|
} fs-6`}
|
||||||
} fs-6`}
|
|
||||||
onClick={(e) => handleTabClick("contacts", e)}
|
onClick={(e) => handleTabClick("contacts", e)}
|
||||||
>
|
>
|
||||||
<i className="bx bxs-contact bx-sm me-1_5"></i>
|
<i className="bx bxs-contact bx-sm me-1_5"></i>
|
||||||
@ -168,105 +166,84 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
|
|||||||
{activeTab === "notes" && (
|
{activeTab === "notes" && (
|
||||||
<div className="col-8 col-md-3">
|
<div className="col-8 col-md-3">
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
placeholder="Search notes..."
|
placeholder="Search notes..."
|
||||||
value={searchNote}
|
value={searchNote}
|
||||||
onChange={(e) => setSearchNote(e.target.value)}
|
onChange={(e) => setSearchNote(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "contacts" && (
|
{activeTab === "contacts" && (
|
||||||
<div className="d-flex align-items-center gap-3">
|
<div className="d-flex align-items-center gap-3">
|
||||||
<div className="col-12 col-md-8 d-flex flex-row gap-2">
|
<div className="col-12 col-md-8 d-flex flex-row gap-2">
|
||||||
<div className="col-7 col-md-4">
|
<div className="col-7 col-md-4">
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
placeholder="Search contacts..."
|
placeholder="Search contacts..."
|
||||||
value={searchContact}
|
value={searchContact}
|
||||||
onChange={(e) => setsearchContact(e.target.value)}
|
onChange={(e) => setsearchContact(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm p-1 ${
|
className={`btn btn-sm p-1 ${gridView ? " btn-primary" : " btn-outline-primary"
|
||||||
!gridView ? "btn-primary" : "btn-outline-primary"
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => setGridView(false)}
|
|
||||||
>
|
|
||||||
<i className="bx bx-list-ul"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className={`btn btn-sm p-1 ${
|
|
||||||
gridView ? " btn-primary" : " btn-outline-primary"
|
|
||||||
}`}
|
|
||||||
onClick={() => setGridView(true)}
|
onClick={() => setGridView(true)}
|
||||||
>
|
>
|
||||||
<i className="bx bx-grid-alt"></i>
|
<i className="bx bx-grid-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
<div className="form-check form-switch d-flex align-items-center d-none d-md-flex">
|
<button
|
||||||
<input
|
className={`btn btn-sm p-1 ${!gridView ? "btn-primary" : "btn-outline-primary"
|
||||||
type="checkbox"
|
}`}
|
||||||
className="form-check-input"
|
onClick={() => setGridView(false)}
|
||||||
role="switch"
|
|
||||||
id="inactiveEmployeesCheckbox"
|
|
||||||
checked={showActive}
|
|
||||||
onChange={(e) => setShowActive(e.target.checked)}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
className="form-check-label ms-2"
|
|
||||||
htmlFor="inactiveEmployeesCheckbox"
|
|
||||||
>
|
>
|
||||||
{showActive ? "Active" : "Inactive"} Contacts
|
<i className="bx bx-list-ul"></i>
|
||||||
</label>
|
</button>
|
||||||
|
|
||||||
|
<div className="form-check form-switch d-flex align-items-end d-none d-md-flex">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input"
|
||||||
|
role="switch"
|
||||||
|
id="inactiveEmployeesCheckbox"
|
||||||
|
checked={showActive}
|
||||||
|
onChange={(e) => setShowActive(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="form-check-label ms-2"
|
||||||
|
htmlFor="inactiveEmployeesCheckbox"
|
||||||
|
>
|
||||||
|
{showActive ? "Active" : "In-active"} Contacts
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-md-2 d-flex justify-content-end align-items-center gap-2">
|
<div className="col-12 col-md-2 d-flex justify-content-end align-items-center gap-2">
|
||||||
<div className={`form-check form-switch d-flex align-items-center ${activeTab === "contacts" ? " d-flex d-md-none m-0":"d-none" }`}>
|
<div className=" btn-group">
|
||||||
<input
|
<button
|
||||||
type="checkbox"
|
className="btn btn-sm btn-label-secondary dropdown-toggle"
|
||||||
className="form-check-input"
|
type="button"
|
||||||
role="switch"
|
data-bs-toggle="dropdown"
|
||||||
id="inactiveEmployeesCheckbox"
|
aria-expanded="false"
|
||||||
checked={showActive}
|
>
|
||||||
onChange={(e) => setShowActive(e.target.checked)}
|
<i className="bx bx-export me-2 bx-sm"></i>Export
|
||||||
/>
|
</button>
|
||||||
<label
|
<ul className="dropdown-menu">
|
||||||
className="form-check-label ms-2"
|
<li>
|
||||||
htmlFor="inactiveEmployeesCheckbox"
|
<a
|
||||||
|
className="dropdown-item cursor-pointer"
|
||||||
|
onClick={() => handleExport("csv")}
|
||||||
>
|
>
|
||||||
{showActive ? "Active" : "Inactive"} Contacts
|
<i className="bx bx-file me-1"></i> CSV
|
||||||
</label>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
<div className=" btn-group">
|
</ul>
|
||||||
<button
|
</div>
|
||||||
className="btn btn-sm btn-label-secondary dropdown-toggle"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<i className="bx bx-export me-2 bx-sm"></i>Export
|
|
||||||
</button>
|
|
||||||
<ul className="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
className="dropdown-item cursor-pointer"
|
|
||||||
onClick={() => handleExport("csv")}
|
|
||||||
>
|
|
||||||
<i className="bx bx-file me-1"></i> CSV
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { FormProvider, useForm } from "react-hook-form";
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
defaultNotesFilter,
|
defaultNotesFilter,
|
||||||
@ -8,11 +8,18 @@ import {
|
|||||||
import { useContactFilter, useNoteFilter } from "../../hooks/useDirectory";
|
import { useContactFilter, useNoteFilter } from "../../hooks/useDirectory";
|
||||||
import { ExpenseFilterSkeleton } from "../../components/Expenses/ExpenseSkeleton";
|
import { ExpenseFilterSkeleton } from "../../components/Expenses/ExpenseSkeleton";
|
||||||
import SelectMultiple from "../../components/common/SelectMultiple";
|
import SelectMultiple from "../../components/common/SelectMultiple";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
const NoteFilterPanel = ({ onApply, clearFilter }) => {
|
const NoteFilterPanel = ({ onApply, clearFilter }) => {
|
||||||
const { data, isError, isLoading, error, isFetched, isFetching } =
|
const { data, isError, isLoading, error, isFetched, isFetching } =
|
||||||
useNoteFilter();
|
useNoteFilter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
resolver: zodResolver(notesFilter),
|
resolver: zodResolver(notesFilter),
|
||||||
defaultValues: defaultNotesFilter,
|
defaultValues: defaultNotesFilter,
|
||||||
@ -31,7 +38,7 @@ const NoteFilterPanel = ({ onApply, clearFilter }) => {
|
|||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
reset(defaultNotesFilter);
|
reset(defaultNotesFilter);
|
||||||
onApply(defaultNotesFilter);
|
onApply(defaultNotesFilter);
|
||||||
closePanel();
|
closePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ import usePagination from "../../hooks/usePagination";
|
|||||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import Pagination from "../../components/common/Pagination";
|
import Pagination from "../../components/common/Pagination";
|
||||||
|
import handleEmployeeExport from "../../components/Employee/handleEmployeeExport";
|
||||||
|
|
||||||
const EmployeeList = () => {
|
const EmployeeList = () => {
|
||||||
const selectedProjectId = useSelector(
|
const selectedProjectId = useSelector(
|
||||||
@ -134,26 +135,11 @@ const EmployeeList = () => {
|
|||||||
|
|
||||||
const tableRef = useRef(null);
|
const tableRef = useRef(null);
|
||||||
const handleExport = (type) => {
|
const handleExport = (type) => {
|
||||||
if (!currentItems || currentItems.length === 0) return;
|
handleEmployeeExport(type, employeeList, filteredData, searchText, tableRef);
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case "csv":
|
|
||||||
exportToCSV(currentItems, "employees");
|
|
||||||
break;
|
|
||||||
case "excel":
|
|
||||||
exportToExcel(currentItems, "employees");
|
|
||||||
break;
|
|
||||||
case "pdf":
|
|
||||||
exportToPDF(currentItems, "employees");
|
|
||||||
break;
|
|
||||||
case "print":
|
|
||||||
printTable(tableRef.current);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleAllEmployeesToggle = (e) => {
|
const handleAllEmployeesToggle = (e) => {
|
||||||
const isChecked = e.target.checked;
|
const isChecked = e.target.checked;
|
||||||
setShowInactive(false);
|
setShowInactive(false);
|
||||||
@ -176,12 +162,10 @@ const EmployeeList = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading && Array.isArray(employees)) {
|
if (!loading && Array.isArray(employees)) {
|
||||||
const sorted = [...employees].sort((a, b) => {
|
const sorted = [...employees].sort((a, b) => {
|
||||||
const nameA = `${a.firstName || ""}${a.middleName || ""}${
|
const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || ""
|
||||||
a.lastName || ""
|
}`.toLowerCase();
|
||||||
}`.toLowerCase();
|
const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || ""
|
||||||
const nameB = `${b.firstName || ""}${b.middleName || ""}${
|
}`.toLowerCase();
|
||||||
b.lastName || ""
|
|
||||||
}`.toLowerCase();
|
|
||||||
return nameA?.localeCompare(nameB);
|
return nameA?.localeCompare(nameB);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -258,9 +242,8 @@ const EmployeeList = () => {
|
|||||||
? "Suspend Employee"
|
? "Suspend Employee"
|
||||||
: "Reactivate Employee"
|
: "Reactivate Employee"
|
||||||
}
|
}
|
||||||
message={`Are you sure you want to ${
|
message={`Are you sure you want to ${selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
|
||||||
selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
|
} this employee?`}
|
||||||
} this employee?`}
|
|
||||||
onSubmit={(id) =>
|
onSubmit={(id) =>
|
||||||
suspendEmployee({
|
suspendEmployee({
|
||||||
employeeId: id,
|
employeeId: id,
|
||||||
@ -309,7 +292,7 @@ const EmployeeList = () => {
|
|||||||
className="form-check-label ms-0"
|
className="form-check-label ms-0"
|
||||||
htmlFor="inactiveEmployeesCheckbox"
|
htmlFor="inactiveEmployeesCheckbox"
|
||||||
>
|
>
|
||||||
Show Inactive Employees
|
In-active Employees
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -471,9 +454,8 @@ const EmployeeList = () => {
|
|||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
className={`sorting_disabled ${
|
className={`sorting_disabled ${!Manage_Employee && "d-none"
|
||||||
!Manage_Employee && "d-none"
|
}`}
|
||||||
}`}
|
|
||||||
rowSpan="1"
|
rowSpan="1"
|
||||||
colSpan="1"
|
colSpan="1"
|
||||||
style={{ width: "50px" }}
|
style={{ width: "50px" }}
|
||||||
@ -493,20 +475,20 @@ const EmployeeList = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
displayData?.length === 0 &&
|
displayData?.length === 0 &&
|
||||||
(!searchText ) ? (
|
(!searchText) ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={8} className="border-0 py-3">
|
<td colSpan={8} className="border-0 py-3">
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
No Data Found
|
No Data Found
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
displayData?.length === 0 &&
|
displayData?.length === 0 &&
|
||||||
(searchText ) ? (
|
(searchText) ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={8} className="border-0 py-3">
|
<td colSpan={8} className="border-0 py-3">
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
@ -542,18 +524,17 @@ const EmployeeList = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="text-start d-none d-sm-table-cell">
|
<td className="text-start d-none d-sm-table-cell">
|
||||||
{item.email ? (
|
{item.email ? (
|
||||||
<span className="text-truncate">
|
<span className="text-truncate">
|
||||||
<i className="bx bxs-envelope text-primary me-2"></i>
|
<i className="bx bxs-envelope text-primary me-2"></i>
|
||||||
{item.email}
|
{item.email}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-truncate text-italic">
|
<span className="d-block text-start text-muted fst-italic">NA</span>
|
||||||
-
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="text-start d-none d-sm-table-cell">
|
<td className="text-start d-none d-sm-table-cell">
|
||||||
<span className="text-truncate">
|
<span className="text-truncate">
|
||||||
<i className="bx bxs-phone-call text-primary me-2"></i>
|
<i className="bx bxs-phone-call text-primary me-2"></i>
|
||||||
@ -567,9 +548,14 @@ const EmployeeList = () => {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className=" d-none d-md-table-cell">
|
<td className="d-none d-md-table-cell">
|
||||||
{moment(item.joiningDate)?.format("DD-MMM-YYYY")}
|
{item.joiningDate ? (
|
||||||
|
moment(item.joiningDate).format("DD-MMM-YYYY")
|
||||||
|
) : (
|
||||||
|
<span className="d-block text-center text-muted fst-italic">NA</span>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{showInactive ? (
|
{showInactive ? (
|
||||||
<span
|
<span
|
||||||
@ -663,17 +649,16 @@ const EmployeeList = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{displayData?.length > 0 && (
|
</div>
|
||||||
|
{displayData?.length > 0 && (
|
||||||
<Pagination
|
<Pagination
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
totalPages={totalPages}
|
totalPages={totalPages}
|
||||||
onPageChange={paginate}
|
onPageChange={paginate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
|
@ -77,7 +77,7 @@ const ProjectDetails = () => {
|
|||||||
<AboutProject />
|
<AboutProject />
|
||||||
<ProjectOverview project={projectId} />
|
<ProjectOverview project={projectId} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-8 col-md-7 mt-5">
|
<div className="col-lg-8 col-md-7 mt-2">
|
||||||
<ProjectProgressChart ShowAllProject="false" DefaultRange="1M" />
|
<ProjectProgressChart ShowAllProject="false" DefaultRange="1M" />
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<AttendanceOverview />
|
<AttendanceOverview />
|
||||||
|
@ -40,112 +40,57 @@ export const exportToExcel = (data, fileName = "data") => {
|
|||||||
* @param {Array} data - Array of objects to export
|
* @param {Array} data - Array of objects to export
|
||||||
* @param {string} fileName - File name for the PDF (optional)
|
* @param {string} fileName - File name for the PDF (optional)
|
||||||
*/
|
*/
|
||||||
export const exportToPDF = async (data, fileName = "data") => {
|
const sanitizeText = (text) => {
|
||||||
|
if (!text) return "";
|
||||||
|
// Replace all non-ASCII characters with "?" or remove them
|
||||||
|
return text.replace(/[^\x00-\x7F]/g, "?");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportToPDF = async (data, fileName = "data", columns = null, options = {}) => {
|
||||||
if (!data || data.length === 0) return;
|
if (!data || data.length === 0) return;
|
||||||
|
|
||||||
// Create a new PDF document
|
|
||||||
const pdfDoc = await PDFDocument.create();
|
const pdfDoc = await PDFDocument.create();
|
||||||
|
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||||
|
|
||||||
// Set up the font
|
// Default options
|
||||||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica); // Use Helvetica font
|
const {
|
||||||
|
columnWidths = [], // array of widths per column
|
||||||
// Calculate column widths dynamically based on data content
|
fontSizeHeader = 12,
|
||||||
const headers = Object.keys(data[0]);
|
fontSizeRow = 10,
|
||||||
const rows = data.map(item => headers.map(header => item[header] || ''));
|
rowHeight = 25,
|
||||||
|
} = options;
|
||||||
|
|
||||||
const getMaxColumnWidth = (columnIndex) => {
|
const pageWidth = 1000;
|
||||||
let maxWidth = font.widthOfTextAtSize(headers[columnIndex], 12);
|
const pageHeight = 600;
|
||||||
rows.forEach(row => {
|
let page = pdfDoc.addPage([pageWidth, pageHeight]);
|
||||||
const cellText = row[columnIndex].toString();
|
const margin = 30;
|
||||||
maxWidth = Math.max(maxWidth, font.widthOfTextAtSize(cellText, 10));
|
let y = pageHeight - margin;
|
||||||
|
|
||||||
|
const headers = columns || Object.keys(data[0]);
|
||||||
|
|
||||||
|
// Draw headers
|
||||||
|
headers.forEach((header, i) => {
|
||||||
|
const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150);
|
||||||
|
page.drawText(header, { x, y, font, size: fontSizeHeader });
|
||||||
|
});
|
||||||
|
y -= rowHeight;
|
||||||
|
|
||||||
|
// Draw rows
|
||||||
|
data.forEach(row => {
|
||||||
|
headers.forEach((header, i) => {
|
||||||
|
const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150);
|
||||||
|
const text = row[header] || '';
|
||||||
|
page.drawText(text, { x, y, font, size: fontSizeRow });
|
||||||
});
|
});
|
||||||
return maxWidth + 10; // Padding for better spacing
|
y -= rowHeight;
|
||||||
};
|
|
||||||
|
|
||||||
const columnWidths = headers.map((_, index) => getMaxColumnWidth(index));
|
if (y < margin) {
|
||||||
const tableX = 30; // X-coordinate for the table start
|
page = pdfDoc.addPage([pageWidth, pageHeight]);
|
||||||
const rowHeight = 20; // Height of each row (can be adjusted)
|
y = pageHeight - margin;
|
||||||
const maxPageHeight = 750; // Max available height for content (before a new page is added)
|
|
||||||
const pageMargin = 30; // Margin from the top of the page
|
|
||||||
|
|
||||||
let tableY = maxPageHeight; // Start Y position for the table
|
|
||||||
const maxPageWidth = 600; // Max available width for content (before a new page is added)
|
|
||||||
|
|
||||||
// Add the headers and rows to the page
|
|
||||||
const addHeadersToPage = (page, scaleFactor) => {
|
|
||||||
let xPosition = tableX;
|
|
||||||
headers.forEach((header, index) => {
|
|
||||||
page.drawText(header, {
|
|
||||||
x: xPosition,
|
|
||||||
y: tableY,
|
|
||||||
font,
|
|
||||||
size: 12 * scaleFactor, // Scale the header font size
|
|
||||||
color: rgb(0, 0, 0),
|
|
||||||
});
|
|
||||||
xPosition += columnWidths[index] * scaleFactor; // Adjust X position based on scaling
|
|
||||||
});
|
|
||||||
tableY -= rowHeight; // Move down after adding headers
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add a new page and reset the table position
|
|
||||||
const addNewPage = (scaleFactor) => {
|
|
||||||
const page = pdfDoc.addPage([600, 800]);
|
|
||||||
tableY = maxPageHeight; // Reset Y position for the new page
|
|
||||||
addHeadersToPage(page, scaleFactor); // Re-add headers to the new page
|
|
||||||
return page;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the first page and add headers
|
|
||||||
let page = pdfDoc.addPage([600, 800]);
|
|
||||||
|
|
||||||
// Check if the content fits within the page width, scale if necessary
|
|
||||||
const checkPageWidth = (row) => {
|
|
||||||
let totalWidth = columnWidths.reduce((acc, width) => acc + width, 0);
|
|
||||||
let scaleFactor = 1;
|
|
||||||
if (totalWidth > maxPageWidth) {
|
|
||||||
scaleFactor = maxPageWidth / totalWidth; // Scale down if necessary
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return scaleFactor;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to check for page breaks when adding a new row
|
|
||||||
const checkPageBreak = () => {
|
|
||||||
if (tableY - rowHeight < pageMargin) {
|
|
||||||
page = addNewPage(scaleFactor); // Add a new page if there is no space for the next row
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add rows to the PDF with pagination and horizontal scaling
|
|
||||||
rows.forEach(row => {
|
|
||||||
checkPageBreak(); // Check for page break before adding each row
|
|
||||||
|
|
||||||
const scaleFactor = checkPageWidth(row); // Get the scaling factor for the row
|
|
||||||
|
|
||||||
// Add headers to the first page and each new page with the same scale factor
|
|
||||||
if (tableY === maxPageHeight) {
|
|
||||||
addHeadersToPage(page, scaleFactor); // Add headers only on the first page
|
|
||||||
}
|
|
||||||
|
|
||||||
let xPosition = tableX;
|
|
||||||
row.forEach((value, index) => {
|
|
||||||
page.drawText(value.toString(), {
|
|
||||||
x: xPosition,
|
|
||||||
y: tableY,
|
|
||||||
font,
|
|
||||||
size: 10 * scaleFactor, // Scale the font size
|
|
||||||
color: rgb(0, 0, 0),
|
|
||||||
});
|
|
||||||
xPosition += columnWidths[index] * scaleFactor; // Adjust X position based on scaling
|
|
||||||
});
|
|
||||||
|
|
||||||
tableY -= rowHeight; // Move down to the next row position
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serialize the document to bytes
|
|
||||||
const pdfBytes = await pdfDoc.save();
|
const pdfBytes = await pdfDoc.save();
|
||||||
|
|
||||||
// Trigger a download of the PDF
|
|
||||||
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
|
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = URL.createObjectURL(blob);
|
link.href = URL.createObjectURL(blob);
|
||||||
@ -153,15 +98,110 @@ export const exportToPDF = async (data, fileName = "data") => {
|
|||||||
link.click();
|
link.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export JSON data to PDF in a card-style format
|
||||||
|
* @param {Array} data - Array of objects to export
|
||||||
|
* @param {string} fileName - File name for the PDF (optional)
|
||||||
|
*/
|
||||||
|
export const exportToPDF1 = async (data, fileName = "data") => {
|
||||||
|
if (!data || data.length === 0) return;
|
||||||
|
|
||||||
|
const pdfDoc = await PDFDocument.create();
|
||||||
|
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||||
|
const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
||||||
|
|
||||||
|
const pageWidth = 600;
|
||||||
|
const pageHeight = 800;
|
||||||
|
const margin = 30;
|
||||||
|
const cardSpacing = 20;
|
||||||
|
const cardPadding = 10;
|
||||||
|
let page = pdfDoc.addPage([pageWidth, pageHeight]);
|
||||||
|
let y = pageHeight - margin;
|
||||||
|
|
||||||
|
for (const item of data) {
|
||||||
|
const title = item.ContactName || "";
|
||||||
|
const subtitle = `by ${item.CreatedBy || ""} on ${item.CreatedAt || ""}`;
|
||||||
|
const body = item.Note || "";
|
||||||
|
|
||||||
|
const cardHeight = 80 + (body.length / 60) * 14; // approximate height for body text
|
||||||
|
|
||||||
|
if (y - cardHeight < margin) {
|
||||||
|
page = pdfDoc.addPage([pageWidth, pageHeight]);
|
||||||
|
y = pageHeight - margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw card border
|
||||||
|
page.drawRectangle({
|
||||||
|
x: margin,
|
||||||
|
y: y - cardHeight,
|
||||||
|
width: pageWidth - 2 * margin,
|
||||||
|
height: cardHeight,
|
||||||
|
borderColor: rgb(0.7, 0.7, 0.7),
|
||||||
|
borderWidth: 1,
|
||||||
|
color: rgb(1, 1, 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw title
|
||||||
|
page.drawText(title, {
|
||||||
|
x: margin + cardPadding,
|
||||||
|
y: y - 20,
|
||||||
|
font: boldFont,
|
||||||
|
size: 12,
|
||||||
|
color: rgb(0.1, 0.1, 0.1),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw subtitle
|
||||||
|
page.drawText(subtitle, {
|
||||||
|
x: margin + cardPadding,
|
||||||
|
y: y - 35,
|
||||||
|
font,
|
||||||
|
size: 10,
|
||||||
|
color: rgb(0.4, 0.4, 0.4),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw body text (wrap manually)
|
||||||
|
const lines = body.match(/(.|[\r\n]){1,80}/g) || [];
|
||||||
|
lines.forEach((line, i) => {
|
||||||
|
page.drawText(line, {
|
||||||
|
x: margin + cardPadding,
|
||||||
|
y: y - 50 - i * 12,
|
||||||
|
font,
|
||||||
|
size: 10,
|
||||||
|
color: rgb(0.2, 0.2, 0.2),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
y -= cardHeight + cardSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfBytes = await pdfDoc.save();
|
||||||
|
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = `${fileName}.pdf`;
|
||||||
|
link.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print the HTML table by accepting the table element or a reference.
|
* Print the HTML table by accepting the table element or a reference.
|
||||||
* @param {HTMLElement} table - The table element (or ref) to print
|
* @param {HTMLElement} table - The table element (or ref) to print
|
||||||
*/
|
*/
|
||||||
export const printTable = (table) => {
|
export const printTable = (table) => {
|
||||||
if (table) {
|
if (table) {
|
||||||
const newWindow = window.open("", "", "width=600,height=600"); // Open a new window
|
const clone = table.cloneNode(true);
|
||||||
|
// Remove last column (Actions) from all rows
|
||||||
|
clone.querySelectorAll("tr").forEach((row) => {
|
||||||
|
row.removeChild(row.lastElementChild);
|
||||||
|
});
|
||||||
|
|
||||||
// Inject styles for the table and body
|
const newWindow = window.open("", "", "width=600,height=600");
|
||||||
newWindow.document.write("<html><head><title>Print Table</title>");
|
newWindow.document.write("<html><head><title>Print Table</title>");
|
||||||
const style = document.createElement('style');
|
const style = document.createElement('style');
|
||||||
style.innerHTML = `
|
style.innerHTML = `
|
||||||
@ -171,16 +211,14 @@ export const printTable = (table) => {
|
|||||||
th { background-color: #f2f2f2; }
|
th { background-color: #f2f2f2; }
|
||||||
`;
|
`;
|
||||||
newWindow.document.head.appendChild(style);
|
newWindow.document.head.appendChild(style);
|
||||||
|
|
||||||
newWindow.document.write("</head><body>");
|
newWindow.document.write("</head><body>");
|
||||||
newWindow.document.write(table.outerHTML); // Write the table HTML to the new window
|
newWindow.document.write(clone.outerHTML);
|
||||||
newWindow.document.write("</body></html>");
|
newWindow.document.write("</body></html>");
|
||||||
|
newWindow.document.close();
|
||||||
newWindow.document.close(); // Close the document stream
|
|
||||||
|
|
||||||
// Wait for the document to load before triggering print
|
|
||||||
newWindow.onload = () => {
|
newWindow.onload = () => {
|
||||||
newWindow.print(); // Trigger the print dialog after the content is loaded
|
newWindow.print();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user