added attandace poject wise overview

This commit is contained in:
pramod.mahajan 2025-12-10 12:09:53 +05:30
parent 363a9c5feb
commit cd1ae64753
6 changed files with 112 additions and 74 deletions

View File

@ -85,7 +85,7 @@ const Dashboard = () => {
</div> </div>
{!isAllProjectsSelected && {!isAllProjectsSelected &&
(canRegularize || canTeamAttendance || canSelfAttendance) && ( (canRegularize || canTeamAttendance || canSelfAttendance) && (
<div className="col-12 col-md-8 mb-sm-0 mb-4"> <div className="col-12 col-md-6 mb-sm-0 mb-4">
<AttendanceOverview /> <AttendanceOverview />
</div> </div>
)} )}

View File

@ -1,82 +1,83 @@
import React from "react"; import React from "react";
import { useProjectName } from "../../hooks/useProjects"; import { useProjectName } from "../../hooks/useProjects";
import { BUCKET_BG_CLASSES } from "../../utils/constants";
import { useAttendaceProjectWiseOveriew } from "../../hooks/useDashboard_Data";
import { AppColors, localToUtc } from "../../utils/appUtils";
import DatePicker from "../common/DatePicker";
import { useAppForm, useAppWatch } from "../../hooks/appHooks/useAppForm";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const ProjectWiseTeamCount = () => { const ProjectWiseTeamCount = () => {
const { projectNames, loading, isError, Errorr } = useProjectName(); const { control } = useAppForm({
resolver: zodResolver(
z.object({
date: z.string().optional(),
})
),
defaultValues: {
date: new Date().toISOString().slice(0, 10),
},
});
if (loading) { const selectedDate = useAppWatch({ control, name: "date" });
return (
<div className="card h-100 p-3 d-flex align-items-center justify-content-center">
<span>Loading...</span>
</div>
);
}
if (isError) { const { data, isLoading, isFetching, isError, error } =
return ( useAttendaceProjectWiseOveriew(localToUtc(selectedDate));
<div className="card h-100 p-3 d-flex align-items-center justify-content-center">
<span>Error: {Errorr?.message || "Something went wrong"}</span> const percent = (teamCount, attendanceCount) => {
</div> return teamCount > 0 ? Math.round((attendanceCount / teamCount) * 100) : 0;
); };
}
return ( return (
<div className="card h-100 p-2"> <div className="card h-100 p-3">
<div className="text-start"> {/* Header */}
<h5 class="card-title m-0 me-2"> Project wise Employee</h5> <div className="d-flex justify-content-between text-start mb-2">
<h5 className="card-title m-0 me-2">Project wise Employee</h5>
<DatePicker name="date" control={control} maxDate={new Date()} />
</div> </div>
{/* Only show spinner for new data, not full component */}
{isFetching && !isLoading && (
<div className="small text-end text-muted">Updating</div>
)}
{/* Table */}
<div className="table-container"> <div className="table-container">
<table className="table table-borderless mb-0"> <table className="table table-borderless mb-0">
<thead className="table-header "> <thead className="table-header">
<tr> <tr>
<th style={{ width: 40 }}>No</th> <th style={{ width: 200 }} className="text-start">
<th style={{ width: 200 }}>Project</th> Project
<th style={{ width: 150 }}>Visits</th> </th>
<th>Data In Percentage</th> <th style={{ width: 100 }}>Team Size</th>
<th style={{ width: 80 }} className="text-start">
Logged In
</th>
{/* <th>Percentage</th> */}
</tr> </tr>
</thead> </thead>
</table> </table>
<div <div
className="table-body-scroll overflow-auto" className="table-body-scroll overflow-auto pe-1"
style={{ maxHeight: "65vh" }} style={{ maxHeight: "60vh" }}
> >
<table className="table table-borderless mb-0"> <table className="table table-borderless mb-0">
<tbody> <tbody>
{(projectNames ?? []).map((item, index) => ( {(data ?? []).map((item, index) => (
<tr key={item.id || index}> <tr key={item.projectId || index}>
<td>{index + 1}</td> <td style={{ width: 200 }}>
<div
<td> className="d-flex align-items-center text-wrap my-2 text-start"
<div className="d-flex align-items-center my-2"> style={{ width: "180px" }}
{item.img && ( >
<img <span className="text-heading">{item.projectName}</span>
src={item.img}
alt={item.shortName}
height="24"
className="me-3"
/>
)}
<span className="text-heading">{item.name}</span>
</div>
</td>
<td>{item.visits}</td>
<td>
<div className="d-flex justify-content-between align-items-center gap-3">
<div
className="progress flex-grow-1"
style={{ height: 10 }}
>
<div
className={`progress-bar ${item.color}`}
style={{ width: `${item.percent}%` }}
></div>
</div>
<small>{item.percent}%</small>
</div> </div>
</td> </td>
<td className="text-center" style={{ width: 80 }}>{item.teamCount}</td>
<td className="text-center" style={{ width: 80 }} >{item.attendanceCount}</td>
{/* <td>{percent(item.teamCount, item.attendanceCount)}%</td> */}
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@ -1,6 +1,7 @@
import { useForm, Controller, FormProvider, useFormContext } from "react-hook-form"; import { useForm, Controller, FormProvider, useFormContext, useWatch } from "react-hook-form";
export const useAppForm = (config) => useForm(config); export const useAppForm = (config) => useForm(config);
export const AppFormProvider = FormProvider; export const AppFormProvider = FormProvider;
export const AppFormController = Controller; export const AppFormController = Controller;
export const useAppFormContext = useFormContext; export const useAppFormContext = useFormContext;
export const useAppWatch = useWatch;

View File

@ -1,6 +1,6 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import GlobalRepository from "../repositories/GlobalRepository"; import GlobalRepository from "../repositories/GlobalRepository";
import { useQuery } from "@tanstack/react-query"; import { keepPreviousData, useQuery } from "@tanstack/react-query";
export const useDashboard_Data = ({ days, FromDate, projectId }) => { export const useDashboard_Data = ({ days, FromDate, projectId }) => {
const [dashboard_data, setDashboard_Data] = useState([]); const [dashboard_data, setDashboard_Data] = useState([]);
@ -212,3 +212,16 @@ export const useJobsProgression = (projectId) => {
}, },
}); });
}; };
export const useAttendaceProjectWiseOveriew = (date) => {
return useQuery({
queryKey: ["attendaceOverview", date],
queryFn: async () => {
const resp = await GlobalRepository.getAttendanceProjectWiseOverview(
date
);
return resp.data;
},
keepPreviousData: true,
});
};

View File

@ -1,7 +1,11 @@
import { api } from "../utils/axiosClient"; import { api } from "../utils/axiosClient";
const GlobalRepository = { const GlobalRepository = {
getDashboardProgressionData: ({ days = '', FromDate = '', projectId = '' }) => { getDashboardProgressionData: ({
days = "",
FromDate = "",
projectId = "",
}) => {
let params; let params;
if (projectId == null) { if (projectId == null) {
params = new URLSearchParams({ params = new URLSearchParams({
@ -18,12 +22,13 @@ const GlobalRepository = {
return api.get(`/api/Dashboard/Progression?${params.toString()}`); return api.get(`/api/Dashboard/Progression?${params.toString()}`);
}, },
getProjectCompletionStatus:()=>api.get(`/api/Dashboard/project-completion-status`), getProjectCompletionStatus: () =>
api.get(`/api/Dashboard/project-completion-status`),
getDashboardAttendanceData: (date, projectId) => { getDashboardAttendanceData: (date, projectId) => {
return api.get(
return api.get(`/api/Dashboard/project-attendance/${projectId}?date=${date}`); `/api/Dashboard/project-attendance/${projectId}?date=${date}`
);
}, },
getDashboardProjectsCardData: () => { getDashboardProjectsCardData: () => {
return api.get(`/api/Dashboard/projects`); return api.get(`/api/Dashboard/projects`);
@ -44,7 +49,7 @@ const GlobalRepository = {
}, },
getExpenseData: (projectId, startDate, endDate) => { getExpenseData: (projectId, startDate, endDate) => {
let url = `api/Dashboard/expense/type` let url = `api/Dashboard/expense/type`;
const queryParams = []; const queryParams = [];
if (projectId) { if (projectId) {
queryParams.push(`projectId=${projectId}`); queryParams.push(`projectId=${projectId}`);
@ -62,10 +67,15 @@ const GlobalRepository = {
return api.get(url); return api.get(url);
}, },
getExpenseStatus: (projectId) => api.get(`/api/Dashboard/expense/pendings${projectId ? `?projectId=${projectId}` : ""}`), getExpenseStatus: (projectId) =>
api.get(
`/api/Dashboard/expense/pendings${
projectId ? `?projectId=${projectId}` : ""
}`
),
getExpenseDataByProject: (projectId, categoryId, months) => { getExpenseDataByProject: (projectId, categoryId, months) => {
let url = `api/Dashboard/expense/monthly` let url = `api/Dashboard/expense/monthly`;
const queryParams = []; const queryParams = [];
if (projectId) { if (projectId) {
queryParams.push(`projectId=${projectId}`); queryParams.push(`projectId=${projectId}`);
@ -82,13 +92,21 @@ const GlobalRepository = {
return api.get(url); return api.get(url);
}, },
getAttendanceOverview: (projectId, days) => api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`), getAttendanceOverview: (projectId, days) =>
api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`),
getCollectionOverview:(projectId) =>api.get(`/api/Dashboard/collection-overview`), getCollectionOverview: (projectId) =>
api.get(`/api/Dashboard/collection-overview`),
getJobsProgression: (projectId) => api.get(`/api/Dashboard/job/progression${projectId ? `?projectId=${projectId}` : ""}`), getJobsProgression: (projectId) =>
api.get(
`/api/Dashboard/job/progression${
projectId ? `?projectId=${projectId}` : ""
}`
),
getAttendanceProjectWiseOverview: (date) =>
api.get(`/api/dashboard/project/attendance-overview?date=${date}`),
}; };
export default GlobalRepository; export default GlobalRepository;

View File

@ -24,6 +24,11 @@ export const AppColorconfig = {
borderColor: "#eceef1", borderColor: "#eceef1",
}, },
}; };
export const AppColors = [
"primary","secodary","warning","info","danger"
]
export const getColorNameFromHex = (hex) => { export const getColorNameFromHex = (hex) => {
const normalizedHex = hex?.replace(/'/g, "").toLowerCase(); const normalizedHex = hex?.replace(/'/g, "").toLowerCase();
const colors = AppColorconfig.colors; const colors = AppColorconfig.colors;