added mutiple contact person inside branch

This commit is contained in:
pramod.mahajan 2025-11-20 12:49:56 +05:30
parent d167c57ab0
commit 047e563505
9 changed files with 238 additions and 272 deletions

View File

@ -2,15 +2,19 @@ import React, { useState } from "react";
import HorizontalBarChart from "../Charts/HorizontalBarChart";
import { useProjects } from "../../hooks/useProjects";
import { ITEMS_PER_PAGE } from "../../utils/constants";
import { useProjectCompletionStatus } from "../../hooks/useDashboard_Data";
const ProjectCompletionChart = () => {
const [currentPage, setCurrentPage] = useState(1);
const { data: projects, isLoading: loading, isError, error } = useProjects(50,currentPage);
// Bar chart logic
const projectNames = projects?.data?.map((p) => p.name) || [];
const {
data: projects,
isLoading: loading,
isError,
error,
} = useProjectCompletionStatus();
const projectNames = projects?.map((p) => p.name) || [];
const projectProgress =
projects?.data?.map((p) => {
projects?.map((p) => {
const completed = p.completedWork || 0;
const planned = p.plannedWork || 1;
const percent = planned ? (completed / planned) * 100 : 0;

View File

@ -27,59 +27,34 @@ const BranchDetails = ({ branch }) => {
</div>
);
return (
<div className="">
<>
<div className="d-flex mb-2">
<span className="fs-6 fw-medium">
<i className="bx bx-sm me-2 bx-buildings"></i>Branch Details
</span>
</div>
<div className="row mb-1">
<div className="col-4 col-md-4 text-secondary">Name:</div>
<div className="col-8 col-md-8">{data?.branchName}</div>
<div className="col-4 text-secondary">Contact No:</div>
<div className="col-8">{data?.contactInformation}</div>
</div>
<div className="row mb-1">
<div className="col-4 col-md-4 text-secondary">Type:</div>
<div className="col-8 col-md-8">{data?.branchType}</div>
</div>
<div className="row mb-1">
<div className="col-4 col-md-4 text-secondary">Contact No:</div>
<div className="col-8 col-md-8">{data?.contactInformation}</div>
<div className="col-4 text-secondary">Type:</div>
<div className="col-8">{data?.branchType}</div>
</div>
<div className="row mb-1">
<div className="col-4 col-md-4 text-secondary">Address:</div>
<div className="col-8 col-md-8">{data?.address}</div>
<div className="col-4 text-secondary">Email:</div>
<div className="col-8">{data?.email}</div>
</div>
{googleMapUrl && (
<div className="row mb-1">
<div className="col-4 col-md-4 text-secondary">Map:</div>
<div className="col-8 col-md-8 d-flex align-items-center gap-2">
<a
href={googleMapUrl}
target="_blank"
rel="noopener noreferrer"
className="text-primary text-decoration-underline text-break"
style={{ wordBreak: "break-all" }}
>
Open in Google Maps
</a>
<i
className={`bx ${
copied ? "bx-check-double text-secondry " : "bx-copy"
}`}
style={{ cursor: "pointer" }}
onClick={handleCopy}
></i>
{copied && <span className="text-secondry small">Copied!</span>}
</div>
</div>
)}
<div className="col-4 text-secondary">Address:</div>
<div className="col-8">{data?.address}</div>
</div>
</>
);
};

View File

@ -26,7 +26,6 @@ const ServiceBranch = () => {
const { data, isLoading, isError, error } = useBranches(
projectId,
// true,
!showInactive,
ITEMS_PER_PAGE - 10,
currentPage,
@ -78,18 +77,17 @@ const ServiceBranch = () => {
<div className="card-datatable" id="payment-request-table">
{/* Header Section */}
<div className="row align-items-center justify-content-between mt-3 mx-1">
<div className="col-md-6 col-sm-12 ms-n3 text-start ">
<div className="col-md-4 col-sm-12 ms-n3 text-start ">
<h5 className="mb-0">
<i className="bx bx-buildings text-primary"></i>
<span className="ms-2 fw-bold">Branch</span>
<span className="ms-2 fw-bold">Branchs</span>
</h5>
</div>
{/* Flex container for toggle + button */}
<div className="col-md-6 col-sm-12 text-end">
<div className="d-flex flex-column flex-md-row align-items-md-center justify-content-md-end gap-2">
<div className="col-md-8 col-sm-12 text-end">
<div className="d-flex flex-column flex-md-row align-items-md-center gap-2">
{/* Toggle Switch */}
<div className="form-check form-switch d-inline-flex align-items-center">
<input
type="checkbox"
@ -98,12 +96,14 @@ const ServiceBranch = () => {
checked={showInactive}
onChange={() => setShowInactive(!showInactive)}
/>
<label htmlFor="inactiveEmployeesCheckbox" className="ms-2 mt-1">
<label
htmlFor="inactiveEmployeesCheckbox"
className="ms-2 mt-1"
>
Show Deleted Branches
</label>
</div>
{/* Add Branch Button */}
<div className="d-flex justify-content-end">
<button
className="btn btn-sm btn-primary"
type="button"
@ -115,11 +115,12 @@ const ServiceBranch = () => {
}
>
<i className="bx bx-sm bx-plus-circle me-2"></i>
<span className="d-none d-md-inline-block">Add Branch</span>
Add Branch
</button>
</div>
</div>
</div>
</div>
<div className="mx-2 mt-3">
<table className="table border-top text-nowrap align-middle table-borderless">
@ -152,7 +153,6 @@ const ServiceBranch = () => {
</tr>
)}
{isError && (
<tr>
<td
@ -215,7 +215,6 @@ const ServiceBranch = () => {
</ul>
</div>
</td>
</tr>
))}
@ -247,7 +246,9 @@ const ServiceBranch = () => {
<GlobalModel
isOpen
size="md"
closeModal={() => setManageState({ IsOpen: false, branchId: null })}
closeModal={() =>
setManageState({ IsOpen: false, branchId: null })
}
>
<ManageBranch
key={manageState.branchId ?? "new"}
@ -256,7 +257,6 @@ const ServiceBranch = () => {
setManageState({ IsOpen: false, branchId: null })
}
/>
</GlobalModel>
)}
</div>

View File

@ -150,7 +150,7 @@ const JobComments = ({ data }) => {
type="submit"
disabled={!watch("comment")?.trim() || isPending}
>
Submit
Send
</button>
</div>
</form>

View File

@ -47,7 +47,11 @@ const ManageJobTicket = ({ Job }) => {
</div>
);
return (
<div className="row text-start" ref={drawerRef}>
<div
className=" text-start position-relative"
ref={drawerRef}
style={{ overflow: "visible" }}
>
<div className="col-12">
<h6 className="fs-5 fw-semibold">{data?.title}</h6>
<div className="d-flex justify-content-between align-items-start flex-wrap mb-2">
@ -56,7 +60,7 @@ const ManageJobTicket = ({ Job }) => {
{data?.jobTicketUId || "N/A"}
</p>
<div className="d-flex flex-column align-items-end gap-3 mb-3">
<div className="d-flex flex-row gap-2">
<div className="d-flex flex-row gap-2 position-relative">
<span className={`badge ${getJobStatusBadge(data?.status?.id)}`}>
{data?.status?.displayName}
</span>
@ -65,6 +69,7 @@ const ManageJobTicket = ({ Job }) => {
id="STATUS_CHANEG"
Mode="click"
className=""
align="right"
content={
<ChangeStatus
statusId={data?.status?.id}
@ -91,23 +96,33 @@ const ManageJobTicket = ({ Job }) => {
<p>{data?.description || "N/A"}</p>
</div>
<div className="d-flex justify-content-between mb-4">
<div className="d-flex flex-row gap-1 fw-medium">
<div className="d-flex justify-content-between align-items-center mb-4">
<div className="d-flex flex-row gap-1 text-secondry">
<i className="bx bx-calendar"></i>{" "}
<span>
Created Date : {formatUTCToLocalTime(data?.createdAt, true)}
</span>
</div>
<div className="d-flex flex-row gap-2 text-wraps">
{data?.tags?.map((tag, ind) => (
<span
key={`${ind}0-${tag?.name}`}
className="badge bg-label-primary"
>
{tag?.name}
</span>
))}
</div>
</div>
<div className="d-flex justify-content-md-between ">
<div className="d-flex flex-row gap-5">
<span className="fw-medium">
<span className="text-secondry">
<i className="bx bx-calendar"></i> Start Date :{" "}
{formatUTCToLocalTime(data?.startDate)}
</span>{" "}
<i className="bx bx-right-arrow-alt"></i>{" "}
<span className="fw-medium">
<span className="text-secondry">
<i className="bx bx-calendar"></i> Due on :{" "}
{formatUTCToLocalTime(data?.startDate)}
</span>
@ -117,7 +132,7 @@ const ManageJobTicket = ({ Job }) => {
const { days, color } = daysLeft(data?.startDate, data?.dueDate);
return (
<span>
<span className="fw-medium me-1">Days Left:</span>
<span className="text-secondry me-1">Days Left:</span>
<span className={`badge bg-${color}`}>
{days !== null ? `${days} days` : "N/A"}
</span>
@ -125,28 +140,79 @@ const ManageJobTicket = ({ Job }) => {
);
})()}
</div>
{data?.projectBranch && (
<div className="d-flex flex-row gap-3 my-2">
<span className="fw-semibold">
<i className="bx bx-buildings me-1"></i> Branch Name :
{/* {data?.projectBranch && (
<div className="d-flex gap-3 my-2 position-relative" ref={drawerRef} style={{ overflow: "visible" }}>
<span className="text-secondary">
<i className="bx bx-buildings"></i> Branch Name:
</span>
<HoverPopup
id="BRANCH_DETAILS"
Mode="click"
align="auto"
boundaryRef={drawerRef}
boundaryRef={drawerRef} // drawer has position-relative
content={<BranchDetails branch={data?.projectBranch?.id} />}
>
<span className="text text-decoration-underline ">
<span className="text-decoration-underline cursor-pointer">
{data?.projectBranch?.branchName}
</span>
</HoverPopup>
</div>
)}
)} */}
<div className="card shadow-none border ">
<span className="text-secondry">People</span>
<div className="border-top">
<p className="m-0 py-1">
<i className="bx bx-group"></i> Peoples
</p>
{/* Created By */}
<div className="d-flex justify-content-between align-items-start w-100">
<p className="text-secondary m-0 me-3">Created By</p>
<div className="flex-grow-1 d-flex align-items-center gap-2">
<Avatar
size="xs"
firstName={data?.createdBy?.firstName}
lastName={data?.createdBy?.lastName}
/>
<div className="d-flex flex-column">
<p className="m-0 text-truncate">
{data?.createdBy?.firstName} {data?.createdBy?.lastName}
</p>
<small className="text-secondary text-xs">
{data?.createdBy?.jobRoleName}
</small>
</div>
</div>
</div>
{/* Assigned To */}
<div className="d-flex flex-column flex-md-row align-items-start w-100 mt-2">
<p className="text-secondary m-0 me-3">Assigned To</p>
<div className="flex-grow-1">
<div className="d-flex flex-wrap gap-3">
{data?.assignees?.map((emp) => (
<div key={emp.id} className="d-flex align-items-center">
<Avatar
size="xs"
firstName={emp.firstName}
lastName={emp.lastName}
/>
<div className="d-flex flex-column ms-2 text-truncate">
<span className="text-truncate">
{emp.firstName} {emp.lastName}
</span>
<small className="text-secondary text-xs text-truncate">
{emp.jobRoleName}
</small>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>

View File

@ -1,6 +1,10 @@
import React, { useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { closePopup, openPopup, togglePopup } from "../../slices/localVariablesSlice";
import {
closePopup,
openPopup,
togglePopup,
} from "../../slices/localVariablesSlice";
/**
* align: "auto" | "left" | "right"
@ -63,7 +67,8 @@ const HoverPopup = ({
const popup = popupRef.current;
// choose boundary: provided boundaryRef or nearest positioned parent (popup.parentElement)
const boundaryEl = (boundaryRef && boundaryRef.current) || popup.parentElement;
const boundaryEl =
(boundaryRef && boundaryRef.current) || popup.parentElement;
if (!boundaryEl) return;
const boundaryRect = boundaryEl.getBoundingClientRect();
@ -75,15 +80,12 @@ const HoverPopup = ({
popup.style.transform = "";
popup.style.top = "";
// default: place below trigger and center horizontally relative to parent
// We'll use absolute positioning with respect to the positioned parent container.
// Ensure popup is positioned using left/right in parent's coordinate system.
// Compute desired left (centered under trigger)
const popupRect = popup.getBoundingClientRect();
const parentRect = boundaryRect; // alias
// Convert trigger center to parent coordinates
const triggerCenterX = triggerRect.left + triggerRect.width / 2 - parentRect.left;
const triggerCenterX =
triggerRect.left + triggerRect.width / 2 - parentRect.left;
// preferred left so popup center aligns to trigger center:
const preferredLeft = triggerCenterX - popupRect.width / 2;
@ -111,10 +113,19 @@ const HoverPopup = ({
setRight(0);
return;
}
if (align === "center") {
popup.style.left = "50%";
popup.style.right = "auto";
popup.style.transform = "translateX(-50%)";
return;
}
// align === "auto": try preferred centered position, but flip fully if overflow
// clamp preferredLeft to boundaries so it doesn't render partially outside
const leftIfCentered = Math.max(0, Math.min(preferredLeft, parentRect.width - popupRect.width));
const leftIfCentered = Math.max(
0,
Math.min(preferredLeft, parentRect.width - popupRect.width)
);
// if centered fits, use it
if (leftIfCentered === preferredLeft) {
@ -142,8 +153,17 @@ const HoverPopup = ({
}, [visible, align, boundaryRef]);
return (
<div className="d-inline-block position-relative">
<div
className="d-inline-block "
style={{
maxWidth: "calc(700px - 100px)",
width: "100%",
wordWrap: "break-word",
overflow: "hidden",
}}
>
<div
className="d-inline-block"
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}

View File

@ -2,7 +2,6 @@ import { useState, useEffect } from "react";
import GlobalRepository from "../repositories/GlobalRepository";
import { useQuery } from "@tanstack/react-query";
export const useDashboard_Data = ({ days, FromDate, projectId }) => {
const [dashboard_data, setDashboard_Data] = useState([]);
const [isLineChartLoading, setLoading] = useState(false);
@ -18,11 +17,13 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => {
try {
const payload = {
days,
FromDate: FromDate || '',
FromDate: FromDate || "",
projectId: projectId || null,
};
const response = await GlobalRepository.getDashboardProgressionData(payload);
const response = await GlobalRepository.getDashboardProgressionData(
payload
);
setDashboard_Data(response.data);
} catch (err) {
setError("Failed to fetch dashboard data.");
@ -38,123 +39,6 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => {
return { dashboard_data, loading: isLineChartLoading, error };
};
// export const useDashboard_AttendanceData = (date, projectId) => {
// const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]);
// const [isLineChartLoading, setLoading] = useState(false);
// const [error, setError] = useState("");
// useEffect(() => {
// const fetchData = async () => {
// setLoading(true);
// setError("");
// try {
// const response = await GlobalRepository.getDashboardAttendanceData(date, projectId); // date in 2nd param
// setDashboard_AttendanceData(response.data);
// } catch (err) {
// setError("Failed to fetch dashboard data.");
// console.error(err);
// } finally {
// setLoading(false);
// }
// };
// if (date && projectId !== null) {
// fetchData();
// }
// }, [date, projectId]);
// return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error };
// };
// 🔹 Dashboard Projects Card Data Hook
// export const useDashboardProjectsCardData = () => {
// const [projectsCardData, setProjectsData] = useState([]);
// const [loading, setLoading] = useState(false);
// const [error, setError] = useState("");
// useEffect(() => {
// const fetchProjectsData = async () => {
// setLoading(true);
// setError("");
// try {
// const response = await GlobalRepository.getDashboardProjectsCardData();
// setProjectsData(response.data);
// } catch (err) {
// setError("Failed to fetch projects card data.");
// console.error(err);
// } finally {
// setLoading(false);
// }
// };
// fetchProjectsData();
// }, []);
// return { projectsCardData, loading, error };
// };
// 🔹 Dashboard Teams Card Data Hook
// export const useDashboardTeamsCardData = (projectId) => {
// const [teamsCardData, setTeamsData] = useState({});
// const [loading, setLoading] = useState(false);
// const [error, setError] = useState("");
// useEffect(() => {
// const fetchTeamsData = async () => {
// setLoading(true);
// setError("");
// try {
// const response = await GlobalRepository.getDashboardTeamsCardData(projectId);
// setTeamsData(response.data || {});
// } catch (err) {
// setError("Failed to fetch teams card data.");
// console.error("Error fetching teams card data:", err);
// setTeamsData({});
// } finally {
// setLoading(false);
// }
// };
// fetchTeamsData();
// }, [projectId]);
// return { teamsCardData, loading, error };
// };
// export const useDashboardTasksCardData = (projectId) => {
// const [tasksCardData, setTasksData] = useState({});
// const [loading, setLoading] = useState(false);
// const [error, setError] = useState("");
// useEffect(() => {
// const fetchTasksData = async () => {
// setLoading(true);
// setError("");
// try {
// const response = await GlobalRepository.getDashboardTasksCardData(projectId);
// setTasksData(response.data);
// } catch (err) {
// setError("Failed to fetch tasks card data.");
// console.error(err);
// setTasksData({});
// } finally {
// setLoading(false);
// }
// };
// fetchTasksData();
// }, [projectId]);
// return { tasksCardData, loading, error };
// };
export const useAttendanceOverviewData = (projectId, days) => {
const [attendanceOverviewData, setAttendanceOverviewData] = useState([]);
const [loading, setLoading] = useState(false);
@ -167,7 +51,10 @@ export const useAttendanceOverviewData = (projectId, days) => {
setError("");
try {
const response = await GlobalRepository.getAttendanceOverview(projectId, days);
const response = await GlobalRepository.getAttendanceOverview(
projectId,
days
);
setAttendanceOverviewData(response.data);
} catch (err) {
setError("Failed to fetch attendance overview data.");
@ -182,7 +69,6 @@ export const useAttendanceOverviewData = (projectId, days) => {
return { attendanceOverviewData, loading, error };
};
// -------------------Query----------------------------
// export const useDashboard_Data = (days, FromDate, projectId)=>{
@ -199,39 +85,47 @@ export const useAttendanceOverviewData = (projectId, days) => {
// }
// })
// }
export const useProjectCompletionStatus = () => {
return useQuery({
queryKey: ["projectCompletionStatus"],
queryFn: async () => {
const resp = await await GlobalRepository.getProjectCompletionStatus();
return resp.data;
},
});
};
export const useDashboard_AttendanceData = (date, projectId) => {
return useQuery({
queryKey: ["dashboardAttendances", date, projectId],
queryFn: async () => {
const resp = await await GlobalRepository.getDashboardAttendanceData(date, projectId)
const resp = await await GlobalRepository.getDashboardAttendanceData(
date,
projectId
);
return resp.data;
}
})
}
},
});
};
export const useDashboardTeamsCardData = (projectId) => {
return useQuery({
queryKey: ["dashboardTeams", projectId],
queryFn: async () => {
const resp = await GlobalRepository.getDashboardTeamsCardData(projectId)
const resp = await GlobalRepository.getDashboardTeamsCardData(projectId);
return resp.data;
}
})
}
},
});
};
export const useDashboardTasksCardData = (projectId) => {
return useQuery({
queryKey: ["dashboardTasks", projectId],
queryFn: async () => {
const resp = await GlobalRepository.getDashboardTasksCardData(projectId)
const resp = await GlobalRepository.getDashboardTasksCardData(projectId);
return resp.data;
}
})
}
},
});
};
// export const useAttendanceOverviewData = (projectId, days) => {
// return useQuery({
// queryKey:["dashboardAttendanceOverView",projectId],
@ -247,24 +141,25 @@ export const useDashboardProjectsCardData = () => {
return useQuery({
queryKey: ["dashboardProjects"],
queryFn: async () => {
const resp = await GlobalRepository.getDashboardProjectsCardData();
return resp.data;
}
})
}
},
});
};
export const useExpenseAnalysis = (projectId, startDate, endDate) => {
const hasBothDates = !!startDate && !!endDate;
const noDatesSelected = !startDate && !endDate;
const shouldFetch =
noDatesSelected ||
hasBothDates;
const shouldFetch = noDatesSelected || hasBothDates;
return useQuery({
queryKey: ["expenseAnalysis", projectId, startDate, endDate],
queryFn: async () => {
const resp = await GlobalRepository.getExpenseData(projectId, startDate, endDate);
const resp = await GlobalRepository.getExpenseData(
projectId,
startDate,
endDate
);
return resp.data;
},
enabled: shouldFetch,
@ -280,17 +175,20 @@ export const useExpenseStatus = (projectId) => {
queryFn: async () => {
const resp = await GlobalRepository.getExpenseStatus(projectId);
return resp.data;
}
})
}
},
});
};
export const useExpenseDataByProject = (projectId, categoryId, months) => {
return useQuery({
queryKey: ["expenseByProject", projectId, categoryId, months],
queryFn: async () => {
const resp = await GlobalRepository.getExpenseDataByProject(projectId, categoryId, months);
const resp = await GlobalRepository.getExpenseDataByProject(
projectId,
categoryId,
months
);
return resp.data;
},
});
};

View File

@ -18,6 +18,8 @@ const GlobalRepository = {
return api.get(`/api/Dashboard/Progression?${params.toString()}`);
},
getProjectCompletionStatus:()=>api.get(`/api/Dashboard/project-completion-status`),
getDashboardAttendanceData: (date, projectId) => {

View File

@ -1,6 +1,7 @@
import { api } from "../utils/axiosClient";
const ProjectRepository = {
getProjectList: (pageSize, pageNumber) =>
api.get(`/api/project/list?pageSize=${pageSize}&pageNumber=${pageNumber}`),
getProjectByprojectId: (projetid) =>