Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Kartik_Task_InfraMask#1266

This commit is contained in:
Kartik Sharma 2025-09-25 10:06:02 +05:30
commit 3e7b2c1a15
30 changed files with 1671 additions and 841 deletions

View File

@ -1,194 +1,194 @@
import React, { useState, useEffect } from "react"; // import React, { useState, useEffect } from "react";
import LineChart from "../Charts/LineChart"; // import LineChart from "../Charts/LineChart";
import { useProjects } from "../../hooks/useProjects"; // import { useProjects } from "../../hooks/useProjects";
import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data"; // import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data";
import ApexChart from "../Charts/Circlechart"; // import ApexChart from "../Charts/Circlechart";
const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId"; // const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId";
const Activity = () => { // const Activity = () => {
const { projects } = useProjects(); // const { projects } = useProjects();
const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD // const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
const [selectedDate, setSelectedDate] = useState(today); // const [selectedDate, setSelectedDate] = useState(today);
const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY); // const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
const initialProjectId = storedProjectId || "all"; // const initialProjectId = storedProjectId || "all";
const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId); // const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
const [displayedProjectName, setDisplayedProjectName] = useState("Select Project"); // const [displayedProjectName, setDisplayedProjectName] = useState("Select Project");
const [activeTab, setActiveTab] = useState("all"); // const [activeTab, setActiveTab] = useState("all");
const { dashboard_Activitydata: ActivityData, isLoading, error: isError } = // const { dashboard_Activitydata: ActivityData, isLoading, error: isError } =
useDashboard_ActivityData(selectedDate, selectedProjectId); // useDashboard_ActivityData(selectedDate, selectedProjectId);
useEffect(() => { // useEffect(() => {
if (selectedProjectId === "all") { // if (selectedProjectId === "all") {
setDisplayedProjectName("All Projects"); // setDisplayedProjectName("All Projects");
} else if (projects) { // } else if (projects) {
const foundProject = projects.find((p) => p.id === selectedProjectId); // const foundProject = projects.find((p) => p.id === selectedProjectId);
setDisplayedProjectName(foundProject ? foundProject.name : "Select Project"); // setDisplayedProjectName(foundProject ? foundProject.name : "Select Project");
} else { // } else {
setDisplayedProjectName("Select Project"); // setDisplayedProjectName("Select Project");
} // }
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId); // localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
}, [selectedProjectId, projects]); // }, [selectedProjectId, projects]);
const handleProjectSelect = (projectId) => { // const handleProjectSelect = (projectId) => {
setSelectedProjectId(projectId); // setSelectedProjectId(projectId);
}; // };
const handleDateChange = (e) => { // const handleDateChange = (e) => {
setSelectedDate(e.target.value); // setSelectedDate(e.target.value);
}; // };
return ( // return (
<div className="card h-100"> // <div className="card h-100">
<div className="card-header"> // <div className="card-header">
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0"> // <div className="d-flex flex-wrap justify-content-between align-items-center mb-0">
<div className="card-title mb-0 text-start"> // <div className="card-title mb-0 text-start">
<h5 className="mb-1">Activity</h5> // <h5 className="mb-1">Activity</h5>
<p className="card-subtitle">Activity Progress Chart</p> // <p className="card-subtitle text-primary">Activity Progress Chart</p>
</div> // </div>
<div className="btn-group"> // <div className="btn-group">
<button // <button
className="btn btn-outline-primary btn-sm dropdown-toggle" // className="btn btn-outline-primary btn-sm dropdown-toggle"
type="button" // type="button"
data-bs-toggle="dropdown" // data-bs-toggle="dropdown"
aria-expanded="false" // aria-expanded="false"
> // >
{displayedProjectName} // {displayedProjectName}
</button> // </button>
<ul className="dropdown-menu"> // <ul className="dropdown-menu">
<li> // <li>
<button className="dropdown-item" onClick={() => handleProjectSelect("all")}> // <button className="dropdown-item" onClick={() => handleProjectSelect("all")}>
All Projects // All Projects
</button> // </button>
</li> // </li>
{projects?.map((project) => ( // {projects?.map((project) => (
<li key={project.id}> // <li key={project.id}>
<button // <button
className="dropdown-item" // className="dropdown-item"
onClick={() => handleProjectSelect(project.id)} // onClick={() => handleProjectSelect(project.id)}
> // >
{project.name} // {project.name}
</button> // </button>
</li> // </li>
))} // ))}
</ul> // </ul>
</div> // </div>
</div> // </div>
</div> // </div>
{/* ✅ Date Picker Aligned Left with Padding */} // {/* Date Picker Aligned Left with Padding */}
<div className="d-flex justify-content-start ps-3 mb-3"> // <div className="d-flex justify-content-start ps-3 mb-3">
<div style={{ width: "150px" }}> // <div style={{ width: "150px" }}>
<input // <input
type="date" // type="date"
className="form-control" // className="form-control"
value={selectedDate} // value={selectedDate}
onChange={handleDateChange} // onChange={handleDateChange}
/> // />
</div> // </div>
</div> // </div>
{/* Tabs */} // {/* Tabs */}
<ul className="nav nav-tabs " role="tablist"> // <ul className="nav nav-tabs " role="tablist">
<li className="nav-item"> // <li className="nav-item">
<button // <button
type="button" // type="button"
className={`nav-link ${activeTab === "all" ? "active" : ""}`} // className={`nav-link ${activeTab === "all" ? "active" : ""}`}
onClick={() => setActiveTab("all")} // onClick={() => setActiveTab("all")}
data-bs-toggle="tab" // data-bs-toggle="tab"
> // >
Summary // Summary
</button> // </button>
</li> // </li>
<li className="nav-item"> // <li className="nav-item">
<button // <button
type="button" // type="button"
className={`nav-link ${activeTab === "logs" ? "active" : ""}`} // className={`nav-link ${activeTab === "logs" ? "active" : ""}`}
onClick={() => setActiveTab("logs")} // onClick={() => setActiveTab("logs")}
data-bs-toggle="tab" // data-bs-toggle="tab"
> // >
Details // Details
</button> // </button>
</li> // </li>
</ul> // </ul>
<div className="card-body"> // <div className="card-body">
{activeTab === "all" && ( // {activeTab === "all" && (
<div className="row justify-content-between"> // <div className="row justify-content-between">
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4"> // <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
{isLoading ? ( // {isLoading ? (
<p>Loading activity data...</p> // <p>Loading activity data...</p>
) : isError ? ( // ) : isError ? (
<p>No data available.</p> // <p>No data available.</p>
) : ( // ) : (
ActivityData && ( // ActivityData && (
<> // <>
<h5 className="fw-bold mb-0 text-start w-80"> // <h5 className="fw-bold mb-0 text-start w-80">
<i className="bx bx-task text-info"></i> Allocated Task // <i className="bx bx-task text-info"></i> Allocated Task
</h5> // </h5>
<h4 className="mb-0 fw-bold"> // <h4 className="mb-0 fw-bold">
{ActivityData.totalCompletedWork?.toLocaleString()}/ // {ActivityData.totalCompletedWork?.toLocaleString()}/
{ActivityData.totalPlannedWork?.toLocaleString()} // {ActivityData.totalPlannedWork?.toLocaleString()}
</h4> // </h4>
<small className="text-muted">Completed / Assigned</small> // <small className="text-muted">Completed / Assigned</small>
<div style={{ maxWidth: "180px" }}> // <div style={{ maxWidth: "180px" }}>
<ApexChart /> // <ApexChart />
</div> // </div>
</> // </>
) // )
)} // )}
</div> // </div>
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4"> // <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
{!isLoading && !isError && ActivityData && ( // {!isLoading && !isError && ActivityData && (
<> // <>
<h5 className="fw-bold mb-0 text-start w-110"> // <h5 className="fw-bold mb-0 text-start w-110">
<i className="bx bx-task text-info"></i> Activities // <i className="bx bx-task text-info"></i> Activities
</h5> // </h5>
<h4 className="mb-0 fw-bold"> // <h4 className="mb-0 fw-bold">
{ActivityData.totalCompletedWork?.toLocaleString()}/ // {ActivityData.totalCompletedWork?.toLocaleString()}/
{ActivityData.totalPlannedWork?.toLocaleString()} // {ActivityData.totalPlannedWork?.toLocaleString()}
</h4> // </h4>
<small className="text-muted ">Pending / Assigned</small> // <small className="text-muted ">Pending / Assigned</small>
<div style={{ maxWidth: "180px" }}> // <div style={{ maxWidth: "180px" }}>
<ApexChart /> // <ApexChart />
</div> // </div>
</> // </>
)} // )}
</div> // </div>
</div> // </div>
)} // )}
{activeTab === "logs" && ( // {activeTab === "logs" && (
<div className="table-responsive"> // <div className="table-responsive">
<table className="table table-bordered table-hover"> // <table className="table table-bordered table-hover">
<thead> // <thead>
<tr> // <tr>
<th>Activity / Location</th> // <th>Activity / Location</th>
<th>Assigned / Completed</th> // <th>Assigned / Completed</th>
</tr> // </tr>
</thead> // </thead>
<tbody> // <tbody>
{[{ // {[{
activity: "Code Review / Remote", // activity: "Code Review / Remote",
assignedToday: 3, // assignedToday: 3,
completed: 2 // completed: 2
}].map((log, index) => ( // }].map((log, index) => (
<tr key={index}> // <tr key={index}>
<td>{log.activity}</td> // <td>{log.activity}</td>
<td>{log.assignedToday} / {log.completed}</td> // <td>{log.assignedToday} / {log.completed}</td>
</tr> // </tr>
))} // ))}
</tbody> // </tbody>
</table> // </table>
</div> // </div>
)} // )}
</div> // </div>
</div> // </div>
); // );
}; // };
export default Activity; // export default Activity;

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState,useMemo } from "react"; import React, { useEffect, useState, useMemo } from "react";
import { FormProvider, useForm, Controller } from "react-hook-form"; import { FormProvider, useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { defaultFilter, SearchSchema } from "./ExpenseSchema"; import { defaultFilter, SearchSchema } from "./ExpenseSchema";
@ -16,8 +16,11 @@ import { ExpenseFilterSkeleton } from "./ExpenseSkeleton";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
const selectedProjectId = useSelector((store) => store.localVariables.projectId); const selectedProjectId = useSelector(
const { data, isLoading,isError,error,isFetching , isFetched} = useExpenseFilter(); (store) => store.localVariables.projectId
);
const { data, isLoading, isError, error, isFetching, isFetched } =
useExpenseFilter();
const groupByList = useMemo(() => { const groupByList = useMemo(() => {
return [ return [
@ -27,11 +30,10 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
{ id: "project", name: "Project" }, { id: "project", name: "Project" },
{ id: "paymentMode", name: "Payment Mode" }, { id: "paymentMode", name: "Payment Mode" },
{ id: "expensesType", name: "Expense Type" }, { id: "expensesType", name: "Expense Type" },
{ id: "createdAt", name: "Submitted Date" } { id: "createdAt", name: "Submitted Date" },
].sort((a, b) => a.name.localeCompare(b.name)); ].sort((a, b) => a.name.localeCompare(b.name));
}, []); }, []);
const [selectedGroup, setSelectedGroup] = useState(groupByList[0]); const [selectedGroup, setSelectedGroup] = useState(groupByList[0]);
const [resetKey, setResetKey] = useState(0); const [resetKey, setResetKey] = useState(0);
@ -40,7 +42,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
defaultValues: defaultFilter, defaultValues: defaultFilter,
}); });
const { control, register, handleSubmit, reset, watch } = methods; const { control, handleSubmit, reset, setValue, watch } = methods;
const isTransactionDate = watch("isTransactionDate"); const isTransactionDate = watch("isTransactionDate");
const closePanel = () => { const closePanel = () => {
@ -78,28 +80,37 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
}, [location]); }, [location]);
if (isLoading || isFetching) return <ExpenseFilterSkeleton />; if (isLoading || isFetching) return <ExpenseFilterSkeleton />;
if(isError && isFetched) return <div>Something went wrong Here- {error.message} </div> if (isError && isFetched)
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">
<div className="mb-3 w-100"> <div className="mb-3 w-100">
<div className="d-flex align-items-center mb-2"> <div className="d-flex align-items-center mb-2">
<label className="form-label me-2">Choose Date:</label> <label className="form-label me-2">Filter By:</label>
<div className="form-check form-switch m-0"> <div className="d-inline-flex border rounded-pill mb-1 overflow-hidden shadow-none">
<input <button
className="form-check-input" type="button"
type="checkbox" className={`btn px-2 py-1 rounded-0 text-tiny ${
id="switchOption1" isTransactionDate ? "active btn-primary text-white" : ""
{...register("isTransactionDate")} }`}
/> onClick={() => setValue("isTransactionDate", true)}
>
Transaction Date
</button>
<button
type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${
!isTransactionDate ? "active btn-primary text-white" : ""
}`}
onClick={() => setValue("isTransactionDate", false)}
>
Submitted Date
</button>
</div> </div>
<label className="form-label mb-0 ms-2">
{isTransactionDate ? "Submitted": "Transaction" }
</label>
</div> </div>
<label className="fw-semibold">Choose Date Range:</label>
<DateRangePicker1 <DateRangePicker1
placeholder="DD-MM-YYYY To DD-MM-YYYY" placeholder="DD-MM-YYYY To DD-MM-YYYY"
startField="startDate" startField="startDate"
@ -169,7 +180,9 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
</div> </div>
</div> </div>
<div className="mb-2 text-start "> <div className="mb-2 text-start ">
<label htmlFor="groupBySelect" className="form-label">Group By :</label> <label htmlFor="groupBySelect" className="form-label">
Group By :
</label>
<select <select
id="groupBySelect" id="groupBySelect"
className="form-select form-select-sm" className="form-select form-select-sm"
@ -198,7 +211,6 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
</div> </div>
</form> </form>
</FormProvider> </FormProvider>
</> </>
); );
}; };

View File

@ -394,6 +394,15 @@ const Header = () => {
</li> </li>
<li> <li>
<div className="dropdown-divider"></div> <div className="dropdown-divider"></div>
</li>
<li onClick={()=>onOpen()}>
{" "}
<a
className="dropdown-item cusor-pointer"
>
<i className="bx bx-transfer-alt me-2"></i>
<span className="align-middle">Switch Tenant</span>
</a>
</li> </li>
<li onClick={handleProfilePage}> <li onClick={handleProfilePage}>
<a <a
@ -424,15 +433,7 @@ const Header = () => {
</a> </a>
</li> </li>
<li onClick={()=>onOpen()}>
{" "}
<a
className="dropdown-item cusor-pointer"
>
<i className="bx bx-transfer-alt me-2"></i>
<span className="align-middle">Switch Tenant</span>
</a>
</li>
<li> <li>
<div className="dropdown-divider"></div> <div className="dropdown-divider"></div>
</li> </li>
@ -440,7 +441,7 @@ const Header = () => {
<a <a
aria-label="click to log out" aria-label="click to log out"
className="dropdown-item cusor-pointer" className="dropdown-item cusor-pointer"
onClick={()=>handleLogout()} onClick={()=>logout()}
> >
{logouting ? "Please Wait":<> <i className="bx bx-log-out me-2"></i> {logouting ? "Please Wait":<> <i className="bx bx-log-out me-2"></i>
<span className="align-middle">SignOut</span></>} <span className="align-middle">SignOut</span></>}

View File

@ -171,8 +171,8 @@ const AssignOrg = ({ setStep }) => {
{flowType !== "default" && ( {flowType !== "default" && (
<> <>
{/* Organization Type */} {/* Organization Type */}
<div className="mb-1 text-start"> <div className="mb-3 text-start">
<Label htmlFor="organizationTypeId" required> <Label htmlFor="organizationTypeId" className="mb-3 fw-semibold" required>
Organization Type Organization Type
</Label> </Label>
<div className="d-flex flex-wrap gap-3 mt-1"> <div className="d-flex flex-wrap gap-3 mt-1">
@ -206,11 +206,11 @@ const AssignOrg = ({ setStep }) => {
{/* Services */} {/* Services */}
<div className="mb-3"> <div className="mb-3">
<Label htmlFor="serviceIds" required> <Label htmlFor="serviceIds" className="mb-3 fw-semibold" required>
Select Services Select Services
</Label> </Label>
{mergedServices?.map((service) => ( {mergedServices?.map((service) => (
<div key={service.id} className="form-check"> <div key={service.id} className="form-check mb-3">
<input <input
type="checkbox" type="checkbox"
value={service.id} value={service.id}
@ -230,7 +230,7 @@ const AssignOrg = ({ setStep }) => {
)} )}
{/* Buttons: Always visible */} {/* Buttons: Always visible */}
<div className="d-flex justify-content-between mt-3"> <div className="d-flex justify-content-between mt-5">
<button <button
type="button" type="button"
className="btn btn-xs btn-outline-secondary" className="btn btn-xs btn-outline-secondary"
@ -248,7 +248,7 @@ const AssignOrg = ({ setStep }) => {
? "Please wait..." ? "Please wait..."
: flowType === "default" : flowType === "default"
? "Assign Organization" ? "Assign Organization"
: "Add"} : "Assign Project"}
</button> </button>
</div> </div>
</form> </form>

View File

@ -146,6 +146,7 @@ const ManagOrg = () => {
required required
valueKey="id" valueKey="id"
options={service?.data || []} options={service?.data || []}
required = {true}
/> />
{errors.serviceIds && ( {errors.serviceIds && (
<span className="danger-text">{errors.serviceIds.message}</span> <span className="danger-text">{errors.serviceIds.message}</span>

View File

@ -84,10 +84,7 @@ const OrgPickerFromSPId = ({ title, placeholder }) => {
height={50} height={50}
/> />
</div> </div>
<div <div className="d-flex flex-column p-0 m-0 cursor-pointer">
className="d-flex flex-column p-0 m-0 cursor-pointer"
onClick={() => onOpen({ startStep: 3, orgData: org })}
>
<span className="fs-6 fw-semibold">{org.name}</span> <span className="fs-6 fw-semibold">{org.name}</span>
<div className="d-flex gap-2"> <div className="d-flex gap-2">
<small <small
@ -102,6 +99,16 @@ const OrgPickerFromSPId = ({ title, placeholder }) => {
<small className="text-small fw-semibold">Address:</small> <small className="text-small fw-semibold">Address:</small>
<div className="d-flex text-wrap">{org.address}</div> <div className="d-flex text-wrap">{org.address}</div>
</div> </div>
<div className="m-0 p-0">
{" "}
<button
type="submit"
className="btn btn-sm btn-primary"
onClick={() => onOpen({ startStep: 3, orgData: org })}
>
Select
</button>
</div>
</div> </div>
</div> </div>
))} ))}

View File

@ -85,7 +85,7 @@ const OrgPickerfromTenant = ({ title }) => {
{isLoading ? ( {isLoading ? (
<div>Loading....</div> <div>Loading....</div>
) : data && data?.data?.length > 0 ? ( ) : data && data?.data?.length > 0 ? (
<div className="dataTables_wrapper no-footer pb-2"> <div className="dataTables_wrapper no-footer pb-5">
<table className="table dataTable text-nowrap"> <table className="table dataTable text-nowrap">
<thead> <thead>
<tr className="table_header_border"> <tr className="table_header_border">
@ -99,6 +99,13 @@ const OrgPickerfromTenant = ({ title }) => {
</th> </th>
</tr> </tr>
</thead> </thead>
</table>
<div
className="scrollable-tbody overflow-y-auto"
style={{ maxHeight: "350px", overflowY: "auto" }}
>
<table className="table dataTable text-nowrap mb-0">
<tbody> <tbody>
{Array.isArray(data.data) && data.data.length > 0 {Array.isArray(data.data) && data.data.length > 0
? data.data.map((row, i) => ( ? data.data.map((row, i) => (
@ -108,13 +115,18 @@ const OrgPickerfromTenant = ({ title }) => {
{col.getValue(row)} {col.getValue(row)}
</td> </td>
))} ))}
<td className="sticky-action-column bg-white"> <td className="sticky-action-column p-0 bg-white">
<div className="p-1">
<button <button
type="submit"
className="btn btn-sm btn-primary" className="btn btn-sm btn-primary"
onClick={() => onOpen({ startStep: 3, orgData: row })} onClick={() =>
onOpen({ startStep: 3, orgData: row })
}
> >
Select Select
</button> </button>
</div>
</td> </td>
</tr> </tr>
)) ))
@ -122,6 +134,7 @@ const OrgPickerfromTenant = ({ title }) => {
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
) : null} ) : null}
<div className="d-flex flex-column align-items-center text-center text-wrap text-black gap-2"> <div className="d-flex flex-column align-items-center text-center text-wrap text-black gap-2">
<small className="mb-1"> <small className="mb-1">

View File

@ -60,7 +60,7 @@ const OrganizationModal = () => {
if (startStep === 1) { if (startStep === 1) {
return orgData && orgData !== null return orgData && orgData !== null
? "Add Organization" ? "Add Organization"
: "Choose Organization1"; : "Choose Organization";
} }
if (startStep === 2) { if (startStep === 2) {

View File

@ -93,7 +93,7 @@ const OrganizationsList = ({searchText}) => {
if (isError) return <div>{error?.message || "Something went wrong"}</div>; if (isError) return <div>{error?.message || "Something went wrong"}</div>;
return ( return (
<div className="card px-0 px-sm-4"> <div className="card px-0 px-sm-4 pb-12 pt-5">
<div className="card-datatable table-responsive" id="horizontal-example"> <div className="card-datatable table-responsive" id="horizontal-example">
<div className="dataTables_wrapper no-footer px-2"> <div className="dataTables_wrapper no-footer px-2">
<table className="table border-top dataTable text-nowrap"> <table className="table border-top dataTable text-nowrap">

View File

@ -14,13 +14,13 @@ import {
getProjectStatusName, getProjectStatusName,
} from "../../utils/projectStatus"; } from "../../utils/projectStatus";
import GlobalModel from "../common/GlobalModel"; import GlobalModel from "../common/GlobalModel";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
const ProjectCard = ({ projectData, recall }) => { const ProjectCard = ({ projectData, recall }) => {
const [ projectInfo, setProjectInfo ] = useState( projectData ); const [projectInfo, setProjectInfo] = useState(projectData);
const { projects_Details, loading, error, refetch } = useProjectDetails( const { projects_Details, loading, error, refetch } = useProjectDetails(
projectInfo?.id,false projectInfo?.id, false
); );
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const dispatch = useDispatch() const dispatch = useDispatch()
@ -37,9 +37,9 @@ const ProjectCard = ({ projectData, recall }) => {
}, },
}) })
useEffect(()=>{ useEffect(() => {
setProjectInfo(projectData); setProjectInfo(projectData);
}, [ projectData ] ) }, [projectData])
const handleShow = async () => { const handleShow = async () => {
try { try {
@ -63,6 +63,10 @@ const ProjectCard = ({ projectData, recall }) => {
dispatch(setProjectId(projectInfo.id)) dispatch(setProjectId(projectInfo.id))
navigate(`/projects/details`); navigate(`/projects/details`);
}; };
const handleViewActivities = () => {
dispatch(setProjectId(projectInfo.id))
navigate(`/activities/records?project=${projectInfo.id}`);
};
const handleFormSubmit = (updatedProject) => { const handleFormSubmit = (updatedProject) => {
if (projectInfo?.id) { if (projectInfo?.id) {
@ -71,7 +75,7 @@ const ProjectCard = ({ projectData, recall }) => {
updatedData: updatedProject, updatedData: updatedProject,
}); });
} }
}; };
return ( return (
<> <>
@ -157,11 +161,7 @@ const ProjectCard = ({ projectData, recall }) => {
</a> </a>
</li> </li>
<li <li
onClick={() => onClick={handleViewActivities}
navigate(
`/activities/records?project=${projectInfo.id}`
)
}
> >
<a className="dropdown-item"> <a className="dropdown-item">
<i className="bx bx-task me-2"></i> <i className="bx bx-task me-2"></i>

View File

@ -41,8 +41,8 @@ const ProjectNav = ({ onPillClick, activePill }) => {
hidden: !(DirAdmin || DireManager || DirUser), hidden: !(DirAdmin || DireManager || DirUser),
}, },
{ key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) }, { key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) },
{ key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam },
{ key: "organization", icon: "bx bx-buildings", label: "Organization"}, { key: "organization", icon: "bx bx-buildings", label: "Organization"},
{ key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam },
]; ];
return ( return (
<div className="nav-align-top"> <div className="nav-align-top">

View File

@ -7,7 +7,7 @@ const ProjectAssignedOrgs = () => {
const { data, isLoading, isError, error } = const { data, isLoading, isError, error } =
useProjectAssignedOrganizations(selectedProject); useProjectAssignedOrganizations(selectedProject);
const contactList = [ const orgList = [
{ {
key: "name", key: "name",
label: "Organization Name", label: "Organization Name",
@ -23,6 +23,16 @@ const ProjectAssignedOrgs = () => {
</div> </div>
), ),
align: "text-start", align: "text-start",
},
{
key: "service",
label: "Service Name",
getValue: (org) => (
<div className="d-flex gap-2 py-1 ">
N/A
</div>
),
align: "text-start",
}, },
{ {
key: "sprid", key: "sprid",
@ -61,7 +71,7 @@ const ProjectAssignedOrgs = () => {
<table className="table dataTable text-nowrap"> <table className="table dataTable text-nowrap">
<thead> <thead>
<tr className="table_header_border"> <tr className="table_header_border">
{contactList.map((col) => ( {orgList.map((col) => (
<th key={col.key} className={col.align}> <th key={col.key} className={col.align}>
{col.label} {col.label}
</th> </th>
@ -72,7 +82,7 @@ const ProjectAssignedOrgs = () => {
{Array.isArray(data) && data.length > 0 ? ( {Array.isArray(data) && data.length > 0 ? (
data.map((row, i) => ( data.map((row, i) => (
<tr key={i}> <tr key={i}>
{contactList.map((col) => ( {orgList.map((col) => (
<td key={col.key} className={col.align}> <td key={col.key} className={col.align}>
{col.getValue(row)} {col.getValue(row)}
</td> </td>
@ -82,7 +92,7 @@ const ProjectAssignedOrgs = () => {
) : ( ) : (
<tr style={{ height: "200px" }}> <tr style={{ height: "200px" }}>
<td <td
colSpan={contactList.length} colSpan={orgList.length}
className="text-center align-middle" className="text-center align-middle"
> >
Not Assigned yet Not Assigned yet

View File

@ -7,12 +7,12 @@ const ProjectOrganizations = () => {
const { onOpen, startStep, flowType } = useOrganizationModal(); const { onOpen, startStep, flowType } = useOrganizationModal();
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
return ( return (
<div className="card"> <div className="card pb-10" >
<div className="card-header"> <div className="card-header">
<div className="d-flex justify-content-end px-2"> <div className="d-flex justify-content-end px-2">
<button <button
type="button" type="button"
className="link-button btn btn-xs rounded-md link-button-sm m-1 btn-primary" className="link-button btn btn-sm rounded-md link-button-sm m-1 btn-primary"
onClick={() => onOpen({ startStep: 1, flowType: "assign" })} onClick={() => onOpen({ startStep: 1, flowType: "assign" })}
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>

View File

@ -29,7 +29,7 @@ const Modal = ({
> >
<div className="modal-content text-white shadow-lg"> <div className="modal-content text-white shadow-lg">
{/* Header */} {/* Header */}
<div className="modal-header pb-2 border-0"> <div className="modal-header justify-content-center pb-2 border-0">
<h5 className="modal-title">{title}</h5> <h5 className="modal-title">{title}</h5>
<button <button
type="button" type="button"

View File

@ -12,6 +12,7 @@ const SelectMultiple = ({
valueKey = "id", valueKey = "id",
placeholder = "Please select...", placeholder = "Please select...",
IsLoading = false, IsLoading = false,
required = false
}) => { }) => {
const { setValue, watch } = useFormContext(); const { setValue, watch } = useFormContext();
const selectedValues = watch(name) || []; const selectedValues = watch(name) || [];
@ -146,7 +147,8 @@ const SelectMultiple = ({
className="multi-select-dropdown-container" className="multi-select-dropdown-container"
style={{ position: "relative" }} style={{ position: "relative" }}
> >
<label>{label}</label> <Label required={required}>{label}</Label>
<div <div
className="multi-select-dropdown-header" className="multi-select-dropdown-header"

View File

@ -24,7 +24,7 @@ const schema = z.object({
.optional(), .optional(),
}); });
const CreateActivity = ({ onClose }) => { const CreateActivity = ({ activity = null, whichGroup = null, close }) => {
const maxDescriptionLength = 255; const maxDescriptionLength = 255;
const { mutate: createActivity, isPending: isLoading } = useCreateActivity(() => onClose?.()); const { mutate: createActivity, isPending: isLoading } = useCreateActivity(() => onClose?.());
@ -86,25 +86,19 @@ const CreateActivity = ({ onClose }) => {
const onSubmit = (formData) => { const onSubmit = (formData) => {
createActivity(formData); createActivity(formData);
}; };
// const onSubmit = (data) => {
// setIsLoading(true);
// MasterRespository.createActivity(data) useEffect(()=>{
// .then( ( resp ) => if (activity) {
// { reset({
activityName: activity.activityName || '',
// const cachedData = getCachedData("Activity"); unitOfMeasurement: activity.unitOfMeasurement || '',
// const updatedData = [ ...cachedData, resp?.data ]; checkList: activity.checkList?.map((check) => ({
// cacheData("Activity", updatedData); description: check.description || '',
// showToast("Activity Successfully Added.", "success"); isMandatory: check.isMandatory || false,
// setIsLoading(false); })) || [{ description: '', isMandatory: false }],
// handleClose() });
// }) }
// .catch((error) => { },[activity,reset])
// showToast(error.message, "error");
// setIsLoading(false);
// });
// };
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
reset(); reset();
onClose(); onClose();

View File

@ -24,7 +24,7 @@ const schema = z.object({
}); });
const UpdateActivity = ({ activityData, onClose }) => { const UpdateActivity = ({ activity = null, whichService = null, close }) => {
const { mutate: updateActivity, isPending: isLoading } = useUpdateActivity(() => onClose?.()); const { mutate: updateActivity, isPending: isLoading } = useUpdateActivity(() => onClose?.());
const { const {
@ -40,10 +40,10 @@ const UpdateActivity = ({ activityData, onClose }) => {
} = useForm({ } = useForm({
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { defaultValues: {
id: activityData?.id, id: activity?.id,
activityName: activityData?.activityName, activityName: activity?.activityName,
unitOfMeasurement: activityData?.unitOfMeasurement, unitOfMeasurement: activity?.unitOfMeasurement,
checkList: activityData?.checkLists || [], checkList: activity?.checkLists || [],
}, },
}); });
@ -53,15 +53,15 @@ const UpdateActivity = ({ activityData, onClose }) => {
}); });
useEffect(() => { useEffect(() => {
if (activityData) { if (activity) {
reset({ reset({
id: activityData.id, id: activity.id,
activityName: activityData.activityName, activityName: activity.activityName,
unitOfMeasurement: activityData.unitOfMeasurement, unitOfMeasurement: activity.unitOfMeasurement,
checkList: activityData.checkLists || [], checkList: activity.checkLists || [],
}); });
} }
}, [activityData, reset]); }, [activity, reset]);
const addChecklistItem = () => { const addChecklistItem = () => {
const values = getValues("checkList"); const values = getValues("checkList");
@ -91,36 +91,10 @@ const UpdateActivity = ({ activityData, onClose }) => {
}; };
const onSubmit = (formData) => { const onSubmit = (formData) => {
const payload = { ...formData, id: activityData.id }; const payload = { ...formData, id: activity.id };
updateActivity({ id: activityData.id, payload }); updateActivity({ id: activity.id, payload });
}; };
// const onSubmit = async(data) => {
// setIsLoading(true);
// const Activity = {...data, id:activityData.id}
// try
// {
// const response = await MasterRespository.updateActivity( activityData?.id, Activity );
// const updatedActivity = response.data;
// const cachedData = getCachedData("Activity")
// if (cachedData) {
// const updatedActivities = cachedData.map((activity) =>
// activity.id === updatedActivity.id ? { ...activity, ...updatedActivity } : activity
// );
// cacheData( "Activity", updatedActivities );
// onClose()
// }
// setIsLoading( false )
// showToast("Activity Successfully Updated", "success");
// } catch ( err )
// {
// setIsLoading( false )
// showToast("error.message", "error");
// }
// };
useEffect(() => { useEffect(() => {
const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]')); const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
@ -129,7 +103,6 @@ const UpdateActivity = ({ activityData, onClose }) => {
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{/* <h6>Update Activity</h6> */}
<div className="row"> <div className="row">
{/* Activity Name */} {/* Activity Name */}
<div className="col-md-6 text-start"> <div className="col-md-6 text-start">

View File

@ -17,6 +17,7 @@ import ManageExpenseStatus from "./ManageExpenseStatus";
import ManageDocumentCategory from "./ManageDocumentCategory"; import ManageDocumentCategory from "./ManageDocumentCategory";
import ManageDocumentType from "./ManageDocumentType"; import ManageDocumentType from "./ManageDocumentType";
import ManageServices from "./Services/ManageServices"; import ManageServices from "./Services/ManageServices";
import ServiceGroups from "./Services/ServicesGroups";
const MasterModal = ({ modaldata, closeModal }) => { const MasterModal = ({ modaldata, closeModal }) => {
if (!modaldata?.modalType || modaldata.modalType === "delete") { if (!modaldata?.modalType || modaldata.modalType === "delete") {
@ -67,6 +68,9 @@ const MasterModal = ({ modaldata, closeModal }) => {
"Edit-Services": ( "Edit-Services": (
<ManageServices data={item} onClose={closeModal} /> <ManageServices data={item} onClose={closeModal} />
), ),
"Manage-Services": (
<ServiceGroups service={item} onClose={closeModal}/>
),
}; };
return modalComponents[modalType] || null; return modalComponents[modalType] || null;

View File

@ -0,0 +1,269 @@
import React, { useState, useEffect, useCallback } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import {
useCreateActivity,
useUpdateActivity,
} from "../../../hooks/masterHook/useMaster";
import Label from "../../common/Label";
const schema = z.object({
activityName: z.string().min(1, { message: "Activity Name is required" }),
unitOfMeasurement: z
.string()
.min(1, { message: "Unit of Measurement is required" }),
checkList: z
.array(
z.object({
description: z
.string()
.min(1, { message: "descriptionlist item cannot be empty" }),
isMandatory: z.boolean().default(false),
id: z.any().default(null),
})
)
.optional(),
});
const ManageActivity = ({ activity = null, whichGroup = null, close }) => {
const maxDescriptionLength = 255;
const { mutate: createActivity, isPending: isLoading } = useCreateActivity(
() => close?.()
);
const { mutate: UpdateActivity, isPending: isUpdating } = useUpdateActivity(
() => close?.()
);
const {
register,
handleSubmit,
control,
setValue,
clearErrors,
setError,
getValues,
reset,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
activityName: "",
unitOfMeasurement: "",
checkList: [],
},
});
const {
fields: checkListItems,
append,
remove,
} = useFieldArray({
control,
name: "checkList",
});
const addChecklistItem = useCallback(() => {
const values = getValues("checkList");
const lastIndex = checkListItems.length - 1;
if (
checkListItems.length > 0 &&
(!values?.[lastIndex] || values[lastIndex].description.trim() === "")
) {
setError(`checkList.${lastIndex}.description`, {
type: "manual",
message: "Please fill this checklist item before adding another.",
});
return;
}
clearErrors(`checkList.${lastIndex}.description`);
append({ id: null, description: "", isMandatory: false });
}, [checkListItems, getValues, append, setError, clearErrors]);
const removeChecklistItem = useCallback(
(index) => {
remove(index);
},
[remove]
);
const handleChecklistChange = useCallback(
(index, value) => {
setValue(`checkList.${index}`, value);
},
[setValue]
);
const onSubmit = (formData) => {
let payload = {
...formData,
activityGroupId: whichGroup,
};
if (activity) {
UpdateActivity({ id: activity.id, payload: payload });
} else {
createActivity(payload);
}
};
useEffect(() => {
if (activity) {
reset({
activityName: activity.activityName || "",
unitOfMeasurement: activity.unitOfMeasurement || "",
checkList: activity.checkLists?.map((check) => ({
id: check.id || null, // Use the ID provided in the checklist
description: check.description || "",
isMandatory: check.isMandatory || false,
})) || [{ description: "", isMandatory: false }], // Default to an empty checklist item
});
}
}, [activity, reset]);
const handleClose = useCallback(() => {
reset();
close();
}, [reset, close]);
useEffect(() => {
const tooltipTriggerList = Array.from(
document.querySelectorAll('[data-bs-toggle="tooltip"]')
);
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []);
let isPending = isLoading || isUpdating;
return (
<form onSubmit={handleSubmit(onSubmit)} className="px-7">
{/* <h6>Create Activity</h6> */}
<div className="row">
<div className="col-6 text-start">
<Label className="form-label" required>
Activity
</Label>
<input
type="text"
{...register("activityName")}
className={`form-control form-control-sm ${
errors.activityName ? "is-invalid" : ""
}`}
/>
{errors.activityName && (
<p className="danger-text">{errors.activityName.message}</p>
)}
</div>
<div className="col-6 text-start">
<Label className="form-label" required>
Measurement
</Label>
<input
type="text"
{...register("unitOfMeasurement")}
className={`form-control form-control-sm ${
errors.unitOfMeasurement ? "is-invalid" : ""
}`}
/>
{errors.unitOfMeasurement && (
<p className="danger-text">{errors.unitOfMeasurement.message}</p>
)}
</div>
<div className="col-md-12 text-start mt-1">
<label className="py-1 form-label my-0">
Add Check List <i
className="bx bx-plus-circle text-primary cursor-pointer"
data-bs-toggle="tooltip"
title="Add Check"
data-bs-original-title="Add check"
onClick={addChecklistItem}
></i>
</label>
{checkListItems.length > 0 ? (
<table className="table mt-1 border-none">
<thead className="py-0 my-0 border-none ">
<tr className="py-1 border-secondary ">
<th colSpan={2} className="py-1">
<small>Name</small>
</th>
<th colSpan={2} className="py-1 text-center">
<small>Is Mandatory</small>
</th>
<th className="text-center py-1">Action</th>
</tr>
</thead>
<tbody className="table-border-bottom-0 border-secondary ">
{checkListItems.map((item, index) => (
<tr key={index}>
<td colSpan={2} className="border-none" >
<input
className="d-none"
{...register(`checkList.${index}.id`)}
></input>
<input
{...register(`checkList.${index}.description`)}
className="form-control form-control-sm"
placeholder={`Checklist item ${index + 1}`}
onChange={(e) =>
handleChecklistChange(index, e.target.value)
}
/>
{errors.checkList?.[index]?.description && (
<small
style={{ fontSize: "10px" }}
className="danger-text"
>
{errors.checkList[index]?.description?.message}
</small>
)}
</td>
<td colSpan={2} className="text-center border-none">
<input
className="form-check-input"
type="checkbox"
{...register(`checkList.${index}.isMandatory`)}
defaultChecked={item.isMandatory}
/>
</td>
<td className="text-center border-none">
<button
type="button"
onClick={() => removeChecklistItem(index)}
className="btn btn-xs btn-icon btn-text-secondary"
>
<i
className="bx bxs-minus-circle text-danger"
data-bs-toggle="tooltip"
title="Remove Check"
data-bs-original-title="Remove check"
></i>
</button>
</td>
</tr>
))}
</tbody>
</table>
):(<div className="d-flex justify-content-center my-0"><p className="text-secondary m-0">Not Yet Added</p> </div>)}
</div>
<div className="col-12 text-end mt-3">
<button
type="reset"
className="btn btn-sm btn-label-secondary me-3"
onClick={handleClose}
disabled={isPending}
>
Cancel
</button>
<button type="submit" className="btn btn-sm btn-primary">
{isPending ? "Please Wait" : activity ? "Update" : "Submit"}
</button>
</div>
</div>
</form>
);
};
export default ManageActivity;

View File

@ -0,0 +1,110 @@
import { useForm } from "react-hook-form";
import {
useCreateActivityGroup,
useUpdateActivityGroup,
} from "../../../hooks/masterHook/useMaster";
import { zodResolver } from "@hookform/resolvers/zod";
import { ActivityGroupSchema } from "./ServicesSchema";
import Label from "../../common/Label";
import { useEffect } from "react";
const ManageGroup = ({ group = null, whichService = null, close }) => {
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm({
resolver: zodResolver(ActivityGroupSchema),
defaultValues: { name: "", description: "" },
});
const { mutate: createGroup, isPending: isCreating } = useCreateActivityGroup(
() => close()
);
const { mutate: UpdateGroup, isPending: isUpdating } = useUpdateActivityGroup(
() => close()
);
useEffect(() => {
if (group) {
reset({
name: group.name || " ",
description: group.description || "",
});
}
}, [group, reset]);
const onSubmit = (formdata) => {
if (group) {
let payload = {
...formdata,
serviceId: whichService,
id: group.id,
};
UpdateGroup({ id: group.id, payload: payload });
} else {
let payload = {
...formdata,
serviceId: whichService,
};
createGroup(payload);
}
};
let isPending = isCreating || isUpdating;
return (
<form className="row px-12" onSubmit={handleSubmit(onSubmit)} cl>
<div className="col-12 col-md-12 text-start">
<Label className="form-label" required>
Group Name
</Label>
<input
type="text"
{...register("name")}
className={`form-control form-control-sm ${
errors.name ? "is-invalids" : ""
}`}
/>
{errors.name && (
<p className="danger-text m-0">{errors.name.message}</p>
)}
</div>
<div className="col-12 col-md-12 text-start mb-2">
<Label className="form-label" htmlFor="description" required>
Description
</Label>
<textarea
rows="3"
{...register("description")}
className={`form-control form-control-sm ${
errors.description ? "is-invalids" : ""
}`}
></textarea>
{errors.description && (
<p className="danger-text m-0">{errors.description.message}</p>
)}
</div>
<div className="col-12 text-end">
<button
className="btn btn-xs btn-label-secondary me-3"
aria-label="Close"
disabled={isPending}
onClick={close}
>
Cancel
</button>
<button
type="submit"
className="btn btn-xs btn-primary"
disabled={isPending}
>
{isPending ? "Please Wait..." : group ? "Update" : "Submit"}
</button>
</div>
</form>
);
};
export default ManageGroup;

View File

@ -0,0 +1,263 @@
import { useState } from "react";
import {
useActivitiesByGroups,
useGroups,
} from "../../../hooks/masterHook/useMaster";
import ManageGroup from "./ManageGroup";
import ManageActivity from "./ManageActivity";
import { useMasterContext } from "../../../pages/master/MasterPage";
const ServiceGroups = ({ service }) => {
const [openService, setOpenService] = useState(true);
const [activeGroupId, setActiveGroupId] = useState(null); // track selected group
const {setDeleletingServiceItem} =useMasterContext()
const [isManageGroup, setManageGroup] = useState({
isOpen: false,
group: null,
serviceId: null,
});
const [isManageActivity, setManageActivity] = useState({
isOpen: false,
activity: null, // activity is either a single activity for editing or null for creating new activity
groupId: null, // groupId for managing activities in specific groups
});
// Fetch groups and activities data
const { data: groups, isLoading } = useGroups(service?.id); // Fetch groups for the service
const { data: activities, isLoading: actLoading } =
useActivitiesByGroups(activeGroupId); // Fetch activities based on activeGroupId
if (isLoading) return <div>Loading groups...</div>; // Show loading state while groups are being fetched
// Handle toggling of group to view activities
const toggleGroup = (groupId) => {
setActiveGroupId((prev) => (prev === groupId ? null : groupId)); // If the same group is clicked again, close it
};
return (
<div className="w-100 my-2">
<p className="fs-5 fw-semibold">Manage Service</p>
<div className="accordion" id="accordionExample">
<div className="accordion-item active shadow-none">
{/* Service Header */}
<div className="d-flex justify-content-between text-start align-items-center accordion-header py-1">
<p className="m-0 fw-bold fs-6">{service.name}</p>
<button
className="btn btn-sm btn-primary"
onClick={() =>
setManageGroup({
isOpen: true,
group: null, // No group selected, for creating a new group
serviceId: service.id,
})
}
>
<i className="bx bx-plus-circle me-2"></i>Add Group
</button>
</div>
{/* Groups Section */}
<div
id="accordionOne"
className={`accordion-collapse collapse ${
openService ? "show" : ""
}`}
aria-labelledby="headingOne"
data-bs-parent="#accordionExample"
>
{/* Show ManageGroup for creating a new group */}
{isManageGroup.isOpen && isManageGroup.group === null ? (
<ManageGroup
group={null} // Indicating new group creation
whichService={isManageGroup.serviceId}
close={() =>
setManageGroup({
isOpen: false,
group: null,
serviceId: service.id,
})
}
/>
) : (
<div className="accordion-body text-start m-0 p-0">
<div className="dropdown-divider border"></div>
{groups?.data?.map((group) => {
const isOpen = activeGroupId === group.id;
return (
<div
className="accordion-item shadow-none m-0 py-2 px-2"
key={group.id}
>
<div className="d-flex justify-content-between text-start accordion-header">
{/* Show group toggle button only if ManageGroup is not open */}
<div className="d-flex gap-1 align-items-center">
{!isManageGroup.isOpen && (
<span
onClick={() => toggleGroup(group.id)}
className="text-end cursor-pointer"
data-bs-toggle="collapse"
data-bs-target={`#accordionGroup${group.id}`}
aria-expanded={isOpen}
aria-controls={`accordionGroup${group.id}`}
>
<i
className={`bx bx-lg ${
isOpen ? "bx-chevron-up" : "bx-chevron-down"
}`}
/>
</span>
)}
<p className="m-0 fw-bold ">{group.name}</p>
</div>
<div className="d-flex flex-row gap-3">
<div className="d-flex flex-row gap-2">
{/* Create New Activity */}
<i
className="bx bx-plus-circle text-primary cursor-pointer"
onClick={() => {
setManageActivity({
isOpen: true,
activity: null, // Indicating new activity creation
groupId: group.id, // Set the groupId for the new activity
});
}}
/>
{/* Edit Group */}
<i
className="bx bx-edit text-secondary cursor-pointer"
onClick={() =>
setManageGroup({
isOpen: true,
group: group, // Group selected for Editing
serviceId: service.id,
})
}
/>
{/* Delete Group */}
<i className="bx bx-trash text-danger cursor-pointer" onClick={()=>setDeleletingServiceItem({isOpen:true,ItemId:group.id,whichItem:"group"})} />
</div>
</div>
</div>
{/* Only show ManageGroup for the specific group if it's open */}
{isManageGroup.isOpen &&
isManageGroup.group?.id === group.id ? (
<ManageGroup
group={group} // For editing
whichService={isManageGroup.serviceId}
close={() =>
setManageGroup({
isOpen: false,
group: null,
serviceId: null,
})
}
/>
) : isManageActivity.isOpen &&
isManageActivity.groupId === group.id ? (
<ManageActivity
activity={isManageActivity.activity} // Pass the activity object for editing
whichGroup={group.id} // Set groupId for creating/editing activity
close={() => {
setManageActivity({
isOpen: false,
activity: null,
groupId: null,
});
}}
/>
) : (
<div
id={`accordionGroup${group.id}`}
className={`accordion-collapse collapse ${
isOpen ? "show" : ""
}`}
aria-labelledby={group.id}
data-bs-parent="#accordionOne"
>
<div className="accordion-body ">
{isOpen && actLoading && (
<p className="text-center m-0">
Loading activities...
</p>
)}
{isOpen && activities?.data?.length > 0 ? (
<div className="row border-top-2">
{/* Header Row */}
<div className="col-12 d-flex justify-content-between py-2 border-bottom px-4">
<span className="fw-semibold text-uppercase">
Activity Name
</span>
<span className="fw-semibold text-uppercase">
Unit of Measurement
</span>
<span className="fw-semibold text-uppercase">
Action
</span>
</div>
{/* Map through activities */}
{activities.data.map((activity) => (
<div
className="col-12 d-flex justify-content-between py-2 "
key={activity.id}
>
{/* Activity Name Column */}
<div className="col d-flex justify-content-start">
<span>{activity.activityName}</span>
</div>
{/* Unit of Measurement Column */}
<div className="col d-flex justify-content-start">
<span>{activity.unitOfMeasurement}</span>
</div>
{/* Action Column */}
<div className="col-auto d-flex gap-3 justify-content-end">
{/* Edit Activity */}
<i
className="bx bx-sm bx-edit text-secondary cursor-pointer"
onClick={() => {
setManageActivity({
isOpen: true,
activity: activity, // Pass the specific activity for editing
groupId: group.id, // Set groupId for the specific activity
});
}}
/>
{/* Delete Activity */}
<i
className="bx bx-sm bx-trash text-danger cursor-pointer"
onClick={() => setDeleletingServiceItem({isOpen:true,ItemId:activity.id,whichItem:"activity"})}
/>
</div>
</div>
))}
</div>
) : (
isOpen && (
<p className="text-center m-0">
No activities found
</p>
)
)}
</div>
</div>
)}
</div>
);
})}
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default ServiceGroups;

View File

@ -7,3 +7,11 @@ const schema = z.object({
.min(1, { message: "Description is required" }) .min(1, { message: "Description is required" })
.max(255, { message: "Description cannot exceed 255 characters" }), .max(255, { message: "Description cannot exceed 255 characters" }),
}); });
export const ActivityGroupSchema = z.object({
name: z.string().min(1, { message: "Group Name is required" }),
description: z
.string()
.min(1, { message: "Description is required" })
.max(255, { message: "Description cannot exceed 255 characters" }),
});

File diff suppressed because it is too large Load Diff

View File

@ -174,7 +174,6 @@ const AttendancePage = () => {
{/* 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 d-flex gap-2 align-items-center">
{/* Organization Dropdown */} {/* Organization Dropdown */}
{organizations?.length > 1 && (
<select <select
className="form-select form-select-sm" className="form-select form-select-sm"
style={{ minWidth: "180px" }} style={{ minWidth: "180px" }}
@ -194,7 +193,6 @@ const AttendancePage = () => {
</option> </option>
))} ))}
</select> </select>
)}
{/* Search Input */} {/* Search Input */}
<input <input
@ -207,6 +205,7 @@ const AttendancePage = () => {
/> />
</div> </div>
</div> </div>
</div> </div>

View File

@ -21,11 +21,11 @@ const TenantSelectionPage = () => {
const {mutate:handleLogout,isPending:isLogouting} = useLogout(()=>{}) const {mutate:handleLogout,isPending:isLogouting} = useLogout(()=>{})
// useEffect(() => { useEffect(() => {
// if (localStorage.getItem("ctnt")) { if (localStorage.getItem("ctnt")) {
// navigate("/dashboard"); navigate("/dashboard");
// } }
// }, [navigate]); }, [navigate]);
useEffect(() => { useEffect(() => {
if (!isLoading && data?.data?.length === 1) { if (!isLoading && data?.data?.length === 1) {

View File

@ -1,4 +1,4 @@
import React, { useState, useMemo, useEffect } from "react"; import React, { useState, useMemo, useEffect, createContext, useContext } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
@ -6,7 +6,9 @@ import MasterModal from "../../components/master/MasterModal";
import ConfirmModal from "../../components/common/ConfirmModal"; import ConfirmModal from "../../components/common/ConfirmModal";
import MasterTable from "./MasterTable"; import MasterTable from "./MasterTable";
import useMaster, { import useMaster, {
useDeleteActivity,
useDeleteMasterItem, useDeleteMasterItem,
useDeleteServiceGroup,
useMasterMenu, useMasterMenu,
} from "../../hooks/masterHook/useMaster"; } from "../../hooks/masterHook/useMaster";
import { changeMaster } from "../../slices/localVariablesSlice"; import { changeMaster } from "../../slices/localVariablesSlice";
@ -14,6 +16,16 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_MASTER } from "../../utils/constants"; import { MANAGE_MASTER } from "../../utils/constants";
import GlobalModel from "../../components/common/GlobalModel"; import GlobalModel from "../../components/common/GlobalModel";
export const MasterContext = createContext();
export const useMasterContext = () => {
const context = useContext(MasterContext);
if (!context) {
throw new Error("useMasterContext must be used within an MasterProvider");
}
return context;
};
const MasterPage = () => { const MasterPage = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -34,6 +46,9 @@ const MasterPage = () => {
isError: isMasterError, isError: isMasterError,
} = useMaster(); } = useMaster();
const { mutate: DeleteMaster, isPending: isDeleting } = useDeleteMasterItem(); const { mutate: DeleteMaster, isPending: isDeleting } = useDeleteMasterItem();
const [isDeleletingServiceItem,setDeleletingServiceItem] = useState({isOpen:false,ItemId:null,whichItem:null})
const {mutate:DeleteSericeGroup,isPending:deletingGroup} =useDeleteServiceGroup()
const {mutate:DeleteAcivity,isPending:deletingActivity} = useDeleteActivity()
const [modalConfig, setModalConfig] = useState(null); const [modalConfig, setModalConfig] = useState(null);
const [deleteData, setDeleteData] = useState(null); const [deleteData, setDeleteData] = useState(null);
@ -73,6 +88,19 @@ const MasterPage = () => {
); );
}; };
const handleDeleteServiceItem =()=>{
if(!isDeleletingServiceItem.ItemId) return
debugger
if(isDeleletingServiceItem.whichItem === "activity"){
DeleteAcivity(isDeleletingServiceItem.ItemId,{onSuccess:()=>setDeleletingServiceItem({isOpen:false,ItemId:null,whichItem:null})})
}else{
DeleteSericeGroup(isDeleletingServiceItem.ItemId,{onSuccess:()=>setDeleletingServiceItem({isOpen:false,ItemId:null,whichItem:null})})
}
}
if (menuErrorFlag || isMasterError) if (menuErrorFlag || isMasterError)
return ( return (
<div className="d-flex flex-column align-items-center justify-content-center py-5"> <div className="d-flex flex-column align-items-center justify-content-center py-5">
@ -87,7 +115,7 @@ const MasterPage = () => {
); );
return ( return (
<> <MasterContext.Provider value={{setDeleletingServiceItem}}>
{modalConfig && ( {modalConfig && (
<GlobalModel <GlobalModel
size={ size={
@ -107,6 +135,7 @@ const MasterPage = () => {
</GlobalModel> </GlobalModel>
)} )}
<ConfirmModal <ConfirmModal
type="delete" type="delete"
header={`Delete ${selectedMaster}`} header={`Delete ${selectedMaster}`}
@ -117,6 +146,16 @@ const MasterPage = () => {
onClose={() => setDeleteData(null)} onClose={() => setDeleteData(null)}
/> />
<ConfirmModal
type="delete"
header={`Delete ${isDeleletingServiceItem?.whichItem}`}
message={`Are you sure you want to delete this ${isDeleletingServiceItem?.whichItem}?`}
isOpen={!!isDeleletingServiceItem?.isOpen}
loading={deletingActivity}
onSubmit={handleDeleteServiceItem}
onClose={() => setDeleletingServiceItem({isOpen:false,ItemId:null,whichItem:null})}
/>
<div className="container-fluid"> <div className="container-fluid">
<Breadcrumb <Breadcrumb
data={[{ label: "Home", link: "/dashboard" }, { label: "Masters" }]} data={[{ label: "Home", link: "/dashboard" }, { label: "Masters" }]}
@ -185,7 +224,7 @@ const MasterPage = () => {
</div> </div>
</div> </div>
</div> </div>
</> </MasterContext.Provider>
); );
}; };

View File

@ -158,6 +158,21 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
</> </>
) : ( ) : (
<> <>
{selectedMaster === "Services" && (
<button
aria-label="View"
type="button"
className="btn p-0 dropdown-toggle hide-arrow"
onClick={() =>
handleModalData(`Manage-${selectedMaster}`, item, selectedMaster)
}
data-bs-toggle="modal"
data-bs-target="#servicesActivityTreeModal"
>
<i className="bx bx-show me-2 text-primary"></i>
</button>
)}
<button <button
aria-label="Modify" aria-label="Modify"
type="button" type="button"

View File

@ -19,7 +19,7 @@ import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { getCachedData, cacheData } from "../../slices/apiDataManager"; import { getCachedData, cacheData } from "../../slices/apiDataManager";
import GlobalModel from "../../components/common/GlobalModel"; import GlobalModel from "../../components/common/GlobalModel";
import {formatNumber} from "../../utils/dateUtils"; import { formatNumber } from "../../utils/dateUtils";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
@ -27,7 +27,7 @@ const ProjectListView = ({ projectData, recall }) => {
const [projectInfo, setProjectInfo] = useState(projectData); const [projectInfo, setProjectInfo] = useState(projectData);
const dispatch = useDispatch() const dispatch = useDispatch()
const { projects_Details, loading, error, refetch } = useProjectDetails( const { projects_Details, loading, error, refetch } = useProjectDetails(
projectInfo?.id,false projectInfo?.id, false
); );
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
@ -40,11 +40,11 @@ const ProjectListView = ({ projectData, recall }) => {
isPending, isPending,
isSuccess, isSuccess,
isError, isError,
} = useUpdateProject({ } = useUpdateProject({
onSuccessCallback: () => { onSuccessCallback: () => {
setShowModal(false); setShowModal(false);
}, },
}) })
const handleShow = async () => { const handleShow = async () => {
try { try {
@ -65,9 +65,15 @@ const ProjectListView = ({ projectData, recall }) => {
const handleClose = () => setShowModal(false); const handleClose = () => setShowModal(false);
const handleViewProject = () => { const handleViewProject = () => {
dispatch(setProjectId(projectInfo.id))
navigate(`/projects/details`); navigate(`/projects/details`);
}; };
const handleViewActivities = () => {
dispatch(setProjectId(projectInfo.id))
navigate(`/activities/records?project=${projectInfo.id}`);
};
const handleFormSubmit = (updatedProject) => { const handleFormSubmit = (updatedProject) => {
if (projectInfo?.id) { if (projectInfo?.id) {
updateProject({ updateProject({
@ -168,7 +174,7 @@ const ProjectListView = ({ projectData, recall }) => {
<a <a
aria-label="click to View details" aria-label="click to View details"
className="dropdown-item" className="dropdown-item"
onClick={() => navigate(`/projects/details`)} onClick={handleViewProject}
> >
<i className="bx bx-detail me-2"></i> <i className="bx bx-detail me-2"></i>
<span className="align-left">View details</span> <span className="align-left">View details</span>
@ -182,9 +188,7 @@ const ProjectListView = ({ projectData, recall }) => {
</a> </a>
</li> </li>
<li <li
onClick={() => onClick={handleViewActivities}
navigate(`/activities/records?project=${projectInfo.id}`)
}
> >
<a className="dropdown-item"> <a className="dropdown-item">
<i className="bx bx-task me-2"></i> <i className="bx bx-task me-2"></i>

View File

@ -32,11 +32,11 @@ export const MasterRespository = {
getActivites: () => api.get("api/master/activities"), getActivites: () => api.get("api/master/activities"),
createActivity: (data) => api.post("api/master/activity", data), createActivity: (data) => api.post("api/master/activity", data),
//Services //Services
getService: () => api.get("api/master/service/list"), getService: () => api.get("api/master/service/list"),
createService: (data) => api.post("api/master/service/create", data), createService: (data) => api.post("api/master/service/create", data),
updateService: (id, data) => api.put(`api/master/service/edit/${id}`, data), updateService: (id, data) => api.put(`api/master/service/edit/${id}`, data),
"Services": (id) => api.delete(`/api/master/service/delete/${id}`), Services: (id) => api.delete(`/api/master/service/delete/${id}`),
updateActivity: (id, data) => updateActivity: (id, data) =>
api.post(`api/master/activity/edit/${id}`, data), api.post(`api/master/activity/edit/${id}`, data),
@ -114,10 +114,20 @@ export const MasterRespository = {
updateDocumentType: (id, data) => updateDocumentType: (id, data) =>
api.put(`/api/Master/document-type/edit/${id}`, data), api.put(`/api/Master/document-type/edit/${id}`, data),
getGlobalServices: () => api.get("/api/Master/global-service/list"),
getMasterServices: () => api.get("/api/Master/service/list"),
getActivityGrops: (serviceId) =>
api.get(`/api/Master/activity-group/list?serviceId=${serviceId}`),
createActivityGroup: (data) =>
api.post(`/api/Master/activity-group/create`, data),
updateActivityGrop: (serviceId, data) =>
api.put(`/api/Master/activity-group/edit/${serviceId}`, data),
getActivitesByGroup: (activityGroupId) =>
api.get(`api/master/activities?activityGroupId=${activityGroupId}`),
deleteActivityGroup:(id)=>api.delete(`/api/Master/activity-group/delete/${id}`),
getGlobalServices:()=>api.get("/api/Master/global-service/list"), deleteActivity:(id)=>api.delete(`/api/Master/activity/delete/${id}`),
getMasterServices:()=>api.get("/api/Master/service/list"),
getOrganizationType:()=>api.get('/api/Master/organization-type/list') getOrganizationType: () => api.get("/api/Master/organization-type/list"),
}; };