Compare commits
23 Commits
b4d2fd86f7
...
3ee5a71cc9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ee5a71cc9 | ||
|
|
c61cb3bb63 | ||
|
|
38fbc6dc84 | ||
|
|
c38a92b8b7 | ||
|
|
d9f4bc47f6 | ||
|
|
099098d39a | ||
|
|
c76045d39e | ||
|
|
3c9e68f91b | ||
|
|
3e71511d8e | ||
| 148058d1d2 | |||
| 4354d0f611 | |||
|
|
7f7f05277a | ||
| a3a827ed78 | |||
|
|
fc2f69f429 | ||
|
|
b15f5df8e7 | ||
|
|
a5d74a0b34 | ||
|
|
450c8143ff | ||
| d64b6c6134 | |||
|
|
461ffeb83d | ||
| b53686ddd5 | |||
|
|
1caeeb890d | ||
|
|
a096f3d743 | ||
| bb6c48300c |
@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import moment from "moment";
|
||||
import { ProjectStatus } from "../../utils/projectStatus";
|
||||
import { getProjectStatusName } from "../../utils/projectStatus";
|
||||
const AboutProject = ({ data }) => {
|
||||
const [CurrentProject, setCurrentProject] = useState(data);
|
||||
|
||||
@ -34,7 +34,7 @@ const AboutProject = ({ data }) => {
|
||||
<li className="d-flex align-items-center mb-2">
|
||||
<i className="bx bx-trophy"></i>
|
||||
<span className="fw-medium mx-2">Status:</span>{" "}
|
||||
<span>{ProjectStatus(data.projectStatusId)}</span>
|
||||
<span>{getProjectStatusName(data.projectStatusId)}</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<i className="bx bx-user"></i>
|
||||
|
||||
@ -34,18 +34,24 @@ const AssignRoleModel = ({ assignData, onClose }) => {
|
||||
const [selectedRole, setSelectedRole] = useState("all");
|
||||
const [selectedEmployees, setSelectedEmployees] = useState([]);
|
||||
|
||||
|
||||
const { handleSubmit, control, setValue, watch, formState: { errors },reset } = useForm({
|
||||
defaultValues: {
|
||||
selectedEmployees: [],
|
||||
description:""
|
||||
},
|
||||
resolver: (data) => {
|
||||
const validation = schema.safeParse(data);
|
||||
if (validation.success) return { values: data, errors: {} };
|
||||
return { values: {}, errors: validation.error.formErrors.fieldErrors };
|
||||
},
|
||||
});
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { errors },
|
||||
reset,
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
selectedEmployees: [],
|
||||
description: "",
|
||||
},
|
||||
resolver: (data) => {
|
||||
const validation = schema.safeParse(data);
|
||||
if (validation.success) return { values: data, errors: {} };
|
||||
return { values: {}, errors: validation.error.formErrors.fieldErrors };
|
||||
},
|
||||
});
|
||||
|
||||
const handleRoleChange = (event) => {
|
||||
setSelectedRole(event.plannedTask.value);
|
||||
@ -78,62 +84,51 @@ const { handleSubmit, control, setValue, watch, formState: { errors },reset } =
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const formattedData = {
|
||||
taskTeam: data.selectedEmployees,
|
||||
plannedTask: parseInt(plannedTask, 10),
|
||||
description: data.description,
|
||||
assignmentDate: new Date().toISOString(),
|
||||
workItemId: assignData?.workItem?.workItem.id,
|
||||
};
|
||||
try {
|
||||
let response = await TasksRepository.assignTask(formattedData);
|
||||
showToast( "Task Successfully Assigend", "success" );
|
||||
setSelectedEmployees([])
|
||||
reset();
|
||||
closeModal()
|
||||
|
||||
const onSubmit = async(data) => {
|
||||
const formattedData = {
|
||||
taskTeam: data.selectedEmployees,
|
||||
plannedTask: parseInt( plannedTask, 10 ),
|
||||
description: data.description,
|
||||
assignmentDate: new Date().toISOString(),
|
||||
workItemId:assignData?.workItem?.workItem.id
|
||||
} catch (error) {
|
||||
showToast("something wrong", "error");
|
||||
}
|
||||
};
|
||||
try
|
||||
{
|
||||
let response = await TasksRepository.assignTask( formattedData );
|
||||
showToast( "Task Successfully Assigend", "success" )
|
||||
reset()
|
||||
onClose()
|
||||
} catch ( error )
|
||||
{
|
||||
showToast("something wrong","error")
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
useEffect(()=>{
|
||||
dispatch(changeMaster("Job Role"))
|
||||
return ()=> setSelectedRole("all")
|
||||
},[dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(changeMaster("Job Role"));
|
||||
return () => setSelectedRole("all");
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="container my-1">
|
||||
<div className="mb-2">
|
||||
<div className="bs-stepper wizard-numbered d-flex justify-content-center align-items-center flex-wrap">
|
||||
<div className="mb-">
|
||||
<p className="fs-sm-5 fs-6 text-dark text-start d-flex align-items-center flex-wrap">
|
||||
{[
|
||||
assignData?.building?.name,
|
||||
assignData?.floor?.floorName,
|
||||
assignData?.workArea?.areaName,
|
||||
assignData?.workItem?.workItem?.activityMaster?.activityName,
|
||||
].map((item, index, array) => (
|
||||
<div
|
||||
key={index}
|
||||
className="col d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<div className="bs-stepper-header p-1 text-center">
|
||||
<span className="fs-5">{item}</span>
|
||||
|
||||
{/* Arrow between items */}
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map((item, index, array) => (
|
||||
<span key={index} className="d-flex align-items-center">
|
||||
{item}
|
||||
{index < array.length - 1 && (
|
||||
<div className="line">
|
||||
<i className="icon-base bx bx-chevron-right scaleX-n1-rtl"></i>
|
||||
</div>
|
||||
<i className="bx bx-chevron-right mx-2"></i>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="row mb-1">
|
||||
@ -166,7 +161,10 @@ useEffect(()=>{
|
||||
<div className="divider text-start">
|
||||
<div className="divider-text">Employee</div>
|
||||
</div>
|
||||
{selectedRole !== "" && (
|
||||
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-8 h-sm-25 overflow-auto">
|
||||
{selectedRole !== "" && (
|
||||
<div className="row mb-2">
|
||||
<div className="col-sm-12">
|
||||
<div className="row">
|
||||
@ -178,7 +176,7 @@ useEffect(()=>{
|
||||
return (
|
||||
<div
|
||||
key={emp.id}
|
||||
className="col-6 col-sm-4 col-md-4 col-lg-3 mb-1"
|
||||
className="col-6 col-sm-6 col-md-4 col-lg-4 mb-1"
|
||||
>
|
||||
<div className="form-check text-start p-0">
|
||||
<div className="li-wrapper d-flex justify-content-start align-items-start">
|
||||
@ -200,10 +198,19 @@ useEffect(()=>{
|
||||
)}
|
||||
/>
|
||||
<div className="list-content">
|
||||
<h6 className="mb-0">
|
||||
<p
|
||||
className=" mb-0"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
fontWeight: "bolder",
|
||||
}}
|
||||
>
|
||||
{emp.firstName} {emp.lastName}
|
||||
</h6>
|
||||
<small className="text-muted">
|
||||
</p>
|
||||
<small
|
||||
className="lead"
|
||||
style={{ fontSize: "10px" }}
|
||||
>
|
||||
{loading && (
|
||||
<p
|
||||
className="skeleton para"
|
||||
@ -224,7 +231,9 @@ useEffect(()=>{
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className="col-12 col-md-4 h-25 overflow-auto" style={{maxHeight:"200px"}}>
|
||||
|
||||
{selectedEmployees.length > 0 && (
|
||||
<div className="mt-1">
|
||||
<div className="text-start px-2">
|
||||
@ -233,7 +242,7 @@ useEffect(()=>{
|
||||
return (
|
||||
<span
|
||||
key={empId}
|
||||
className="badge bg-label-primary d-inline-flex align-items-center gap-2 me-1 p-2 mb-2"
|
||||
className="badge bg-label-primary d-inline-flex align-items-center gap-2 me-1 p-1 mb-2"
|
||||
>
|
||||
{emp.firstName} {emp.lastName}
|
||||
<p
|
||||
@ -256,6 +265,10 @@ useEffect(()=>{
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className="col-md text-start mx-0 px-0">
|
||||
<div className="form-check form-check-inline mt-4 px-1">
|
||||
@ -273,7 +286,7 @@ useEffect(()=>{
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm "
|
||||
className="form-control form-control-xs"
|
||||
value={plannedTask}
|
||||
onChange={(e) => setPlannedTask(e.target.value)}
|
||||
id="defaultFormControlInput"
|
||||
@ -298,7 +311,7 @@ useEffect(()=>{
|
||||
{...field}
|
||||
className="form-control"
|
||||
id="exampleFormControlTextarea1"
|
||||
rows="3"
|
||||
rows="2"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -159,8 +159,14 @@ const WorkItem = ({ workItem, forBuilding, forFloor, forWorkArea }) => {
|
||||
</a>
|
||||
<a className="dropdown-item">
|
||||
{" "}
|
||||
<i className="bx bx-trash me-1 text-danger"></i>Delete
|
||||
<i className="bx bx-trash me-1 text-danger" ></i>Delete
|
||||
</a>
|
||||
{!projectId && ( <a className="dropdown-item" data-bs-toggle="modal"
|
||||
data-bs-target="#project-modal"
|
||||
onClick={openModal}>
|
||||
{" "}
|
||||
<i className="bx bx-task me-1 text-info" ></i>Assign
|
||||
</a> )}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@ -79,18 +79,18 @@ const ProjectBanner = ( {project_data} ) =>
|
||||
{/* Project Banner */}
|
||||
<div className="col-12">
|
||||
<div className="card mb-6 pb-0">
|
||||
<div className="d-flex align-items-center justify-content-between p-4 flex-wrap">
|
||||
<div className="d-flex align-items-center justify-content-between p-2 flex-wrap">
|
||||
{/* Left: Icon + Name */}
|
||||
<div className="d-flex align-items-center gap-3">
|
||||
<img
|
||||
src="../../assets/icons/civil-engineering.svg"
|
||||
alt="user image"
|
||||
className="rounded-3"
|
||||
style={{ width: "60px", height: "60px" }}
|
||||
style={{ width: "40px", height: "40px" }}
|
||||
/>
|
||||
<h4 className="mb-0">
|
||||
<h5 className="mb-0">
|
||||
{CurrentProject.name ? CurrentProject.name : "N/A"}
|
||||
</h4>
|
||||
</h5>
|
||||
</div>
|
||||
{manageProject && (
|
||||
<button
|
||||
|
||||
@ -8,278 +8,270 @@ import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||
import showToast from "../../services/toastService";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_PROJECT } from "../../utils/constants";
|
||||
|
||||
|
||||
import {MANAGE_PROJECT} from "../../utils/constants";
|
||||
import { getProjectStatusColor,getProjectStatusName } from "../../utils/projectStatus";
|
||||
|
||||
const ProjectCard = ({ projectData }) => {
|
||||
const [projectInfo, setProjectInfo] = useState(projectData);
|
||||
const [projectDetails, setProjectDetails] = useState(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||
const [projectInfo, setProjectInfo] = useState(projectData);
|
||||
const [projectDetails, setProjectDetails] = useState(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||
|
||||
const handleShow = async () => {
|
||||
try {
|
||||
const response = await ProjectRepository.getProjectByprojectId(projectInfo.id);
|
||||
setProjectDetails(response.data);
|
||||
setShowModal(true);
|
||||
} catch (error) {
|
||||
showToast("Failed to load project details", "error");
|
||||
}
|
||||
};
|
||||
const handleShow = async () => {
|
||||
try {
|
||||
const response = await ProjectRepository.getProjectByprojectId(
|
||||
projectInfo.id
|
||||
);
|
||||
setProjectDetails(response.data);
|
||||
setShowModal(true);
|
||||
} catch (error) {
|
||||
showToast("Failed to load project details", "error");
|
||||
}
|
||||
};
|
||||
|
||||
const getProgress = (planned, completed) => {
|
||||
return (completed * 100) / planned + "%";
|
||||
};
|
||||
const getProgressInNumber = (planned, completed) => {
|
||||
return (completed * 100) / planned;
|
||||
};
|
||||
const getProgress = (planned, completed) => {
|
||||
return (completed * 100) / planned + "%";
|
||||
};
|
||||
const getProgressInNumber = (planned, completed) => {
|
||||
return (completed * 100) / planned;
|
||||
};
|
||||
|
||||
const handleClose = () => setShowModal(false);
|
||||
|
||||
const getProjectStatusName = (statusId) => {
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "Active";
|
||||
case 2:
|
||||
return "On Hold";
|
||||
// case 3:
|
||||
// return "Suspended";
|
||||
case 3:
|
||||
return "Inactive";
|
||||
case 4:
|
||||
return "Completed";
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectStatusColor = (statusId) => {
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "bg-label-success";
|
||||
case 2:
|
||||
return "bg-label-warning";
|
||||
case 3:
|
||||
return "bg-label-info";
|
||||
case 4:
|
||||
return "bg-label-secondary";
|
||||
case 5:
|
||||
return "bg-label-dark";
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewProject = () => {
|
||||
navigate(`/projects/${projectData.id}`);
|
||||
};
|
||||
const handleClose = () => setShowModal(false);
|
||||
|
||||
|
||||
const handleFormSubmit = (updatedProject) => {
|
||||
if (projectInfo?.id) {
|
||||
ProjectRepository.updateProject(projectInfo.id, updatedProject)
|
||||
.then((response) => {
|
||||
const updatedProjectData = {
|
||||
...projectInfo,
|
||||
...response.data,
|
||||
building: projectDetails?.building,
|
||||
};
|
||||
const handleViewProject = () => {
|
||||
navigate(`/projects/${projectData.id}`);
|
||||
};
|
||||
|
||||
setProjectInfo(updatedProject);
|
||||
const handleFormSubmit = (updatedProject) => {
|
||||
if (projectInfo?.id) {
|
||||
ProjectRepository.updateProject(projectInfo.id, updatedProject)
|
||||
.then((response) => {
|
||||
const updatedProjectData = {
|
||||
...projectInfo,
|
||||
...response.data,
|
||||
building: projectDetails?.building,
|
||||
};
|
||||
|
||||
if (getCachedData(`projectinfo-${projectInfo.id}`)) {
|
||||
cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
|
||||
}
|
||||
setProjectInfo(updatedProject);
|
||||
|
||||
const projects_list = getCachedData("projectslist");
|
||||
if (projects_list) {
|
||||
const updatedProjectsList = projects_list.map((project) =>
|
||||
project.id === projectInfo.id
|
||||
? { ...project, ...response.data, tenant: project.tenant }
|
||||
: project
|
||||
);
|
||||
cacheData("projectslist", updatedProjectsList);
|
||||
}
|
||||
if (getCachedData(`projectinfo-${projectInfo.id}`)) {
|
||||
cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
|
||||
}
|
||||
|
||||
showToast("Project updated successfully.", "success");
|
||||
setShowModal(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message, "error");
|
||||
});
|
||||
}
|
||||
};
|
||||
const projects_list = getCachedData("projectslist");
|
||||
if (projects_list) {
|
||||
const updatedProjectsList = projects_list.map((project) =>
|
||||
project.id === projectInfo.id
|
||||
? { ...project, ...response.data, tenant: project.tenant }
|
||||
: project
|
||||
);
|
||||
cacheData("projectslist", updatedProjectsList);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModal && projectDetails && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: "block" }}
|
||||
aria-hidden="false"
|
||||
>
|
||||
<ManageProjectInfo
|
||||
project={projectDetails}
|
||||
handleSubmitForm={handleFormSubmit}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
showToast("Project updated successfully.", "success");
|
||||
setShowModal(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message, "error");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModal && projectDetails && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: "block" }}
|
||||
aria-hidden="false"
|
||||
>
|
||||
<ManageProjectInfo
|
||||
project={projectDetails}
|
||||
handleSubmitForm={handleFormSubmit}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
|
||||
<div className="card cursor-pointer">
|
||||
<div className="card-header pb-4">
|
||||
<div className="d-flex align-items-start">
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="avatar me-4">
|
||||
<i
|
||||
className="rounded-circle bx bx-building-house"
|
||||
style={{ fontSize: "xx-large" }}
|
||||
></i>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
|
||||
<div className="card cursor-pointer">
|
||||
<div className="card-header pb-4">
|
||||
<div className="d-flex align-items-start">
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="avatar me-4">
|
||||
<i
|
||||
className="rounded-circle bx bx-building-house"
|
||||
style={{ fontSize: "xx-large" }}
|
||||
></i>
|
||||
</div>
|
||||
<div className="me-2">
|
||||
<h5 className="mb-0">
|
||||
<a
|
||||
className="stretched-link text-heading"
|
||||
onClick={handleViewProject}
|
||||
>
|
||||
{projectInfo.name}
|
||||
</a>
|
||||
</h5>
|
||||
<div className="client-info text-body">
|
||||
<span className="fw-medium">Client: </span>
|
||||
<span>{projectInfo.contactPerson}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`ms-auto ${!ManageProject && "d-none"}`}>
|
||||
<div className="dropdown z-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<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>
|
||||
<a
|
||||
aria-label="click to View details"
|
||||
className="dropdown-item"
|
||||
|
||||
onClick={handleViewProject}
|
||||
>
|
||||
<i className="bx bx-detail me-2"></i>
|
||||
<span className="align-left">View details</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li onClick={handleShow}>
|
||||
<a className="dropdown-item">
|
||||
<i className="bx bx-pencil me-2"></i>
|
||||
<span className="align-left">Modify</span>
|
||||
</a>
|
||||
</li>
|
||||
<li onClick={()=>navigate(`/activities/records?project=${projectInfo.id}`)}>
|
||||
<a className="dropdown-item">
|
||||
<i className="bx bx-task me-2"></i>
|
||||
<span className="align-left" >Activities</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body pb-1">
|
||||
<div className="d-flex align-items-center flex-wrap">
|
||||
<div className="text-start mb-4">
|
||||
<p className="mb-1">
|
||||
<span className="text-heading fw-medium">Start Date: </span>
|
||||
{projectInfo.startDate
|
||||
? moment(projectInfo.startDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</p>
|
||||
<p className="mb-1">
|
||||
<span className="text-heading fw-medium">Deadline: </span>
|
||||
|
||||
{projectInfo.endDate
|
||||
? moment(projectInfo.endDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</p>
|
||||
<p className="mb-0">{projectInfo.projectAddress}</p>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body border-top">
|
||||
<div className="d-flex align-items-center mb-4">
|
||||
<p className="mb-0">
|
||||
<span
|
||||
className={
|
||||
`badge rounded-pill ` +
|
||||
getProjectStatusColor(projectInfo.projectStatusId)
|
||||
}
|
||||
>
|
||||
{getProjectStatusName(projectInfo.projectStatusId)}
|
||||
</span>
|
||||
</p>{" "}
|
||||
{getDateDifferenceInDays(projectInfo.endDate, Date()) >= 0 &&
|
||||
( <span className="badge bg-label-success ms-auto">
|
||||
{projectInfo.endDate &&
|
||||
getDateDifferenceInDays(projectInfo.endDate, Date())}{" "}
|
||||
Days left
|
||||
</span>) }
|
||||
{getDateDifferenceInDays(projectInfo.endDate, Date()) < 0 &&
|
||||
( <span className="badge bg-label-danger ms-auto">
|
||||
{projectInfo.endDate &&
|
||||
getDateDifferenceInDays(projectInfo.endDate, Date())}{" "}
|
||||
Days overdue
|
||||
</span>)}
|
||||
|
||||
</div>
|
||||
<div className="d-flex justify-content-between align-items-center mb-2">
|
||||
<small className="text-body">Task: {projectInfo.completedWork} / {projectInfo.plannedWork}</small>
|
||||
<small className="text-body">{Math.floor(getProgressInNumber(projectInfo.plannedWork, projectInfo.completedWork)) || 0} % Completed</small>
|
||||
</div>
|
||||
<div className="progress mb-4 rounded" style={{ height: "8px" }}>
|
||||
<div
|
||||
className="progress-bar rounded"
|
||||
role="progressbar"
|
||||
style={{ width: getProgress(projectInfo.plannedWork, projectInfo.completedWork) }}
|
||||
aria-valuenow={projectInfo.completedWork}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax={projectInfo.plannedWork}
|
||||
></div>
|
||||
</div>
|
||||
<div className="d-flex align-items-center justify-content-between">
|
||||
{/* <div className="d-flex align-items-center ">
|
||||
</div> */}
|
||||
<div >
|
||||
<a
|
||||
|
||||
className="text-muted d-flex " alt="Active team size"
|
||||
>
|
||||
<i className="bx bx-group bx-sm me-1_5"></i>{projectInfo?.teamSize} Members
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div >
|
||||
<a
|
||||
className="text-muted d-flex align-items-center"
|
||||
>
|
||||
<i className="bx bx-chat me-1 "></i> <span className="text-decoration-line-through">15</span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="me-2">
|
||||
<h5 className="mb-0">
|
||||
<a
|
||||
className="stretched-link text-heading"
|
||||
onClick={handleViewProject}
|
||||
>
|
||||
{projectInfo.name}
|
||||
</a>
|
||||
</h5>
|
||||
<div className="client-info text-body">
|
||||
<span className="fw-medium">Client: </span>
|
||||
<span>{projectInfo.contactPerson}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`ms-auto ${!ManageProject && "d-none"}`}>
|
||||
<div className="dropdown z-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<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>
|
||||
<a
|
||||
aria-label="click to View details"
|
||||
className="dropdown-item"
|
||||
onClick={handleViewProject}
|
||||
>
|
||||
<i className="bx bx-detail me-2"></i>
|
||||
<span className="align-left">View details</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li onClick={handleShow}>
|
||||
<a className="dropdown-item">
|
||||
<i className="bx bx-pencil me-2"></i>
|
||||
<span className="align-left">Modify</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/activities/records?project=${projectInfo.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<a className="dropdown-item">
|
||||
<i className="bx bx-task me-2"></i>
|
||||
<span className="align-left">Activities</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
</div>
|
||||
<div className="card-body pb-1">
|
||||
<div className="d-flex align-items-center flex-wrap">
|
||||
<div className="text-start mb-4">
|
||||
<p className="mb-1">
|
||||
<span className="text-heading fw-medium">Start Date: </span>
|
||||
{projectInfo.startDate
|
||||
? moment(projectInfo.startDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</p>
|
||||
<p className="mb-1">
|
||||
<span className="text-heading fw-medium">Deadline: </span>
|
||||
|
||||
{projectInfo.endDate
|
||||
? moment(projectInfo.endDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</p>
|
||||
<p className="mb-0">{projectInfo.projectAddress}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body border-top">
|
||||
<div className="d-flex align-items-center mb-4">
|
||||
<p className="mb-0">
|
||||
<span
|
||||
className={
|
||||
`badge rounded-pill ` +
|
||||
getProjectStatusColor(projectInfo.projectStatusId)
|
||||
}
|
||||
>
|
||||
{getProjectStatusName(projectInfo.projectStatusId)}
|
||||
</span>
|
||||
</p>{" "}
|
||||
{getDateDifferenceInDays(projectInfo.endDate, Date()) >= 0 && (
|
||||
<span className="badge bg-label-success ms-auto">
|
||||
{projectInfo.endDate &&
|
||||
getDateDifferenceInDays(projectInfo.endDate, Date())}{" "}
|
||||
Days left
|
||||
</span>
|
||||
)}
|
||||
{getDateDifferenceInDays(projectInfo.endDate, Date()) < 0 && (
|
||||
<span className="badge bg-label-danger ms-auto">
|
||||
{projectInfo.endDate &&
|
||||
getDateDifferenceInDays(projectInfo.endDate, Date())}{" "}
|
||||
Days overdue
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="d-flex justify-content-between align-items-center mb-2">
|
||||
<small className="text-body">
|
||||
Task: {projectInfo.completedWork} / {projectInfo.plannedWork}
|
||||
</small>
|
||||
<small className="text-body">
|
||||
{Math.floor(
|
||||
getProgressInNumber(
|
||||
projectInfo.plannedWork,
|
||||
projectInfo.completedWork
|
||||
)
|
||||
) || 0}{" "}
|
||||
% Completed
|
||||
</small>
|
||||
</div>
|
||||
<div className="progress mb-4 rounded" style={{ height: "8px" }}>
|
||||
<div
|
||||
className="progress-bar rounded"
|
||||
role="progressbar"
|
||||
style={{
|
||||
width: getProgress(
|
||||
projectInfo.plannedWork,
|
||||
projectInfo.completedWork
|
||||
),
|
||||
}}
|
||||
aria-valuenow={projectInfo.completedWork}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax={projectInfo.plannedWork}
|
||||
></div>
|
||||
</div>
|
||||
<div className="d-flex align-items-center justify-content-between">
|
||||
{/* <div className="d-flex align-items-center ">
|
||||
</div> */}
|
||||
<div>
|
||||
<a className="text-muted d-flex " alt="Active team size">
|
||||
<i className="bx bx-group bx-sm me-1_5"></i>
|
||||
{projectInfo?.teamSize} Members
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a className="text-muted d-flex align-items-center">
|
||||
<i className="bx bx-chat me-1 "></i>{" "}
|
||||
<span className="text-decoration-line-through">15</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectCard;
|
||||
|
||||
@ -45,7 +45,7 @@ const GlobalModel = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`modal fade ${modalType}`}
|
||||
className={`modal fade ${ modalType }`}
|
||||
id="customModal"
|
||||
tabIndex="-1"
|
||||
aria-labelledby="exampleModalLabel"
|
||||
@ -53,9 +53,9 @@ const GlobalModel = ({
|
||||
ref={modalRef} // Assign the ref to the modal element
|
||||
{...dataAttributesProps}
|
||||
>
|
||||
<div className={`modal-dialog ${dialogClass} ${modalSizeClass}`} role={role}>
|
||||
<div className={`modal-dialog ${dialogClass} ${modalSizeClass } mx-sm-auto mx-1`} role={role}>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<div className="modal-header p-0">
|
||||
{/* Close button inside the modal header */}
|
||||
<button
|
||||
type="button"
|
||||
@ -65,7 +65,7 @@ const GlobalModel = ({
|
||||
onClick={closeModal} // Trigger the React closeModal function
|
||||
></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="modal-body p-sm-4 p-0">
|
||||
{children} {/* Render children here, which can be the ReportTask component */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,30 +1,37 @@
|
||||
import React from "react";
|
||||
|
||||
const ProgressBar = ( {completeValue, totalValue} ) =>
|
||||
{
|
||||
|
||||
|
||||
const getProgress = (complete, total) => {
|
||||
return (total * 100) / complete + "%";
|
||||
const ProgressBar = ({
|
||||
plannedWork = 100,
|
||||
completedWork = 0,
|
||||
height = "8px",
|
||||
className = "mb-4",
|
||||
rounded = true,
|
||||
}) => {
|
||||
const getProgress = (planned, completed) => {
|
||||
if (!planned || planned === 0) return "0%";
|
||||
return `${Math.min((completed / planned) * 100, 100).toFixed(2)}%`;
|
||||
};
|
||||
return (
|
||||
<div className="progress mb-4 rounded" style={{height: "8px"}}>
|
||||
<div className="progress p-0">
|
||||
|
||||
const progressStyle = {
|
||||
width: getProgress(plannedWork, completedWork),
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`progress ${className} ${rounded ? "rounded" : ""}`}
|
||||
style={{ height }}
|
||||
>
|
||||
<div
|
||||
className="progress-bar"
|
||||
className={`progress-bar ${rounded ? "rounded" : ""}`}
|
||||
role="progressbar"
|
||||
style={{
|
||||
width: `${getProgress( totalValue,completeValue)}`,
|
||||
height: "10px",
|
||||
}}
|
||||
aria-valuenow={completeValue}
|
||||
style={progressStyle}
|
||||
aria-valuenow={completedWork}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax={totalValue}
|
||||
aria-valuemax={plannedWork}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgressBar;
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import { setProjectId } from "../slices/localVariablesSlice";
|
||||
|
||||
export const useProjects = () =>
|
||||
{
|
||||
const {profile} = useProfile()
|
||||
const dispatch = useDispatch();
|
||||
const [projects, setProjects] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -48,9 +49,19 @@ export const useProjects = () =>
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setProjectId(projects[0]?.id));
|
||||
}, [projects]);
|
||||
useEffect( () =>
|
||||
{
|
||||
if (projects )
|
||||
{
|
||||
if ( profile?.projects && profile?.projects?.length > 0 )
|
||||
{
|
||||
dispatch(setProjectId(profile?.projects[0]))
|
||||
} else
|
||||
{
|
||||
dispatch(setProjectId(1))
|
||||
}
|
||||
}
|
||||
}, [profile]);
|
||||
|
||||
return { projects, loading, error, refetch: fetchData };
|
||||
};
|
||||
|
||||
@ -19,15 +19,37 @@ const DailyTask = () => {
|
||||
const { profile: LoggedUser } = useProfile();
|
||||
const [searchParams] = useSearchParams();
|
||||
const projectId = searchParams.get("project");
|
||||
|
||||
const selectedProject = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
);
|
||||
const {
|
||||
projects,
|
||||
loading: project_lodaing,
|
||||
error: projects_Error,
|
||||
} = useProjects();
|
||||
const selectedProject = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
);
|
||||
|
||||
|
||||
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
// Sync projectId (either from URL or pick first accessible one)
|
||||
useEffect(() => {
|
||||
if (!project_lodaing && projects.length > 0) {
|
||||
const userProjects = projects.filter((p) =>
|
||||
LoggedUser?.projects?.map(Number).includes(p.id)
|
||||
);
|
||||
|
||||
if (projectId) {
|
||||
dispatch(setProjectId(projectId));
|
||||
} else if (!selectedProject && userProjects.length > 0) {
|
||||
dispatch(setProjectId(userProjects[0].id));
|
||||
}
|
||||
|
||||
setInitialized(true);
|
||||
}
|
||||
}, [project_lodaing, projects, projectId, selectedProject]);
|
||||
|
||||
|
||||
const dispatch = useDispatch(selectedProject);
|
||||
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
||||
|
||||
@ -60,9 +82,6 @@ const DailyTask = () => {
|
||||
openModal();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (projectId) dispatch(setProjectId(projectId));
|
||||
}, [projectId]);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@ -161,6 +180,11 @@ const DailyTask = () => {
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{(!initialized || !selectedProject) && ( <tr>
|
||||
<td colSpan={7} className="text-center">
|
||||
<p>Loading..</p>
|
||||
</td>
|
||||
</tr>)}
|
||||
|
||||
{TaskLists.map((task, index) => {
|
||||
const accordionId = `accordion-${index}`;
|
||||
|
||||
@ -35,13 +35,17 @@ const ForgotPasswordPage = () => {
|
||||
setLoading(true)
|
||||
const response = await AuthRepository.forgotPassword(data)
|
||||
if ( response.data && response.success )
|
||||
showToast( response.message, "success" )
|
||||
showToast( "verification email has been sent to your registered email address", "success" )
|
||||
reset()
|
||||
setLoading( false )
|
||||
} catch ( err )
|
||||
{
|
||||
if(err.response.status === 404){
|
||||
showToast( "verification email has been sent to your registered email address", "success" )
|
||||
}else{
|
||||
showToast("Something wrong","error")
|
||||
}
|
||||
|
||||
showToast( err.message, "error" )
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ const LoginPage = () => {
|
||||
setLoading(false);
|
||||
navigate("/dashboard");
|
||||
} catch (err) {
|
||||
showToast("Invalid username or password.","error")
|
||||
console.log("Unable to proceed. Please try again.");
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect,useRef } from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import moment from "moment";
|
||||
import { Link, NavLink, useNavigate } from "react-router-dom";
|
||||
import Avatar from "../../components/common/Avatar";
|
||||
@ -11,7 +11,12 @@ import { hasUserPermission } from "../../utils/authUtils";
|
||||
import { MANAGE_EMPLOYEES } from "../../utils/constants";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import SuspendEmp from "../../components/Employee/SuspendEmp";
|
||||
import {exportToCSV,exportToExcel,printTable,exportToPDF} from "../../utils/tableExportUtils";
|
||||
import {
|
||||
exportToCSV,
|
||||
exportToExcel,
|
||||
printTable,
|
||||
exportToPDF,
|
||||
} from "../../utils/tableExportUtils";
|
||||
|
||||
const EmployeeList = () => {
|
||||
const { profile: loginUser } = useProfile();
|
||||
@ -105,7 +110,7 @@ const EmployeeList = () => {
|
||||
const tableRef = useRef(null);
|
||||
const handleExport = (type) => {
|
||||
if (!currentItems || currentItems.length === 0) return;
|
||||
|
||||
|
||||
switch (type) {
|
||||
case "csv":
|
||||
exportToCSV(currentItems, "employees");
|
||||
@ -114,17 +119,16 @@ const EmployeeList = () => {
|
||||
exportToExcel(currentItems, "employees");
|
||||
break;
|
||||
case "pdf":
|
||||
exportToPDF(currentItems, "employees"); // Pass the employeeList directly
|
||||
exportToPDF(currentItems, "employees"); // Pass the employeeList directly
|
||||
break;
|
||||
case "print":
|
||||
printTable(tableRef.current);
|
||||
printTable(tableRef.current);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{isCreateModalOpen && (
|
||||
@ -178,11 +182,17 @@ const EmployeeList = () => {
|
||||
<>
|
||||
<option value="">All Employees</option>
|
||||
{Array.isArray(projects) &&
|
||||
projects.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
projects
|
||||
.filter((item) =>
|
||||
loginUser?.projects?.includes(
|
||||
String(item.id)
|
||||
)
|
||||
)
|
||||
.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
@ -221,22 +231,39 @@ const EmployeeList = () => {
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
<li>
|
||||
<a className="dropdown-item" href="#" onClick={() => handleExport("print")}>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
href="#"
|
||||
onClick={() => handleExport("print")}
|
||||
>
|
||||
<i className="bx bx-printer me-1"></i> Print
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="dropdown-item" href="#" onClick={() => handleExport("csv")}>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
href="#"
|
||||
onClick={() => handleExport("csv")}
|
||||
>
|
||||
<i className="bx bx-file me-1"></i> CSV
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="dropdown-item" href="#" onClick={() => handleExport("excel")}>
|
||||
<i className="bx bxs-file-export me-1"></i> Excel
|
||||
<a
|
||||
className="dropdown-item"
|
||||
href="#"
|
||||
onClick={() => handleExport("excel")}
|
||||
>
|
||||
<i className="bx bxs-file-export me-1"></i>{" "}
|
||||
Excel
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="dropdown-item" href="#" onClick={() => handleExport("pdf")}>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
href="#"
|
||||
onClick={() => handleExport("pdf")}
|
||||
>
|
||||
<i className="bx bxs-file-pdf me-1"></i> PDF
|
||||
</a>
|
||||
</li>
|
||||
@ -270,7 +297,7 @@ const EmployeeList = () => {
|
||||
id="DataTables_Table_0"
|
||||
aria-describedby="DataTables_Table_0_info"
|
||||
style={{ width: "100%" }}
|
||||
ref={tableRef}
|
||||
ref={tableRef}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
@ -460,19 +487,19 @@ const EmployeeList = () => {
|
||||
}
|
||||
className="dropdown-item py-1"
|
||||
>
|
||||
<i className="bx bx-detail bx-sm"></i> View
|
||||
<i className="bx bx-detail bx-sm"></i> View
|
||||
</button>
|
||||
<Link
|
||||
to={`/employee/manage/${item.id}`}
|
||||
className="dropdown-item py-1"
|
||||
>
|
||||
<i class='bx bx-edit bx-sm'></i> Edit
|
||||
<i class="bx bx-edit bx-sm"></i> Edit
|
||||
</Link>
|
||||
<button
|
||||
className="dropdown-item py-1"
|
||||
onClick={handleShow}
|
||||
>
|
||||
<i class='bx bx-task-x bx-sm'></i> Suspend
|
||||
<i class="bx bx-task-x bx-sm"></i> Suspend
|
||||
</button>
|
||||
<button
|
||||
className="dropdown-item py-1"
|
||||
@ -481,7 +508,7 @@ const EmployeeList = () => {
|
||||
data-bs-target="#managerole-modal"
|
||||
onClick={() => handleConfigData(item.id)}
|
||||
>
|
||||
<i class='bx bx-cog bx-sm'></i> Manage Role
|
||||
<i class="bx bx-cog bx-sm"></i> Manage Role
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -17,42 +17,39 @@ const ProjectList = () => {
|
||||
const [listView, setListView] = useState(true);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const { projects, loading, error, refetch } = useProjects();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [projectList, setProjectList] = useState([]);
|
||||
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
||||
const [HasManageProject, setHasManageProject] = useState(
|
||||
HasManageProjectPermission
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage] = useState(6);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [selectedStatuses, setSelectedStatuses] = useState([1, 2, 3, 4]);
|
||||
|
||||
const handleShow = () => setShowModal(true);
|
||||
const handleClose = () => setShowModal(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && Array.isArray(projects)) {
|
||||
// Step 1: Group projects by statusId
|
||||
const grouped = {};
|
||||
|
||||
projects.forEach((project) => {
|
||||
const statusId = project.projectStatusId;
|
||||
if (!grouped[statusId]) {
|
||||
grouped[statusId] = [];
|
||||
}
|
||||
if (!grouped[statusId]) grouped[statusId] = [];
|
||||
grouped[statusId].push(project);
|
||||
});
|
||||
|
||||
// Step 2: Sort each group by name
|
||||
const sortedGrouped = Object.keys(grouped)
|
||||
.sort() // sort group keys (status IDs)
|
||||
.sort()
|
||||
.flatMap((statusId) =>
|
||||
grouped[statusId].sort((a, b) =>
|
||||
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
||||
)
|
||||
);
|
||||
|
||||
setProjectList(sortedGrouped); // final sorted flat list
|
||||
setProjectList(sortedGrouped);
|
||||
}
|
||||
}, [projects, loginUser?.projects, loading]);
|
||||
|
||||
@ -67,22 +64,16 @@ const ProjectList = () => {
|
||||
const handleSubmitForm = (newProject) => {
|
||||
ProjectRepository.manageProject(newProject)
|
||||
.then((response) => {
|
||||
const cachedProjects_list = getCachedData("projectslist") || [];
|
||||
|
||||
const updated_Projects_list = [...cachedProjects_list, response.data];
|
||||
|
||||
cacheData("projectslist", updated_Projects_list);
|
||||
setProjectList((prevProjectList) => [
|
||||
...prevProjectList,
|
||||
response.data,
|
||||
]);
|
||||
|
||||
const cachedProjects = getCachedData("projectslist") || [];
|
||||
const updatedProjects = [...cachedProjects, response.data];
|
||||
cacheData("projectslist", updatedProjects);
|
||||
setProjectList((prev) => [...prev, response.data]);
|
||||
showToast("Project Created successfully.", "success");
|
||||
setShowModal(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
closeModal();
|
||||
showToast(error.message, "error");
|
||||
setShowModal(false);
|
||||
});
|
||||
};
|
||||
|
||||
@ -90,19 +81,43 @@ const ProjectList = () => {
|
||||
if (!projects || projects.length === 0) {
|
||||
refetch();
|
||||
}
|
||||
setRefresh((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleStatusChange = (statusId) => {
|
||||
setCurrentPage(1);
|
||||
setSelectedStatuses((prev) =>
|
||||
prev.includes(statusId)
|
||||
? prev.filter((id) => id !== statusId)
|
||||
: [...prev, statusId]
|
||||
);
|
||||
};
|
||||
|
||||
const handleStatusFilterFromChild = (statusesFromChild) => {
|
||||
setSelectedStatuses(statusesFromChild);
|
||||
};
|
||||
|
||||
const filteredProjects = projectList.filter((project) => {
|
||||
const matchesStatus = selectedStatuses.includes(project.projectStatusId);
|
||||
const matchesSearch = project.name
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase());
|
||||
return matchesStatus && matchesSearch;
|
||||
});
|
||||
|
||||
const indexOfLastItem = currentPage * itemsPerPage;
|
||||
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
||||
const currentItems = Array.isArray(projectList)
|
||||
? projectList.slice(indexOfFirstItem, indexOfLastItem)
|
||||
: [];
|
||||
const currentItems = filteredProjects.slice(
|
||||
indexOfFirstItem,
|
||||
indexOfLastItem
|
||||
);
|
||||
const totalPages = Math.ceil(filteredProjects.length / itemsPerPage);
|
||||
|
||||
const paginate = (pageNumber) => setCurrentPage(pageNumber);
|
||||
const totalPages = Array.isArray(projectList)
|
||||
? Math.ceil(projectList.length / itemsPerPage)
|
||||
: 0;
|
||||
useEffect(() => {
|
||||
const tooltipTriggerList = Array.from(
|
||||
document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
);
|
||||
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -117,7 +132,7 @@ const ProjectList = () => {
|
||||
project={null}
|
||||
handleSubmitForm={handleSubmitForm}
|
||||
onClose={handleClose}
|
||||
></ManageProjectInfo>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="container-xxl flex-grow-1 container-p-y">
|
||||
@ -126,55 +141,62 @@ const ProjectList = () => {
|
||||
{ label: "Home", link: "/dashboard" },
|
||||
{ label: "Projects", link: null },
|
||||
]}
|
||||
></Breadcrumb>
|
||||
/>
|
||||
|
||||
<div className="d-flex justify-content-between mb-4">
|
||||
{/* <div
|
||||
className={`col-md-12 col-lg-12 col-xl-12 order-0 mb-4 ${
|
||||
!error && !projects ? "text-center" : "text-end"
|
||||
}`}
|
||||
>
|
||||
|
||||
|
||||
</div> */}
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-start mb-4">
|
||||
<div className="d-flex flex-wrap align-items-start">
|
||||
<div className="flex-grow-1 me-2 mb-2">
|
||||
<input
|
||||
type="search"
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search projects..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
id="DataTables_Table_0_filter"
|
||||
className="dataTables_filter d-flex justify-content-start"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
type="search"
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Search"
|
||||
aria-controls="DataTables_Table_0"
|
||||
></input>
|
||||
</label>
|
||||
<div className="d-flex gap-2 mb-2">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-icon btn-sm ms-2 p-0 ${ listView ? "btn-secondary" : "btn-label-secondary" }`}
|
||||
onClick={()=>setListView(!listView)}
|
||||
className={`btn btn-sm ${
|
||||
listView ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
onClick={() => setListView(true)}
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-offset="0,8"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="tooltip"
|
||||
title="List View"
|
||||
>
|
||||
<span class="bx bx-list-ul"></span>
|
||||
<i className="bx bx-list-ul"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-icon btn-sm ms-2 p-0 ${ listView ? "btn-label-secondary" : "btn-secondary" }`}
|
||||
onClick={()=>setListView(!listView)}
|
||||
className={`btn btn-sm ${
|
||||
!listView ? "btn-primary" : "btn-outline-primary"
|
||||
}`}
|
||||
onClick={() => setListView(false)}
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-offset="0,8"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="tooltip"
|
||||
title="Card View"
|
||||
>
|
||||
<span class="bx bx-grid-alt"></span>
|
||||
<i className="bx bx-grid-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-xs btn-primary ${
|
||||
className={`btn btn-sm btn-primary ${
|
||||
!HasManageProject && "d-none"
|
||||
}`}
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#create-project-model"
|
||||
onClick={handleShow}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
@ -183,71 +205,123 @@ const ProjectList = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{((error && !loading) || !projects) && (
|
||||
<p className="text-center text-body-secondary">
|
||||
There was an error loading the projects. Please try again.
|
||||
</p>
|
||||
{loading && <p className="text-center">Loading...</p>}
|
||||
{!loading && filteredProjects.length === 0 && !listView && (
|
||||
<p className="text-center text-muted">No projects found.</p>
|
||||
)}
|
||||
|
||||
{(!projects || projects.length === 0 || projectList.length == 0) &&
|
||||
!loading &&
|
||||
error && (
|
||||
<div className="text-center">
|
||||
<button
|
||||
className="btn btn-xs btn-label-secondary"
|
||||
onClick={handleReFresh}
|
||||
>
|
||||
Retry Fetching Projects
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="row">
|
||||
{loading && <p className="text-center">Loading...</p>}
|
||||
|
||||
{listView ? <ProjectListView/> :
|
||||
currentItems &&
|
||||
currentItems.map((item) => (
|
||||
<ProjectCard projectData={item} key={item.id}></ProjectCard>
|
||||
))}
|
||||
{listView ? (
|
||||
<div className="table-responsive text-nowrap py-2 ">
|
||||
<table className="table px-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-start" colSpan={5}>
|
||||
Project Name
|
||||
</th>
|
||||
<th className="mx-2">Project Manger</th>
|
||||
<th className="mx-2">START DATE</th>
|
||||
<th className="mx-2">DEADLINE</th>
|
||||
<th className="mx-2">Task</th>
|
||||
<th className="mx-2">Progress</th>
|
||||
<th className="mx-2">
|
||||
<div className="dropdown">
|
||||
<a
|
||||
className="dropdown-toggle hide-arrow cursor-pointer"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Status <i className="bx bx-filter"></i>
|
||||
</a>
|
||||
<ul className="dropdown-menu p-2 text-capitalize">
|
||||
{[
|
||||
{ id: 1, label: "Active" },
|
||||
{ id: 2, label: "On Hold" },
|
||||
{ id: 3, label: "Inactive" },
|
||||
{ id: 4, label: "Completed" },
|
||||
].map(({ id, label }) => (
|
||||
<li key={id}>
|
||||
<div className="form-check">
|
||||
<input
|
||||
className="form-check-input "
|
||||
type="checkbox"
|
||||
checked={selectedStatuses.includes(id)}
|
||||
onChange={() => handleStatusChange(id)}
|
||||
/>
|
||||
<label className="form-check-label">
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
className={`mx-2 ${
|
||||
HasManageProject ? "d-sm-table-cell" : "d-none"
|
||||
}`}
|
||||
>
|
||||
Action
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-border-bottom-0 overflow-auto ">
|
||||
{currentItems.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan="12" className="text-center py-4">
|
||||
No projects found
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
currentItems.map((project) => (
|
||||
<ProjectListView key={project.id} projectData={project} />
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
currentItems.map((project) => (
|
||||
<ProjectCard key={project.id} projectData={project} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
{/* Pagination */}
|
||||
{!loading && (
|
||||
<nav aria-label="Page ">
|
||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||
<li
|
||||
className={`page-item ${currentPage === 1 ? "disabled" : ""}`}
|
||||
>
|
||||
|
||||
{!loading && totalPages > 1 && (
|
||||
<nav>
|
||||
<ul className="pagination pagination-sm justify-content-end py-2">
|
||||
<li className={`page-item ${currentPage === 1 && "disabled"}`}>
|
||||
<button
|
||||
className="page-link btn-xs"
|
||||
onClick={() => paginate(currentPage - 1)}
|
||||
className="page-link"
|
||||
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
||||
>
|
||||
«
|
||||
</button>
|
||||
</li>
|
||||
{[...Array(totalPages)]?.map((_, index) => (
|
||||
{[...Array(totalPages)].map((_, i) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`page-item ${
|
||||
currentPage === index + 1 ? "active" : ""
|
||||
}`}
|
||||
key={i}
|
||||
className={`page-item ${currentPage === i + 1 && "active"}`}
|
||||
>
|
||||
<button
|
||||
className="page-link "
|
||||
onClick={() => paginate(index + 1)}
|
||||
className="page-link"
|
||||
onClick={() => setCurrentPage(i + 1)}
|
||||
>
|
||||
{index + 1}
|
||||
{i + 1}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
<li
|
||||
className={`page-item ${
|
||||
currentPage === totalPages ? "disabled" : ""
|
||||
currentPage === totalPages && "disabled"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="page-link "
|
||||
onClick={() => paginate(currentPage + 1)}
|
||||
className="page-link"
|
||||
onClick={() =>
|
||||
setCurrentPage((p) => Math.min(totalPages, p + 1))
|
||||
}
|
||||
>
|
||||
»
|
||||
</button>
|
||||
|
||||
@ -1,9 +1,38 @@
|
||||
import React from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import moment from "moment";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import {
|
||||
getProjectStatusName,
|
||||
getProjectStatusColor,
|
||||
} from "../../utils/projectStatus";
|
||||
import ProgressBar from "../../components/common/ProgressBar";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import ManageProject from "../../components/Project/ManageProject";
|
||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import { MANAGE_PROJECT } from "../../utils/constants";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
|
||||
import showToast from "../../services/toastService";
|
||||
import { getCachedData, cacheData } from "../../slices/apiDataManager";
|
||||
|
||||
const ProjectListView = () => {
|
||||
const { projects } = useProjects();
|
||||
const ProjectListView = ({ projectData }) => {
|
||||
const [projectInfo, setProjectInfo] = useState(projectData);
|
||||
const [projectDetails, setProjectDetails] = useState(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||
|
||||
const handleShow = async () => {
|
||||
try {
|
||||
const response = await ProjectRepository.getProjectByprojectId(
|
||||
projectInfo.id
|
||||
);
|
||||
setProjectDetails(response.data);
|
||||
setShowModal(true);
|
||||
} catch (error) {
|
||||
showToast("Failed to load project details", "error");
|
||||
}
|
||||
};
|
||||
|
||||
const getProgress = (planned, completed) => {
|
||||
return (completed * 100) / planned + "%";
|
||||
@ -12,184 +41,158 @@ const ProjectListView = () => {
|
||||
return (completed * 100) / planned;
|
||||
};
|
||||
|
||||
const getProjectStatusName = (statusId) => {
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "Active";
|
||||
case 2:
|
||||
return "On Hold";
|
||||
// case 3:
|
||||
// return "Suspended";
|
||||
case 3:
|
||||
return "Inactive";
|
||||
case 4:
|
||||
return "Completed";
|
||||
}
|
||||
const handleClose = () => setShowModal(false);
|
||||
|
||||
|
||||
const handleViewProject = () => {
|
||||
navigate(`/projects/${projectData.id}`);
|
||||
};
|
||||
|
||||
const getProjectStatusColor = (statusId) => {
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "bg-label-success";
|
||||
case 2:
|
||||
return "bg-label-warning";
|
||||
case 3:
|
||||
return "bg-label-info";
|
||||
case 4:
|
||||
return "bg-label-secondary";
|
||||
case 5:
|
||||
return "bg-label-dark";
|
||||
const handleFormSubmit = (updatedProject) => {
|
||||
if (projectInfo?.id) {
|
||||
ProjectRepository.updateProject(projectInfo.id, updatedProject)
|
||||
.then((response) => {
|
||||
const updatedProjectData = {
|
||||
...projectInfo,
|
||||
...response.data,
|
||||
building: projectDetails?.building,
|
||||
};
|
||||
|
||||
setProjectInfo( updatedProjectData );
|
||||
|
||||
if (getCachedData(`projectinfo-${projectInfo.id}`)) {
|
||||
cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
|
||||
}
|
||||
|
||||
const projects_list = getCachedData("projectslist");
|
||||
if (projects_list) {
|
||||
const updatedProjectsList = projects_list.map((project) =>
|
||||
project.id === projectInfo.id
|
||||
? { ...project, ...response.data, tenant: project.tenant }
|
||||
: project
|
||||
);
|
||||
cacheData("projectslist", updatedProjectsList);
|
||||
}
|
||||
showToast("Project updated successfully.", "success");
|
||||
setShowModal(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message, "error");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="table-responsive text-nowrap py-2 overflow-auto">
|
||||
<table className="table px-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-start" colSpan={5}>
|
||||
Project Name
|
||||
</th>
|
||||
<th className="mx-2">Project Manger</th>
|
||||
<th className="mx-2">START DATE</th>
|
||||
<th className="mx-2">DEADLINE</th>
|
||||
<th className="mx-2">Task</th>
|
||||
<th className="mx-2">Progress</th>
|
||||
<th className="mx-2">
|
||||
<div class="dropdown">
|
||||
<>
|
||||
{showModal && projectDetails && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: "block" }}
|
||||
aria-hidden="false"
|
||||
>
|
||||
<ManageProjectInfo
|
||||
project={projectDetails}
|
||||
handleSubmitForm={handleFormSubmit}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<tr className="py-8">
|
||||
<td className="text-start" colSpan={5}>
|
||||
<strong
|
||||
className="text-primary cursor-pointer"
|
||||
onClick={() => navigate(`/projects/${projectInfo.id}`)}
|
||||
>
|
||||
{projectInfo.name}
|
||||
</strong>
|
||||
</td>
|
||||
<td className="text-start small">{projectInfo.contactPerson}</td>
|
||||
<td className="small text-start">
|
||||
<small>
|
||||
{projectInfo.startDate
|
||||
? moment(projectInfo.startDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</small>
|
||||
</td>
|
||||
<td className="mx-2 text-start small">
|
||||
{projectInfo.endDate
|
||||
? moment(projectInfo.endDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</td>
|
||||
<td className="mx-2 text-start small">{projectInfo.plannedWork}</td>
|
||||
<td className="py-6 mx-2 text-start small align-items-center">
|
||||
<ProgressBar
|
||||
plannedWork={projectInfo.plannedWork}
|
||||
completedWork={projectInfo.completedWork}
|
||||
className="mb-0"
|
||||
height="4px"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td className="mx-6">
|
||||
<p className="mb-0">
|
||||
<span
|
||||
className={`badge ${getProjectStatusColor(
|
||||
projectInfo.projectStatusId
|
||||
)}`}
|
||||
>
|
||||
{getProjectStatusName(projectInfo.projectStatusId)}
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td className={`mx-2 ${ManageProject ? "d-sm-table-cell":"d-none"}`}>
|
||||
<div className="dropdown z-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<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>
|
||||
<a
|
||||
class="dropdown-toggle hide-arrow cursor-pointer"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
|
||||
aria-label="click to View details"
|
||||
className="dropdown-item"
|
||||
onClick={() => navigate(`/projects/${projectInfo.id}`)}
|
||||
>
|
||||
Status <i class="bx bx-filter"></i>
|
||||
<i className="bx bx-detail me-2"></i>
|
||||
<span className="align-left">View details</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu px-3 py-2">
|
||||
<li>
|
||||
<div class="form-check form-check-success mb-1">
|
||||
<input
|
||||
name="statusRadio"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
value="active"
|
||||
id="statusActive"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label text-success" for="statusActive">
|
||||
Active
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="form-check form-check-warning mb-1">
|
||||
<input
|
||||
name="statusRadio"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
value="onhold"
|
||||
id="statusOnHold"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label text-warning" for="statusOnHold">
|
||||
On-Hold
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="form-check form-check-info mb-1">
|
||||
<input
|
||||
name="statusRadio"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
value="inactive"
|
||||
id="statusInactive"
|
||||
checked
|
||||
/>
|
||||
<label class="form-check-label text-info" for="statusInactive">
|
||||
Inactive
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="form-check form-check-secondary mb-1">
|
||||
<input
|
||||
name="customRadioSecondary" class="form-check-input" type="radio" value="" id="customRadioSecondary" checked
|
||||
/>
|
||||
<label class="form-check-label text-secondary" for="statusCompleted">
|
||||
Completed
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</th>
|
||||
<th className="mx-2">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-border-bottom-0">
|
||||
{projects.map((project) => (
|
||||
<tr className="py-8">
|
||||
<td className="text-start" colSpan={5}>
|
||||
<strong className="text-primary"> {project.name}</strong>
|
||||
</td>
|
||||
<td className="text-start">{project.contactPerson}</td>
|
||||
<td className="small text-start">
|
||||
<small>
|
||||
{" "}
|
||||
{project.startDate
|
||||
? moment(project.startDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</small>
|
||||
</td>
|
||||
<td className="mx-2 text-start">
|
||||
{project.endDate
|
||||
? moment(project.endDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</td>
|
||||
<td className="mx-2 text-start">{project.plannedWork}</td>
|
||||
</li>
|
||||
|
||||
<td className="py-6 mx-2 text-start">
|
||||
<div
|
||||
className="progress rounded align-items-center"
|
||||
style={{ height: "8px" }}
|
||||
>
|
||||
<div
|
||||
className="progress-bar rounded"
|
||||
role="progressbar"
|
||||
style={{
|
||||
width: getProgress(
|
||||
project.plannedWork,
|
||||
project.completedWork
|
||||
),
|
||||
}}
|
||||
aria-valuenow={project.completedWork}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax={project.plannedWork}
|
||||
></div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="mx-6 ">
|
||||
<p className="mb-0">
|
||||
<span
|
||||
className={
|
||||
`badge ` + getProjectStatusColor(project.projectStatusId)
|
||||
}
|
||||
>
|
||||
{getProjectStatusName(project.projectStatusId)}
|
||||
</span>
|
||||
</p>{" "}
|
||||
</td>
|
||||
|
||||
<td className="mx-2 ">
|
||||
{" "}
|
||||
<i className="bx bx-dots-vertical-rounded bx-sm text-muted"></i>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<li onClick={handleShow}>
|
||||
<a className="dropdown-item">
|
||||
<i className="bx bx-pencil me-2"></i>
|
||||
<span className="align-left">Modify</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
onClick={() =>
|
||||
navigate(`/activities/records?project=${projectInfo.id}`)
|
||||
}
|
||||
>
|
||||
<a className="dropdown-item">
|
||||
<i className="bx bx-task me-2"></i>
|
||||
<span className="align-left">Activities</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ export const axiosClient = axios.create({
|
||||
"Content-Type": "application/json", // Specify the content type
|
||||
},
|
||||
});
|
||||
|
||||
axiosRetry(axiosClient, { retries: 3 });
|
||||
|
||||
// Request interceptor to add Bearer token
|
||||
@ -21,6 +20,10 @@ axiosClient.interceptors.request.use(
|
||||
const token = localStorage.getItem("jwtToken");
|
||||
if (token) {
|
||||
config.headers["Authorization"] = `Bearer ${token}`;
|
||||
config._retry = true;
|
||||
}
|
||||
else{
|
||||
config._retry = false;
|
||||
}
|
||||
}
|
||||
return config;
|
||||
@ -32,7 +35,9 @@ axiosClient.interceptors.request.use(
|
||||
// Add an interceptor to handle expired tokens
|
||||
axiosClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
|
||||
async (error) => {
|
||||
|
||||
const originalRequest = error.config;
|
||||
|
||||
if (!originalRequest) {
|
||||
@ -80,13 +85,12 @@ axiosClient.interceptors.response.use(
|
||||
if (error.config.url.indexOf("refresh-token") != -1) {
|
||||
//showToast("Server is unreachable. Try again later!", "error");
|
||||
console.log("4 - error fetching refresh token :", error);
|
||||
} else {
|
||||
showToast(error.response.data.message, "error");
|
||||
}
|
||||
|
||||
}
|
||||
// else {
|
||||
// // showToast(error.response.data.message, "error"); // repeted toast
|
||||
// }
|
||||
if (error.response.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
try {
|
||||
// Get the refresh token from secure storage
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
@ -129,11 +133,11 @@ axiosClient.interceptors.response.use(
|
||||
//showToast("Server is unreachable. Try again later!", "error");
|
||||
console.log("6 - error fetching refresh token :", error);
|
||||
}
|
||||
showToast(
|
||||
error.response.data?.message ||
|
||||
"An error occurred. Please try again.",
|
||||
"error"
|
||||
);
|
||||
// showToast(
|
||||
// error.response.data?.message ||
|
||||
// "An error occurred. Please try again.",
|
||||
// "error"
|
||||
// );
|
||||
}
|
||||
} else {
|
||||
console.error("An unknown error occurred:", error.message);
|
||||
|
||||
@ -1,25 +1,32 @@
|
||||
export const ProjectStatus =(statusId)=>{
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "Active"
|
||||
break;
|
||||
case 2:
|
||||
return "On Hold"
|
||||
break;
|
||||
// case 3:
|
||||
// return "Suspended"
|
||||
// break;
|
||||
case 3:
|
||||
return "Inactive"
|
||||
break;
|
||||
case 4:
|
||||
return "Completed"
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
export const getProjectStatusName = (statusId) => {
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "Active";
|
||||
case 2:
|
||||
return "On Hold";
|
||||
// case 3:
|
||||
// return "Suspended";
|
||||
case 3:
|
||||
return "Inactive";
|
||||
case 4:
|
||||
return "Completed";
|
||||
}
|
||||
};
|
||||
|
||||
export const getProjectStatusColor = (statusId) => {
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "bg-label-success";
|
||||
case 2:
|
||||
return "bg-label-warning";
|
||||
case 3:
|
||||
return "bg-label-info";
|
||||
case 4:
|
||||
return "bg-label-secondary";
|
||||
case 5:
|
||||
return "bg-label-dark";
|
||||
}
|
||||
};
|
||||
|
||||
// for different color for each user
|
||||
export function hashString(str) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user