Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Kartik_Task_InfraMask#1266
This commit is contained in:
commit
3e7b2c1a15
@ -1,194 +1,194 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import LineChart from "../Charts/LineChart";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data";
|
||||
import ApexChart from "../Charts/Circlechart";
|
||||
// import React, { useState, useEffect } from "react";
|
||||
// import LineChart from "../Charts/LineChart";
|
||||
// import { useProjects } from "../../hooks/useProjects";
|
||||
// import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data";
|
||||
// import ApexChart from "../Charts/Circlechart";
|
||||
|
||||
const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId";
|
||||
// const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId";
|
||||
|
||||
const Activity = () => {
|
||||
const { projects } = useProjects();
|
||||
const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
|
||||
const [selectedDate, setSelectedDate] = useState(today);
|
||||
const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
|
||||
const initialProjectId = storedProjectId || "all";
|
||||
const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
|
||||
const [displayedProjectName, setDisplayedProjectName] = useState("Select Project");
|
||||
const [activeTab, setActiveTab] = useState("all");
|
||||
// const Activity = () => {
|
||||
// const { projects } = useProjects();
|
||||
// const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
|
||||
// const [selectedDate, setSelectedDate] = useState(today);
|
||||
// const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
|
||||
// const initialProjectId = storedProjectId || "all";
|
||||
// const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
|
||||
// const [displayedProjectName, setDisplayedProjectName] = useState("Select Project");
|
||||
// const [activeTab, setActiveTab] = useState("all");
|
||||
|
||||
const { dashboard_Activitydata: ActivityData, isLoading, error: isError } =
|
||||
useDashboard_ActivityData(selectedDate, selectedProjectId);
|
||||
// const { dashboard_Activitydata: ActivityData, isLoading, error: isError } =
|
||||
// useDashboard_ActivityData(selectedDate, selectedProjectId);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProjectId === "all") {
|
||||
setDisplayedProjectName("All Projects");
|
||||
} else if (projects) {
|
||||
const foundProject = projects.find((p) => p.id === selectedProjectId);
|
||||
setDisplayedProjectName(foundProject ? foundProject.name : "Select Project");
|
||||
} else {
|
||||
setDisplayedProjectName("Select Project");
|
||||
}
|
||||
// useEffect(() => {
|
||||
// if (selectedProjectId === "all") {
|
||||
// setDisplayedProjectName("All Projects");
|
||||
// } else if (projects) {
|
||||
// const foundProject = projects.find((p) => p.id === selectedProjectId);
|
||||
// setDisplayedProjectName(foundProject ? foundProject.name : "Select Project");
|
||||
// } else {
|
||||
// setDisplayedProjectName("Select Project");
|
||||
// }
|
||||
|
||||
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
|
||||
}, [selectedProjectId, projects]);
|
||||
// localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
|
||||
// }, [selectedProjectId, projects]);
|
||||
|
||||
const handleProjectSelect = (projectId) => {
|
||||
setSelectedProjectId(projectId);
|
||||
};
|
||||
// const handleProjectSelect = (projectId) => {
|
||||
// setSelectedProjectId(projectId);
|
||||
// };
|
||||
|
||||
const handleDateChange = (e) => {
|
||||
setSelectedDate(e.target.value);
|
||||
};
|
||||
// const handleDateChange = (e) => {
|
||||
// setSelectedDate(e.target.value);
|
||||
// };
|
||||
|
||||
return (
|
||||
<div className="card h-100">
|
||||
<div className="card-header">
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0">
|
||||
<div className="card-title mb-0 text-start">
|
||||
<h5 className="mb-1">Activity</h5>
|
||||
<p className="card-subtitle">Activity Progress Chart</p>
|
||||
</div>
|
||||
// return (
|
||||
// <div className="card h-100">
|
||||
// <div className="card-header">
|
||||
// <div className="d-flex flex-wrap justify-content-between align-items-center mb-0">
|
||||
// <div className="card-title mb-0 text-start">
|
||||
// <h5 className="mb-1">Activity</h5>
|
||||
// <p className="card-subtitle text-primary">Activity Progress Chart</p>
|
||||
// </div>
|
||||
|
||||
<div className="btn-group">
|
||||
<button
|
||||
className="btn btn-outline-primary btn-sm dropdown-toggle"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{displayedProjectName}
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
<li>
|
||||
<button className="dropdown-item" onClick={() => handleProjectSelect("all")}>
|
||||
All Projects
|
||||
</button>
|
||||
</li>
|
||||
{projects?.map((project) => (
|
||||
<li key={project.id}>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => handleProjectSelect(project.id)}
|
||||
>
|
||||
{project.name}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// <div className="btn-group">
|
||||
// <button
|
||||
// className="btn btn-outline-primary btn-sm dropdown-toggle"
|
||||
// type="button"
|
||||
// data-bs-toggle="dropdown"
|
||||
// aria-expanded="false"
|
||||
// >
|
||||
// {displayedProjectName}
|
||||
// </button>
|
||||
// <ul className="dropdown-menu">
|
||||
// <li>
|
||||
// <button className="dropdown-item" onClick={() => handleProjectSelect("all")}>
|
||||
// All Projects
|
||||
// </button>
|
||||
// </li>
|
||||
// {projects?.map((project) => (
|
||||
// <li key={project.id}>
|
||||
// <button
|
||||
// className="dropdown-item"
|
||||
// onClick={() => handleProjectSelect(project.id)}
|
||||
// >
|
||||
// {project.name}
|
||||
// </button>
|
||||
// </li>
|
||||
// ))}
|
||||
// </ul>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
{/* ✅ Date Picker Aligned Left with Padding */}
|
||||
<div className="d-flex justify-content-start ps-3 mb-3">
|
||||
<div style={{ width: "150px" }}>
|
||||
<input
|
||||
type="date"
|
||||
className="form-control"
|
||||
value={selectedDate}
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
// {/* ✅ Date Picker Aligned Left with Padding */}
|
||||
// <div className="d-flex justify-content-start ps-3 mb-3">
|
||||
// <div style={{ width: "150px" }}>
|
||||
// <input
|
||||
// type="date"
|
||||
// className="form-control"
|
||||
// value={selectedDate}
|
||||
// onChange={handleDateChange}
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
{/* Tabs */}
|
||||
<ul className="nav nav-tabs " role="tablist">
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${activeTab === "all" ? "active" : ""}`}
|
||||
onClick={() => setActiveTab("all")}
|
||||
data-bs-toggle="tab"
|
||||
>
|
||||
Summary
|
||||
</button>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${activeTab === "logs" ? "active" : ""}`}
|
||||
onClick={() => setActiveTab("logs")}
|
||||
data-bs-toggle="tab"
|
||||
>
|
||||
Details
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
// {/* Tabs */}
|
||||
// <ul className="nav nav-tabs " role="tablist">
|
||||
// <li className="nav-item">
|
||||
// <button
|
||||
// type="button"
|
||||
// className={`nav-link ${activeTab === "all" ? "active" : ""}`}
|
||||
// onClick={() => setActiveTab("all")}
|
||||
// data-bs-toggle="tab"
|
||||
// >
|
||||
// Summary
|
||||
// </button>
|
||||
// </li>
|
||||
// <li className="nav-item">
|
||||
// <button
|
||||
// type="button"
|
||||
// className={`nav-link ${activeTab === "logs" ? "active" : ""}`}
|
||||
// onClick={() => setActiveTab("logs")}
|
||||
// data-bs-toggle="tab"
|
||||
// >
|
||||
// Details
|
||||
// </button>
|
||||
// </li>
|
||||
// </ul>
|
||||
|
||||
<div className="card-body">
|
||||
{activeTab === "all" && (
|
||||
<div className="row justify-content-between">
|
||||
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||
{isLoading ? (
|
||||
<p>Loading activity data...</p>
|
||||
) : isError ? (
|
||||
<p>No data available.</p>
|
||||
) : (
|
||||
ActivityData && (
|
||||
<>
|
||||
<h5 className="fw-bold mb-0 text-start w-80">
|
||||
<i className="bx bx-task text-info"></i> Allocated Task
|
||||
</h5>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{ActivityData.totalCompletedWork?.toLocaleString()}/
|
||||
{ActivityData.totalPlannedWork?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Completed / Assigned</small>
|
||||
<div style={{ maxWidth: "180px" }}>
|
||||
<ApexChart />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
// <div className="card-body">
|
||||
// {activeTab === "all" && (
|
||||
// <div className="row justify-content-between">
|
||||
// <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||
// {isLoading ? (
|
||||
// <p>Loading activity data...</p>
|
||||
// ) : isError ? (
|
||||
// <p>No data available.</p>
|
||||
// ) : (
|
||||
// ActivityData && (
|
||||
// <>
|
||||
// <h5 className="fw-bold mb-0 text-start w-80">
|
||||
// <i className="bx bx-task text-info"></i> Allocated Task
|
||||
// </h5>
|
||||
// <h4 className="mb-0 fw-bold">
|
||||
// {ActivityData.totalCompletedWork?.toLocaleString()}/
|
||||
// {ActivityData.totalPlannedWork?.toLocaleString()}
|
||||
// </h4>
|
||||
// <small className="text-muted">Completed / Assigned</small>
|
||||
// <div style={{ maxWidth: "180px" }}>
|
||||
// <ApexChart />
|
||||
// </div>
|
||||
// </>
|
||||
// )
|
||||
// )}
|
||||
// </div>
|
||||
|
||||
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||
{!isLoading && !isError && ActivityData && (
|
||||
<>
|
||||
<h5 className="fw-bold mb-0 text-start w-110">
|
||||
<i className="bx bx-task text-info"></i> Activities
|
||||
</h5>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{ActivityData.totalCompletedWork?.toLocaleString()}/
|
||||
{ActivityData.totalPlannedWork?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted ">Pending / Assigned</small>
|
||||
<div style={{ maxWidth: "180px" }}>
|
||||
<ApexChart />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
// <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||
// {!isLoading && !isError && ActivityData && (
|
||||
// <>
|
||||
// <h5 className="fw-bold mb-0 text-start w-110">
|
||||
// <i className="bx bx-task text-info"></i> Activities
|
||||
// </h5>
|
||||
// <h4 className="mb-0 fw-bold">
|
||||
// {ActivityData.totalCompletedWork?.toLocaleString()}/
|
||||
// {ActivityData.totalPlannedWork?.toLocaleString()}
|
||||
// </h4>
|
||||
// <small className="text-muted ">Pending / Assigned</small>
|
||||
// <div style={{ maxWidth: "180px" }}>
|
||||
// <ApexChart />
|
||||
// </div>
|
||||
// </>
|
||||
// )}
|
||||
// </div>
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
{activeTab === "logs" && (
|
||||
<div className="table-responsive">
|
||||
<table className="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Activity / Location</th>
|
||||
<th>Assigned / Completed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{[{
|
||||
activity: "Code Review / Remote",
|
||||
assignedToday: 3,
|
||||
completed: 2
|
||||
}].map((log, index) => (
|
||||
<tr key={index}>
|
||||
<td>{log.activity}</td>
|
||||
<td>{log.assignedToday} / {log.completed}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// {activeTab === "logs" && (
|
||||
// <div className="table-responsive">
|
||||
// <table className="table table-bordered table-hover">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// <th>Activity / Location</th>
|
||||
// <th>Assigned / Completed</th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {[{
|
||||
// activity: "Code Review / Remote",
|
||||
// assignedToday: 3,
|
||||
// completed: 2
|
||||
// }].map((log, index) => (
|
||||
// <tr key={index}>
|
||||
// <td>{log.activity}</td>
|
||||
// <td>{log.assignedToday} / {log.completed}</td>
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
export default Activity;
|
||||
// export default Activity;
|
||||
|
@ -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 { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { defaultFilter, SearchSchema } from "./ExpenseSchema";
|
||||
@ -16,8 +16,11 @@ import { ExpenseFilterSkeleton } from "./ExpenseSkeleton";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
|
||||
const { data, isLoading,isError,error,isFetching , isFetched} = useExpenseFilter();
|
||||
const selectedProjectId = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
);
|
||||
const { data, isLoading, isError, error, isFetching, isFetched } =
|
||||
useExpenseFilter();
|
||||
|
||||
const groupByList = useMemo(() => {
|
||||
return [
|
||||
@ -27,11 +30,10 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||
{ id: "project", name: "Project" },
|
||||
{ id: "paymentMode", name: "Payment Mode" },
|
||||
{ id: "expensesType", name: "Expense Type" },
|
||||
{ id: "createdAt", name: "Submitted Date" }
|
||||
{ id: "createdAt", name: "Submitted Date" },
|
||||
].sort((a, b) => a.name.localeCompare(b.name));
|
||||
}, []);
|
||||
|
||||
|
||||
const [selectedGroup, setSelectedGroup] = useState(groupByList[0]);
|
||||
const [resetKey, setResetKey] = useState(0);
|
||||
|
||||
@ -40,7 +42,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||
defaultValues: defaultFilter,
|
||||
});
|
||||
|
||||
const { control, register, handleSubmit, reset, watch } = methods;
|
||||
const { control, handleSubmit, reset, setValue, watch } = methods;
|
||||
const isTransactionDate = watch("isTransactionDate");
|
||||
|
||||
const closePanel = () => {
|
||||
@ -78,28 +80,37 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||
}, [location]);
|
||||
|
||||
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 (
|
||||
<>
|
||||
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
|
||||
<div className="mb-3 w-100">
|
||||
<div className="d-flex align-items-center mb-2">
|
||||
<label className="form-label me-2">Choose Date:</label>
|
||||
<div className="form-check form-switch m-0">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
id="switchOption1"
|
||||
{...register("isTransactionDate")}
|
||||
/>
|
||||
<label className="form-label me-2">Filter By:</label>
|
||||
<div className="d-inline-flex border rounded-pill mb-1 overflow-hidden shadow-none">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||
isTransactionDate ? "active btn-primary text-white" : ""
|
||||
}`}
|
||||
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>
|
||||
<label className="form-label mb-0 ms-2">
|
||||
{isTransactionDate ? "Submitted": "Transaction" }
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="fw-semibold">Choose Date Range:</label>
|
||||
<DateRangePicker1
|
||||
placeholder="DD-MM-YYYY To DD-MM-YYYY"
|
||||
startField="startDate"
|
||||
@ -169,7 +180,9 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
id="groupBySelect"
|
||||
className="form-select form-select-sm"
|
||||
@ -198,9 +211,8 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExpenseFilterPanel;
|
||||
export default ExpenseFilterPanel;
|
||||
|
@ -394,6 +394,15 @@ const Header = () => {
|
||||
</li>
|
||||
<li>
|
||||
<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 onClick={handleProfilePage}>
|
||||
<a
|
||||
@ -424,15 +433,7 @@ const Header = () => {
|
||||
</a>
|
||||
</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>
|
||||
<div className="dropdown-divider"></div>
|
||||
</li>
|
||||
@ -440,7 +441,7 @@ const Header = () => {
|
||||
<a
|
||||
aria-label="click to log out"
|
||||
className="dropdown-item cusor-pointer"
|
||||
onClick={()=>handleLogout()}
|
||||
onClick={()=>logout()}
|
||||
>
|
||||
{logouting ? "Please Wait":<> <i className="bx bx-log-out me-2"></i>
|
||||
<span className="align-middle">SignOut</span></>}
|
||||
|
@ -171,8 +171,8 @@ const AssignOrg = ({ setStep }) => {
|
||||
{flowType !== "default" && (
|
||||
<>
|
||||
{/* Organization Type */}
|
||||
<div className="mb-1 text-start">
|
||||
<Label htmlFor="organizationTypeId" required>
|
||||
<div className="mb-3 text-start">
|
||||
<Label htmlFor="organizationTypeId" className="mb-3 fw-semibold" required>
|
||||
Organization Type
|
||||
</Label>
|
||||
<div className="d-flex flex-wrap gap-3 mt-1">
|
||||
@ -206,11 +206,11 @@ const AssignOrg = ({ setStep }) => {
|
||||
|
||||
{/* Services */}
|
||||
<div className="mb-3">
|
||||
<Label htmlFor="serviceIds" required>
|
||||
<Label htmlFor="serviceIds" className="mb-3 fw-semibold" required>
|
||||
Select Services
|
||||
</Label>
|
||||
{mergedServices?.map((service) => (
|
||||
<div key={service.id} className="form-check">
|
||||
<div key={service.id} className="form-check mb-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
value={service.id}
|
||||
@ -230,7 +230,7 @@ const AssignOrg = ({ setStep }) => {
|
||||
)}
|
||||
|
||||
{/* Buttons: Always visible */}
|
||||
<div className="d-flex justify-content-between mt-3">
|
||||
<div className="d-flex justify-content-between mt-5">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-outline-secondary"
|
||||
@ -247,8 +247,8 @@ const AssignOrg = ({ setStep }) => {
|
||||
{isPending
|
||||
? "Please wait..."
|
||||
: flowType === "default"
|
||||
? "Assign Organization"
|
||||
: "Add"}
|
||||
? "Assign Organization"
|
||||
: "Assign Project"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -146,6 +146,7 @@ const ManagOrg = () => {
|
||||
required
|
||||
valueKey="id"
|
||||
options={service?.data || []}
|
||||
required = {true}
|
||||
/>
|
||||
{errors.serviceIds && (
|
||||
<span className="danger-text">{errors.serviceIds.message}</span>
|
||||
|
@ -84,10 +84,7 @@ const OrgPickerFromSPId = ({ title, placeholder }) => {
|
||||
height={50}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex flex-column p-0 m-0 cursor-pointer"
|
||||
onClick={() => onOpen({ startStep: 3, orgData: org })}
|
||||
>
|
||||
<div className="d-flex flex-column p-0 m-0 cursor-pointer">
|
||||
<span className="fs-6 fw-semibold">{org.name}</span>
|
||||
<div className="d-flex gap-2">
|
||||
<small
|
||||
@ -102,6 +99,16 @@ const OrgPickerFromSPId = ({ title, placeholder }) => {
|
||||
<small className="text-small fw-semibold">Address:</small>
|
||||
<div className="d-flex text-wrap">{org.address}</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>
|
||||
))}
|
||||
|
@ -85,7 +85,7 @@ const OrgPickerfromTenant = ({ title }) => {
|
||||
{isLoading ? (
|
||||
<div>Loading....</div>
|
||||
) : 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">
|
||||
<thead>
|
||||
<tr className="table_header_border">
|
||||
@ -99,28 +99,41 @@ const OrgPickerfromTenant = ({ title }) => {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Array.isArray(data.data) && data.data.length > 0
|
||||
? data.data.map((row, i) => (
|
||||
<tr key={i}>
|
||||
{contactList.map((col) => (
|
||||
<td key={col.key} className={col.align}>
|
||||
{col.getValue(row)}
|
||||
</td>
|
||||
))}
|
||||
<td className="sticky-action-column bg-white">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => onOpen({ startStep: 3, orgData: row })}
|
||||
>
|
||||
Select
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div
|
||||
className="scrollable-tbody overflow-y-auto"
|
||||
style={{ maxHeight: "350px", overflowY: "auto" }}
|
||||
>
|
||||
<table className="table dataTable text-nowrap mb-0">
|
||||
<tbody>
|
||||
{Array.isArray(data.data) && data.data.length > 0
|
||||
? data.data.map((row, i) => (
|
||||
<tr key={i}>
|
||||
{contactList.map((col) => (
|
||||
<td key={col.key} className={col.align}>
|
||||
{col.getValue(row)}
|
||||
</td>
|
||||
))}
|
||||
<td className="sticky-action-column p-0 bg-white">
|
||||
<div className="p-1">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() =>
|
||||
onOpen({ startStep: 3, orgData: row })
|
||||
}
|
||||
>
|
||||
Select
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="d-flex flex-column align-items-center text-center text-wrap text-black gap-2">
|
||||
|
@ -60,7 +60,7 @@ const OrganizationModal = () => {
|
||||
if (startStep === 1) {
|
||||
return orgData && orgData !== null
|
||||
? "Add Organization"
|
||||
: "Choose Organization1";
|
||||
: "Choose Organization";
|
||||
}
|
||||
|
||||
if (startStep === 2) {
|
||||
|
@ -93,7 +93,7 @@ const OrganizationsList = ({searchText}) => {
|
||||
if (isError) return <div>{error?.message || "Something went wrong"}</div>;
|
||||
|
||||
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="dataTables_wrapper no-footer px-2">
|
||||
<table className="table border-top dataTable text-nowrap">
|
||||
|
@ -14,34 +14,34 @@ import {
|
||||
getProjectStatusName,
|
||||
} from "../../utils/projectStatus";
|
||||
import GlobalModel from "../common/GlobalModel";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
|
||||
const ProjectCard = ({ projectData, recall }) => {
|
||||
const [ projectInfo, setProjectInfo ] = useState( projectData );
|
||||
const { projects_Details, loading, error, refetch } = useProjectDetails(
|
||||
projectInfo?.id,false
|
||||
const [projectInfo, setProjectInfo] = useState(projectData);
|
||||
const { projects_Details, loading, error, refetch } = useProjectDetails(
|
||||
projectInfo?.id, false
|
||||
);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const dispatch = useDispatch()
|
||||
const dispatch = useDispatch()
|
||||
const navigate = useNavigate();
|
||||
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||
const {
|
||||
mutate: updateProject,
|
||||
isPending,
|
||||
isSuccess,
|
||||
isError,
|
||||
} = useUpdateProject({
|
||||
onSuccessCallback: () => {
|
||||
setShowModal(false);
|
||||
},
|
||||
})
|
||||
mutate: updateProject,
|
||||
isPending,
|
||||
isSuccess,
|
||||
isError,
|
||||
} = useUpdateProject({
|
||||
onSuccessCallback: () => {
|
||||
setShowModal(false);
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
setProjectInfo(projectData);
|
||||
}, [ projectData ] )
|
||||
}, [projectData])
|
||||
|
||||
const handleShow = async () => {
|
||||
const handleShow = async () => {
|
||||
try {
|
||||
const { data } = await refetch();
|
||||
setShowModal(true);
|
||||
@ -63,22 +63,26 @@ const ProjectCard = ({ projectData, recall }) => {
|
||||
dispatch(setProjectId(projectInfo.id))
|
||||
navigate(`/projects/details`);
|
||||
};
|
||||
const handleViewActivities = () => {
|
||||
dispatch(setProjectId(projectInfo.id))
|
||||
navigate(`/activities/records?project=${projectInfo.id}`);
|
||||
};
|
||||
|
||||
const handleFormSubmit = (updatedProject) => {
|
||||
if (projectInfo?.id) {
|
||||
updateProject({
|
||||
projectId: projectInfo.id,
|
||||
updatedData: updatedProject,
|
||||
});
|
||||
}
|
||||
};
|
||||
if (projectInfo?.id) {
|
||||
updateProject({
|
||||
projectId: projectInfo.id,
|
||||
updatedData: updatedProject,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
|
||||
{showModal && projects_Details && (
|
||||
<GlobalModel isOpen={showModal} closeModal={handleClose}>
|
||||
<ManageProjectInfo
|
||||
<ManageProjectInfo
|
||||
project={projects_Details}
|
||||
handleSubmitForm={handleFormSubmit}
|
||||
onClose={handleClose}
|
||||
@ -102,7 +106,7 @@ const ProjectCard = ({ projectData, recall }) => {
|
||||
<h5
|
||||
className="mb-0 stretched-link text-heading text-start"
|
||||
onClick={handleViewProject}
|
||||
>
|
||||
>
|
||||
{projectInfo.shortName
|
||||
? projectInfo.shortName
|
||||
: projectInfo.name}
|
||||
@ -120,23 +124,23 @@ const ProjectCard = ({ projectData, recall }) => {
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{loading ? (
|
||||
<div
|
||||
className="spinner-border spinner-border-sm text-secondary"
|
||||
role="status"
|
||||
>
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
<i
|
||||
className="bx bx-dots-vertical-rounded bx-sm text-muted"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-offset="0,8"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="tooltip-dark"
|
||||
title="More Action"
|
||||
></i>
|
||||
)}
|
||||
{loading ? (
|
||||
<div
|
||||
className="spinner-border spinner-border-sm text-secondary"
|
||||
role="status"
|
||||
>
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
<i
|
||||
className="bx bx-dots-vertical-rounded bx-sm text-muted"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-offset="0,8"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="tooltip-dark"
|
||||
title="More Action"
|
||||
></i>
|
||||
)}
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
@ -157,11 +161,7 @@ const ProjectCard = ({ projectData, recall }) => {
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/activities/records?project=${projectInfo.id}`
|
||||
)
|
||||
}
|
||||
onClick={handleViewActivities}
|
||||
>
|
||||
<a className="dropdown-item">
|
||||
<i className="bx bx-task me-2"></i>
|
||||
|
@ -41,8 +41,8 @@ const ProjectNav = ({ onPillClick, activePill }) => {
|
||||
hidden: !(DirAdmin || DireManager || DirUser),
|
||||
},
|
||||
{ key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) },
|
||||
{ key: "organization", icon: "bx bx-buildings", label: "Organization"},
|
||||
{ key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam },
|
||||
{ key: "organization", icon: "bx bx-buildings", label: "Organization"},
|
||||
];
|
||||
return (
|
||||
<div className="nav-align-top">
|
||||
|
@ -7,7 +7,7 @@ const ProjectAssignedOrgs = () => {
|
||||
const { data, isLoading, isError, error } =
|
||||
useProjectAssignedOrganizations(selectedProject);
|
||||
|
||||
const contactList = [
|
||||
const orgList = [
|
||||
{
|
||||
key: "name",
|
||||
label: "Organization Name",
|
||||
@ -23,6 +23,16 @@ const ProjectAssignedOrgs = () => {
|
||||
</div>
|
||||
),
|
||||
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",
|
||||
@ -61,7 +71,7 @@ const ProjectAssignedOrgs = () => {
|
||||
<table className="table dataTable text-nowrap">
|
||||
<thead>
|
||||
<tr className="table_header_border">
|
||||
{contactList.map((col) => (
|
||||
{orgList.map((col) => (
|
||||
<th key={col.key} className={col.align}>
|
||||
{col.label}
|
||||
</th>
|
||||
@ -72,7 +82,7 @@ const ProjectAssignedOrgs = () => {
|
||||
{Array.isArray(data) && data.length > 0 ? (
|
||||
data.map((row, i) => (
|
||||
<tr key={i}>
|
||||
{contactList.map((col) => (
|
||||
{orgList.map((col) => (
|
||||
<td key={col.key} className={col.align}>
|
||||
{col.getValue(row)}
|
||||
</td>
|
||||
@ -82,7 +92,7 @@ const ProjectAssignedOrgs = () => {
|
||||
) : (
|
||||
<tr style={{ height: "200px" }}>
|
||||
<td
|
||||
colSpan={contactList.length}
|
||||
colSpan={orgList.length}
|
||||
className="text-center align-middle"
|
||||
>
|
||||
Not Assigned yet
|
||||
|
@ -7,12 +7,12 @@ const ProjectOrganizations = () => {
|
||||
const { onOpen, startStep, flowType } = useOrganizationModal();
|
||||
const selectedProject = useSelectedProject();
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card pb-10" >
|
||||
<div className="card-header">
|
||||
<div className="d-flex justify-content-end px-2">
|
||||
<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" })}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
|
@ -27,9 +27,9 @@ const Modal = ({
|
||||
className={`modal-dialog modal-${size} modal-dialog-${position}`}
|
||||
role="document"
|
||||
>
|
||||
<div className="modal-content text-white shadow-lg">
|
||||
<div className="modal-content text-white shadow-lg">
|
||||
{/* 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>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -12,6 +12,7 @@ const SelectMultiple = ({
|
||||
valueKey = "id",
|
||||
placeholder = "Please select...",
|
||||
IsLoading = false,
|
||||
required = false
|
||||
}) => {
|
||||
const { setValue, watch } = useFormContext();
|
||||
const selectedValues = watch(name) || [];
|
||||
@ -146,7 +147,8 @@ const SelectMultiple = ({
|
||||
className="multi-select-dropdown-container"
|
||||
style={{ position: "relative" }}
|
||||
>
|
||||
<label>{label}</label>
|
||||
<Label required={required}>{label}</Label>
|
||||
|
||||
|
||||
<div
|
||||
className="multi-select-dropdown-header"
|
||||
|
@ -24,7 +24,7 @@ const schema = z.object({
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const CreateActivity = ({ onClose }) => {
|
||||
const CreateActivity = ({ activity = null, whichGroup = null, close }) => {
|
||||
const maxDescriptionLength = 255;
|
||||
const { mutate: createActivity, isPending: isLoading } = useCreateActivity(() => onClose?.());
|
||||
|
||||
@ -86,25 +86,19 @@ const CreateActivity = ({ onClose }) => {
|
||||
const onSubmit = (formData) => {
|
||||
createActivity(formData);
|
||||
};
|
||||
// const onSubmit = (data) => {
|
||||
// setIsLoading(true);
|
||||
|
||||
// MasterRespository.createActivity(data)
|
||||
// .then( ( resp ) =>
|
||||
// {
|
||||
|
||||
// const cachedData = getCachedData("Activity");
|
||||
// const updatedData = [ ...cachedData, resp?.data ];
|
||||
// cacheData("Activity", updatedData);
|
||||
// showToast("Activity Successfully Added.", "success");
|
||||
// setIsLoading(false);
|
||||
// handleClose()
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// showToast(error.message, "error");
|
||||
// setIsLoading(false);
|
||||
// });
|
||||
// };
|
||||
|
||||
useEffect(()=>{
|
||||
if (activity) {
|
||||
reset({
|
||||
activityName: activity.activityName || '',
|
||||
unitOfMeasurement: activity.unitOfMeasurement || '',
|
||||
checkList: activity.checkList?.map((check) => ({
|
||||
description: check.description || '',
|
||||
isMandatory: check.isMandatory || false,
|
||||
})) || [{ description: '', isMandatory: false }],
|
||||
});
|
||||
}
|
||||
},[activity,reset])
|
||||
const handleClose = useCallback(() => {
|
||||
reset();
|
||||
onClose();
|
||||
|
@ -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 {
|
||||
@ -40,10 +40,10 @@ const UpdateActivity = ({ activityData, onClose }) => {
|
||||
} = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
id: activityData?.id,
|
||||
activityName: activityData?.activityName,
|
||||
unitOfMeasurement: activityData?.unitOfMeasurement,
|
||||
checkList: activityData?.checkLists || [],
|
||||
id: activity?.id,
|
||||
activityName: activity?.activityName,
|
||||
unitOfMeasurement: activity?.unitOfMeasurement,
|
||||
checkList: activity?.checkLists || [],
|
||||
},
|
||||
});
|
||||
|
||||
@ -53,15 +53,15 @@ const UpdateActivity = ({ activityData, onClose }) => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (activityData) {
|
||||
if (activity) {
|
||||
reset({
|
||||
id: activityData.id,
|
||||
activityName: activityData.activityName,
|
||||
unitOfMeasurement: activityData.unitOfMeasurement,
|
||||
checkList: activityData.checkLists || [],
|
||||
id: activity.id,
|
||||
activityName: activity.activityName,
|
||||
unitOfMeasurement: activity.unitOfMeasurement,
|
||||
checkList: activity.checkLists || [],
|
||||
});
|
||||
}
|
||||
}, [activityData, reset]);
|
||||
}, [activity, reset]);
|
||||
|
||||
const addChecklistItem = () => {
|
||||
const values = getValues("checkList");
|
||||
@ -91,36 +91,10 @@ const UpdateActivity = ({ activityData, onClose }) => {
|
||||
};
|
||||
|
||||
const onSubmit = (formData) => {
|
||||
const payload = { ...formData, id: activityData.id };
|
||||
updateActivity({ id: activityData.id, payload });
|
||||
const payload = { ...formData, id: activity.id };
|
||||
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(() => {
|
||||
const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
@ -129,7 +103,6 @@ const UpdateActivity = ({ activityData, onClose }) => {
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* <h6>Update Activity</h6> */}
|
||||
<div className="row">
|
||||
{/* Activity Name */}
|
||||
<div className="col-md-6 text-start">
|
||||
|
@ -17,6 +17,7 @@ import ManageExpenseStatus from "./ManageExpenseStatus";
|
||||
import ManageDocumentCategory from "./ManageDocumentCategory";
|
||||
import ManageDocumentType from "./ManageDocumentType";
|
||||
import ManageServices from "./Services/ManageServices";
|
||||
import ServiceGroups from "./Services/ServicesGroups";
|
||||
|
||||
const MasterModal = ({ modaldata, closeModal }) => {
|
||||
if (!modaldata?.modalType || modaldata.modalType === "delete") {
|
||||
@ -67,6 +68,9 @@ const MasterModal = ({ modaldata, closeModal }) => {
|
||||
"Edit-Services": (
|
||||
<ManageServices data={item} onClose={closeModal} />
|
||||
),
|
||||
"Manage-Services": (
|
||||
<ServiceGroups service={item} onClose={closeModal}/>
|
||||
),
|
||||
};
|
||||
|
||||
return modalComponents[modalType] || null;
|
||||
|
269
src/components/master/Services/ManageActivity.jsx
Normal file
269
src/components/master/Services/ManageActivity.jsx
Normal 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;
|
110
src/components/master/Services/ManageGroup.jsx
Normal file
110
src/components/master/Services/ManageGroup.jsx
Normal 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;
|
263
src/components/master/Services/ServicesGroups.jsx
Normal file
263
src/components/master/Services/ServicesGroups.jsx
Normal 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;
|
@ -6,4 +6,12 @@ const schema = z.object({
|
||||
.string()
|
||||
.min(1, { message: "Description is required" })
|
||||
.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
@ -174,27 +174,25 @@ const AttendancePage = () => {
|
||||
{/* 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">
|
||||
{/* Organization Dropdown */}
|
||||
{organizations?.length > 1 && (
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
style={{ minWidth: "180px" }}
|
||||
value={appliedFilters.selectedOrganization}
|
||||
onChange={(e) =>
|
||||
setAppliedFilters((prev) => ({
|
||||
...prev,
|
||||
selectedOrganization: e.target.value,
|
||||
}))
|
||||
}
|
||||
disabled={orgLoading}
|
||||
>
|
||||
<option value="">All Organizations</option>
|
||||
{organizations?.map((org) => (
|
||||
<option key={org.id} value={org.id}>
|
||||
{org.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
style={{ minWidth: "180px" }}
|
||||
value={appliedFilters.selectedOrganization}
|
||||
onChange={(e) =>
|
||||
setAppliedFilters((prev) => ({
|
||||
...prev,
|
||||
selectedOrganization: e.target.value,
|
||||
}))
|
||||
}
|
||||
disabled={orgLoading}
|
||||
>
|
||||
<option value="">All Organizations</option>
|
||||
{organizations?.map((org) => (
|
||||
<option key={org.id} value={org.id}>
|
||||
{org.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{/* Search Input */}
|
||||
<input
|
||||
@ -207,6 +205,7 @@ const AttendancePage = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -32,7 +32,7 @@ const OrganizationPage = () => {
|
||||
<div className="col-6 text-end mt-2 mt-sm-0">
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 me-1 m-sm-0 bg-primary rounded-circle"
|
||||
className="p-1 me-1 m-sm-0 bg-primary rounded-circle"
|
||||
title="Add New Organization"
|
||||
onClick={()=>onOpen({ startStep: 2,flowType:"default" })}
|
||||
>
|
||||
|
@ -21,11 +21,11 @@ const TenantSelectionPage = () => {
|
||||
const {mutate:handleLogout,isPending:isLogouting} = useLogout(()=>{})
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// if (localStorage.getItem("ctnt")) {
|
||||
// navigate("/dashboard");
|
||||
// }
|
||||
// }, [navigate]);
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem("ctnt")) {
|
||||
navigate("/dashboard");
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && data?.data?.length === 1) {
|
||||
|
@ -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 { useQueryClient } from "@tanstack/react-query";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
@ -6,7 +6,9 @@ import MasterModal from "../../components/master/MasterModal";
|
||||
import ConfirmModal from "../../components/common/ConfirmModal";
|
||||
import MasterTable from "./MasterTable";
|
||||
import useMaster, {
|
||||
useDeleteActivity,
|
||||
useDeleteMasterItem,
|
||||
useDeleteServiceGroup,
|
||||
useMasterMenu,
|
||||
} from "../../hooks/masterHook/useMaster";
|
||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
||||
@ -14,6 +16,16 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_MASTER } from "../../utils/constants";
|
||||
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 dispatch = useDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
@ -34,6 +46,9 @@ const MasterPage = () => {
|
||||
isError: isMasterError,
|
||||
} = useMaster();
|
||||
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 [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)
|
||||
return (
|
||||
<div className="d-flex flex-column align-items-center justify-content-center py-5">
|
||||
@ -87,7 +115,7 @@ const MasterPage = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MasterContext.Provider value={{setDeleletingServiceItem}}>
|
||||
{modalConfig && (
|
||||
<GlobalModel
|
||||
size={
|
||||
@ -106,6 +134,7 @@ const MasterPage = () => {
|
||||
/>
|
||||
</GlobalModel>
|
||||
)}
|
||||
|
||||
|
||||
<ConfirmModal
|
||||
type="delete"
|
||||
@ -116,6 +145,16 @@ const MasterPage = () => {
|
||||
onSubmit={handleDeleteSubmit}
|
||||
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">
|
||||
<Breadcrumb
|
||||
@ -185,7 +224,7 @@ const MasterPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</MasterContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
aria-label="Modify"
|
||||
type="button"
|
||||
|
@ -19,15 +19,15 @@ import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
|
||||
import showToast from "../../services/toastService";
|
||||
import { getCachedData, cacheData } from "../../slices/apiDataManager";
|
||||
import GlobalModel from "../../components/common/GlobalModel";
|
||||
import {formatNumber} from "../../utils/dateUtils";
|
||||
import { formatNumber } from "../../utils/dateUtils";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
const ProjectListView = ({ projectData, recall }) => {
|
||||
const [projectInfo, setProjectInfo] = useState(projectData);
|
||||
const dispatch = useDispatch()
|
||||
const dispatch = useDispatch()
|
||||
const { projects_Details, loading, error, refetch } = useProjectDetails(
|
||||
projectInfo?.id,false
|
||||
projectInfo?.id, false
|
||||
);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
@ -35,16 +35,16 @@ const ProjectListView = ({ projectData, recall }) => {
|
||||
useEffect(() => {
|
||||
setProjectInfo(projectData);
|
||||
}, [projectData]);
|
||||
const {
|
||||
mutate: updateProject,
|
||||
isPending,
|
||||
isSuccess,
|
||||
isError,
|
||||
} = useUpdateProject({
|
||||
onSuccessCallback: () => {
|
||||
setShowModal(false);
|
||||
},
|
||||
})
|
||||
const {
|
||||
mutate: updateProject,
|
||||
isPending,
|
||||
isSuccess,
|
||||
isError,
|
||||
} = useUpdateProject({
|
||||
onSuccessCallback: () => {
|
||||
setShowModal(false);
|
||||
},
|
||||
})
|
||||
|
||||
const handleShow = async () => {
|
||||
try {
|
||||
@ -65,9 +65,15 @@ const ProjectListView = ({ projectData, recall }) => {
|
||||
const handleClose = () => setShowModal(false);
|
||||
|
||||
const handleViewProject = () => {
|
||||
dispatch(setProjectId(projectInfo.id))
|
||||
navigate(`/projects/details`);
|
||||
};
|
||||
|
||||
const handleViewActivities = () => {
|
||||
dispatch(setProjectId(projectInfo.id))
|
||||
navigate(`/activities/records?project=${projectInfo.id}`);
|
||||
};
|
||||
|
||||
const handleFormSubmit = (updatedProject) => {
|
||||
if (projectInfo?.id) {
|
||||
updateProject({
|
||||
@ -81,18 +87,18 @@ const ProjectListView = ({ projectData, recall }) => {
|
||||
<>
|
||||
{showModal && projects_Details && (
|
||||
<GlobalModel isOpen={showModal} closeModal={handleClose}> <ManageProjectInfo
|
||||
project={projects_Details}
|
||||
handleSubmitForm={handleFormSubmit}
|
||||
onClose={handleClose}
|
||||
isPending={isPending}
|
||||
/></GlobalModel>
|
||||
project={projects_Details}
|
||||
handleSubmitForm={handleFormSubmit}
|
||||
onClose={handleClose}
|
||||
isPending={isPending}
|
||||
/></GlobalModel>
|
||||
)}
|
||||
|
||||
<tr className={`py-8 ${isPending ? "bg-light opacity-50 pointer-events-none" : ""} `}>
|
||||
<td className="text-start" colSpan={5}>
|
||||
<span
|
||||
className="text-primary cursor-pointer"
|
||||
onClick={() => {
|
||||
onClick={() => {
|
||||
dispatch(setProjectId(projectInfo.id))
|
||||
navigate(`/projects/details`)
|
||||
}}
|
||||
@ -168,7 +174,7 @@ const ProjectListView = ({ projectData, recall }) => {
|
||||
<a
|
||||
aria-label="click to View details"
|
||||
className="dropdown-item"
|
||||
onClick={() => navigate(`/projects/details`)}
|
||||
onClick={handleViewProject}
|
||||
>
|
||||
<i className="bx bx-detail me-2"></i>
|
||||
<span className="align-left">View details</span>
|
||||
@ -182,9 +188,7 @@ const ProjectListView = ({ projectData, recall }) => {
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
onClick={() =>
|
||||
navigate(`/activities/records?project=${projectInfo.id}`)
|
||||
}
|
||||
onClick={handleViewActivities}
|
||||
>
|
||||
<a className="dropdown-item">
|
||||
<i className="bx bx-task me-2"></i>
|
||||
|
@ -32,11 +32,11 @@ export const MasterRespository = {
|
||||
getActivites: () => api.get("api/master/activities"),
|
||||
createActivity: (data) => api.post("api/master/activity", data),
|
||||
|
||||
//Services
|
||||
//Services
|
||||
getService: () => api.get("api/master/service/list"),
|
||||
createService: (data) => api.post("api/master/service/create", 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) =>
|
||||
api.post(`api/master/activity/edit/${id}`, data),
|
||||
@ -114,10 +114,20 @@ export const MasterRespository = {
|
||||
updateDocumentType: (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"),
|
||||
getMasterServices:()=>api.get("/api/Master/service/list"),
|
||||
deleteActivity:(id)=>api.delete(`/api/Master/activity/delete/${id}`),
|
||||
|
||||
getOrganizationType:()=>api.get('/api/Master/organization-type/list')
|
||||
getOrganizationType: () => api.get("/api/Master/organization-type/list"),
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user