Compare commits
No commits in common. "80083aa5dbc8c6f28fc2017689ef2f1075ffa5ef" and "a02a33a24781d0439e4051c8410d852ef4392d0e" have entirely different histories.
80083aa5db
...
a02a33a247
@ -213,8 +213,3 @@
|
|||||||
.ql-editor {
|
.ql-editor {
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove Table Header Top Line */
|
|
||||||
thead tr {
|
|
||||||
border-top: 1px solid white;
|
|
||||||
}
|
|
||||||
|
|||||||
1
public/assets/vendor/css/core.css
vendored
1
public/assets/vendor/css/core.css
vendored
@ -4978,7 +4978,6 @@ fieldset:disabled .btn {
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x);
|
padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x);
|
||||||
color: var(--bs-card-color);
|
color: var(--bs-card-color);
|
||||||
word-break: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
|
|||||||
@ -143,7 +143,7 @@ const Attendance = ({
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{!loading && filteredData.length > 20 && (
|
{!loading > 20 && (
|
||||||
<nav aria-label="Page ">
|
<nav aria-label="Page ">
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
<li
|
<li
|
||||||
|
|||||||
@ -334,11 +334,11 @@ const AttendanceLog = ({
|
|||||||
{!loading && !isRefreshing && data.length === 0 && (
|
{!loading && !isRefreshing && data.length === 0 && (
|
||||||
<span>No employee logs</span>
|
<span>No employee logs</span>
|
||||||
)}
|
)}
|
||||||
{/* {error && !loading && !isRefreshing && (
|
{error && !loading && !isRefreshing && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6}>{error}</td>
|
<td colSpan={6}>{error}</td>
|
||||||
</tr>
|
</tr>
|
||||||
)} */}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!loading && !isRefreshing && processedData.length > 10 && (
|
{!loading && !isRefreshing && processedData.length > 10 && (
|
||||||
<nav aria-label="Page ">
|
<nav aria-label="Page ">
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel";
|
|||||||
import TaskModel from "../Project/Infrastructure/TaskModel";
|
import TaskModel from "../Project/Infrastructure/TaskModel";
|
||||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import {useProjectDetails, useProjectInfra, useProjects} from "../../hooks/useProjects";
|
import {useProjectDetails, useProjects} from "../../hooks/useProjects";
|
||||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
||||||
import {MANAGE_PROJECT_INFRA} from "../../utils/constants";
|
import {MANAGE_PROJECT_INFRA} from "../../utils/constants";
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
@ -21,12 +21,11 @@ const InfraPlanning = () =>
|
|||||||
{
|
{
|
||||||
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
// const {projects,loading:project_listLoader,error:projects_error} = useProjects()
|
||||||
|
|
||||||
const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
||||||
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
|
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
|
||||||
|
const {projects_Details, loading: project_deatilsLoader, error: project_error,refetch} = useProjectDetails( selectedProject )
|
||||||
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
|
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
|
||||||
|
|
||||||
|
|
||||||
@ -46,9 +45,12 @@ const InfraPlanning = () =>
|
|||||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||||
<div className="align-items-center">
|
<div className="align-items-center">
|
||||||
<div className="row ">
|
<div className="row ">
|
||||||
{isLoading && ( <p>Loading...</p> )}
|
{project_deatilsLoader && ( <p>Loading...</p> )}
|
||||||
{( !isLoading && projectInfra?.length === 0 ) && ( <p>No Result Found</p> )}
|
{( !project_deatilsLoader && projects_Details?.buildings.length === 0 ) && ( <p>No Result Found</p> )}
|
||||||
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
|
|
||||||
|
|
||||||
|
|
||||||
|
{(!project_deatilsLoader && projects_Details?.buildings?.length > 0) && (<InfraTable buildings={projects_Details?.buildings} projectId={projects_Details.id}/>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const Regularization = ({ handleRequest }) => {
|
|||||||
|
|
||||||
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
||||||
filteredData,
|
filteredData,
|
||||||
20
|
10
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eventBus.on("regularization", handler);
|
eventBus.on("regularization", handler);
|
||||||
@ -67,8 +67,8 @@ const Regularization = ({ handleRequest }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="table-responsive text-nowrap pb-4"
|
className="table-responsive text-nowrap"
|
||||||
|
style={{ minHeight: "300px" }}
|
||||||
>
|
>
|
||||||
<table className="table mb-0">
|
<table className="table mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
@ -85,11 +85,11 @@ const Regularization = ({ handleRequest }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{/* {loading && (
|
{loading && (
|
||||||
<td colSpan={6} className="text-center py-5">
|
<td colSpan={6} className="text-center py-5">
|
||||||
Loading...
|
Loading...
|
||||||
</td>
|
</td>
|
||||||
)} */}
|
)}
|
||||||
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
(regularizes?.length > 0 ? (
|
(regularizes?.length > 0 ? (
|
||||||
@ -145,9 +145,9 @@ const Regularization = ({ handleRequest }) => {
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{!loading && totalPages > 1 && (
|
{!loading > 10 && (
|
||||||
<nav aria-label="Page ">
|
<nav aria-label="Page ">
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1 mt-3">
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
||||||
<button
|
<button
|
||||||
className="page-link btn-xs"
|
className="page-link btn-xs"
|
||||||
@ -190,4 +190,4 @@ const Regularization = ({ handleRequest }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Regularization;
|
export default Regularization;
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const Dashboard = () => {
|
|||||||
const { tasksCardData } = useDashboardTasksCardData();
|
const { tasksCardData } = useDashboardTasksCardData();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid mt-3">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<div className="row gy-4">
|
<div className="row gy-4">
|
||||||
{/* Projects Card */}
|
{/* Projects Card */}
|
||||||
<div className="col-sm-6 col-lg-4">
|
<div className="col-sm-6 col-lg-4">
|
||||||
|
|||||||
@ -69,7 +69,6 @@ const ProjectProgressChart = () => {
|
|||||||
);
|
);
|
||||||
const lineChartCategoriesDates = sortedDashboardData.map((d) =>
|
const lineChartCategoriesDates = sortedDashboardData.map((d) =>
|
||||||
new Date(d.date).toLocaleDateString("en-US", {
|
new Date(d.date).toLocaleDateString("en-US", {
|
||||||
weekday:"short",
|
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
|
|||||||
@ -88,14 +88,8 @@ const ListViewDirectory = ({
|
|||||||
{contact.organization}
|
{contact.organization}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* <td className="px-2" style={{ width: "10%" }}>
|
|
||||||
<span className="badge badge-outline-secondary">
|
|
||||||
{contact?.contactCategory?.name || "Other"}
|
|
||||||
</span>
|
|
||||||
</td> */}
|
|
||||||
|
|
||||||
<td className="px-2" style={{ width: "10%" }}>
|
<td className="px-2" style={{ width: "10%" }}>
|
||||||
<span className="text-truncate">
|
<span className="badge badge-outline-secondary">
|
||||||
{contact?.contactCategory?.name || "Other"}
|
{contact?.contactCategory?.name || "Other"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -1,256 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import ReactQuill from "react-quill";
|
|
||||||
import moment from "moment";
|
|
||||||
import Avatar from "../common/Avatar";
|
|
||||||
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
|
||||||
import showToast from "../../services/toastService";
|
|
||||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
|
||||||
import ConfirmModal from "../common/ConfirmModal"; // Make sure path is correct
|
|
||||||
import "../common/TextEditor/Editor.css";
|
|
||||||
import ProfileContactDirectory from "./ProfileContactDirectory";
|
|
||||||
import GlobalModel from "../common/GlobalModel";
|
|
||||||
|
|
||||||
const NoteCardDirectoryEditable = ({
|
|
||||||
noteItem,
|
|
||||||
contactId,
|
|
||||||
onNoteUpdate,
|
|
||||||
onNoteDelete,
|
|
||||||
}) => {
|
|
||||||
const [editing, setEditing] = useState(false);
|
|
||||||
const [editorValue, setEditorValue] = useState(noteItem.note);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
|
||||||
const [isRestoring, setIsRestoring] = useState(false);
|
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
||||||
const [open_contact, setOpen_contact] = useState(null);
|
|
||||||
const [isOpenModalNote, setIsOpenModalNote] = useState(false);
|
|
||||||
|
|
||||||
const handleUpdateNote = async () => {
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
const payload = {
|
|
||||||
id: noteItem.id,
|
|
||||||
note: editorValue,
|
|
||||||
contactId,
|
|
||||||
};
|
|
||||||
const response = await DirectoryRepository.UpdateNote(noteItem.id, payload);
|
|
||||||
|
|
||||||
const cachedContactProfile = getCachedData("Contact Profile");
|
|
||||||
if (cachedContactProfile?.contactId === contactId) {
|
|
||||||
const updatedCache = {
|
|
||||||
...cachedContactProfile,
|
|
||||||
data: {
|
|
||||||
...cachedContactProfile.data,
|
|
||||||
notes: cachedContactProfile.data.notes.map((note) =>
|
|
||||||
note.id === noteItem.id ? response.data : note
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
cacheData("Contact Profile", updatedCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
onNoteUpdate?.(response.data);
|
|
||||||
setEditing(false);
|
|
||||||
showToast("Note updated successfully", "success");
|
|
||||||
} catch (error) {
|
|
||||||
showToast("Failed to update note", "error");
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const suspendEmployee = async () => {
|
|
||||||
try {
|
|
||||||
setIsDeleting(true);
|
|
||||||
await DirectoryRepository.DeleteNote(noteItem.id, false);
|
|
||||||
onNoteDelete?.(noteItem.id);
|
|
||||||
setIsDeleteModalOpen(false);
|
|
||||||
showToast("Note deleted successfully", "success");
|
|
||||||
} catch (error) {
|
|
||||||
showToast("Failed to delete note", "error");
|
|
||||||
} finally {
|
|
||||||
setIsDeleting(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const contactProfile = (contactId) => {
|
|
||||||
DirectoryRepository.GetContactProfile(contactId).then((res) => {
|
|
||||||
setOpen_contact(res?.data);
|
|
||||||
setIsOpenModalNote(true);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRestore = async () => {
|
|
||||||
try {
|
|
||||||
setIsRestoring(true);
|
|
||||||
await DirectoryRepository.DeleteNote(noteItem.id, true);
|
|
||||||
onNoteDelete?.(noteItem.id);
|
|
||||||
showToast("Note restored successfully", "success");
|
|
||||||
} catch (error) {
|
|
||||||
showToast("Failed to restore note", "error");
|
|
||||||
} finally {
|
|
||||||
setIsRestoring(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
{isOpenModalNote && (
|
|
||||||
<GlobalModel
|
|
||||||
isOpen={isOpenModalNote}
|
|
||||||
closeModal={() => {
|
|
||||||
setOpen_contact(null);
|
|
||||||
setIsOpenModalNote(false);
|
|
||||||
}}
|
|
||||||
size="xl"
|
|
||||||
>
|
|
||||||
{open_contact && (
|
|
||||||
<ProfileContactDirectory
|
|
||||||
contact={open_contact}
|
|
||||||
setOpen_contact={setOpen_contact}
|
|
||||||
closeModal={() => setIsOpenModalNote(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</GlobalModel>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className="card shadow-sm border-1 mb-3 p-4 rounded"
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
background: noteItem.isActive ? "#fff" : "#f8f6f6",
|
|
||||||
}}
|
|
||||||
key={noteItem.id}
|
|
||||||
>
|
|
||||||
{/* Header */}
|
|
||||||
<div className="d-flex justify-content-between align-items-center mb-1">
|
|
||||||
<div className="d-flex align-items-center">
|
|
||||||
|
|
||||||
<Avatar
|
|
||||||
size="xxs"
|
|
||||||
firstName={noteItem?.createdBy?.firstName}
|
|
||||||
lastName={noteItem?.createdBy?.lastName}
|
|
||||||
className="m-0"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<div className="d-flex ms-0 align-middle cursor-pointer" onClick={() =>contactProfile(noteItem.contactId)}>
|
|
||||||
<span>
|
|
||||||
<span className="fw-bold "> {noteItem?.contactName} </span> <span className="text-muted font-weight-normal">
|
|
||||||
({noteItem?.organizationName})
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="d-flex ms-0 align-middle">
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="d-flex ms-0 mt-2">
|
|
||||||
<span className="text-muted">
|
|
||||||
by <span className="fw-bold "> {noteItem?.createdBy?.firstName} {noteItem?.createdBy?.lastName} </span>
|
|
||||||
<span className="text-muted">
|
|
||||||
on {moment
|
|
||||||
.utc(noteItem?.createdAt)
|
|
||||||
.add(5, "hours")
|
|
||||||
.add(30, "minutes")
|
|
||||||
.format("MMMM DD, YYYY [at] hh:mm A")}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Action Icons */}
|
|
||||||
<div>
|
|
||||||
{noteItem.isActive ? (
|
|
||||||
<>
|
|
||||||
<i
|
|
||||||
className="bx bxs-edit bx-sm me-2 text-primary cursor-pointer"
|
|
||||||
onClick={() => setEditing(true)}
|
|
||||||
title="Edit"
|
|
||||||
></i>
|
|
||||||
{!isDeleting ? (
|
|
||||||
<i
|
|
||||||
className="bx bx-trash bx-sm me-2 text-danger cursor-pointer"
|
|
||||||
onClick={() => setIsDeleteModalOpen(true)}
|
|
||||||
title="Delete"
|
|
||||||
></i>
|
|
||||||
) : (
|
|
||||||
<div className="spinner-border spinner-border-sm text-danger" />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : isRestoring ? (
|
|
||||||
<i className="bx bx-loader-alt bx-spin text-primary"></i>
|
|
||||||
) : (
|
|
||||||
<i
|
|
||||||
className="bx bx-recycle me-2 text-success cursor-pointer"
|
|
||||||
onClick={handleRestore}
|
|
||||||
title="Restore"
|
|
||||||
></i>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr className="mt-0 mb-2" />
|
|
||||||
|
|
||||||
{/* Editor or Content */}
|
|
||||||
{editing ? (
|
|
||||||
<>
|
|
||||||
<ReactQuill
|
|
||||||
value={editorValue}
|
|
||||||
onChange={setEditorValue}
|
|
||||||
theme="snow"
|
|
||||||
className="compact-editor"
|
|
||||||
/>
|
|
||||||
<div className="d-flex justify-content-end gap-3 mt-2">
|
|
||||||
<span
|
|
||||||
className="text-secondary cursor-pointer"
|
|
||||||
onClick={() => setEditing(false)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="text-primary cursor-pointer"
|
|
||||||
onClick={handleUpdateNote}
|
|
||||||
>
|
|
||||||
{isLoading ? "Saving..." : "Submit"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className="mx-4 px-10 text-start"
|
|
||||||
dangerouslySetInnerHTML={{ __html: noteItem.note }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Delete Confirm Modal */}
|
|
||||||
{isDeleteModalOpen && (
|
|
||||||
<div
|
|
||||||
className={`modal fade ${isDeleteModalOpen ? "show" : ""}`}
|
|
||||||
tabIndex="-1"
|
|
||||||
role="dialog"
|
|
||||||
style={{
|
|
||||||
display: isDeleteModalOpen ? "block" : "none",
|
|
||||||
backgroundColor: "rgba(0,0,0,0.5)",
|
|
||||||
}}
|
|
||||||
aria-hidden="false"
|
|
||||||
>
|
|
||||||
<ConfirmModal
|
|
||||||
type={"delete"}
|
|
||||||
header={"Delete Note"}
|
|
||||||
message={"Are you sure you want to delete this note?"}
|
|
||||||
onSubmit={suspendEmployee}
|
|
||||||
onClose={() => setIsDeleteModalOpen(false)}
|
|
||||||
loading={isDeleting}
|
|
||||||
paramData={noteItem}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NoteCardDirectoryEditable;
|
|
||||||
@ -1,176 +0,0 @@
|
|||||||
import React, { useEffect, useState, useMemo } from "react";
|
|
||||||
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
|
||||||
import NoteCardDirectoryEditable from "./NoteCardDirectoryEditable";
|
|
||||||
|
|
||||||
const NotesCardViewDirectory = ({ notes, setNotesForFilter, searchText, filterAppliedNotes }) => {
|
|
||||||
const [allNotes, setAllNotes] = useState([]);
|
|
||||||
const [filteredNotes, setFilteredNotes] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
|
||||||
const [selectedCreators, setSelectedCreators] = useState([]);
|
|
||||||
const [selectedOrgs, setSelectedOrgs] = useState([]);
|
|
||||||
const pageSize = 20;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchNotes();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const fetchNotes = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const response = await DirectoryRepository.GetNotes(1000, 1);
|
|
||||||
const fetchedNotes = response.data?.data || [];
|
|
||||||
setAllNotes(fetchedNotes);
|
|
||||||
setNotesForFilter(fetchedNotes)
|
|
||||||
|
|
||||||
const creatorsSet = new Set();
|
|
||||||
const orgsSet = new Set();
|
|
||||||
|
|
||||||
fetchedNotes.forEach((note) => {
|
|
||||||
const creator = `${note.createdBy?.firstName || ""} ${note.createdBy?.lastName || ""}`.trim();
|
|
||||||
if (creator) creatorsSet.add(creator);
|
|
||||||
|
|
||||||
const org = note.organizationName;
|
|
||||||
if (org) orgsSet.add(org);
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to fetch notes:", error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const applyCombinedFilter = () => {
|
|
||||||
const lowerSearch = searchText?.toLowerCase() || "";
|
|
||||||
|
|
||||||
const filtered = allNotes.filter((noteItem) => {
|
|
||||||
const creator = `${noteItem.createdBy?.firstName || ""} ${noteItem.createdBy?.lastName || ""}`.trim();
|
|
||||||
const org = noteItem.organizationName;
|
|
||||||
|
|
||||||
const matchesCreator = selectedCreators.length === 0 || selectedCreators.includes(creator);
|
|
||||||
const matchesOrg = selectedOrgs.length === 0 || selectedOrgs.includes(org);
|
|
||||||
|
|
||||||
const plainNote = noteItem?.note?.replace(/<[^>]+>/g, "").toLowerCase();
|
|
||||||
|
|
||||||
const stringValues = [];
|
|
||||||
const extractStrings = (obj) => {
|
|
||||||
for (const key in obj) {
|
|
||||||
const value = obj[key];
|
|
||||||
if (typeof value === "string") {
|
|
||||||
stringValues.push(value.toLowerCase());
|
|
||||||
} else if (typeof value === "object" && value !== null) {
|
|
||||||
extractStrings(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
extractStrings(noteItem);
|
|
||||||
stringValues.push(plainNote, creator.toLowerCase());
|
|
||||||
|
|
||||||
const matchesSearch = stringValues.some((val) => val.includes(lowerSearch));
|
|
||||||
|
|
||||||
return matchesCreator && matchesOrg && matchesSearch;
|
|
||||||
});
|
|
||||||
|
|
||||||
setFilteredNotes(filtered);
|
|
||||||
setCurrentPage(1);
|
|
||||||
setTotalPages(Math.ceil(filtered.length / pageSize));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
applyCombinedFilter();
|
|
||||||
}, [searchText, allNotes]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFilteredNotes(filterAppliedNotes);
|
|
||||||
}, [filterAppliedNotes])
|
|
||||||
|
|
||||||
const currentItems = useMemo(() => {
|
|
||||||
const startIndex = (currentPage - 1) * pageSize;
|
|
||||||
return filteredNotes.slice(startIndex, startIndex + pageSize);
|
|
||||||
}, [filteredNotes, currentPage]);
|
|
||||||
|
|
||||||
const handlePageClick = (page) => {
|
|
||||||
if (page !== currentPage) {
|
|
||||||
setCurrentPage(page);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) return <p className="mt-10 text-center">Loading notes...</p>;
|
|
||||||
|
|
||||||
if (!filteredNotes.length) return <p className="mt-10 text-center">No matching notes found</p>;
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-100 h-100 ">
|
|
||||||
{/* Filter Dropdown */}
|
|
||||||
<div className="dropdown mb-3 ms-2">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Notes List */}
|
|
||||||
<div className="d-flex flex-column text-start" style={{ gap: "0rem", minHeight: "100%" }}>
|
|
||||||
{currentItems.map((noteItem) => (
|
|
||||||
<NoteCardDirectoryEditable
|
|
||||||
key={noteItem.id}
|
|
||||||
noteItem={noteItem}
|
|
||||||
contactId={noteItem.contactId}
|
|
||||||
onNoteUpdate={(updatedNote) => {
|
|
||||||
setAllNotes((prevNotes) =>
|
|
||||||
prevNotes.map((n) => (n.id === updatedNote.id ? updatedNote : n))
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onNoteDelete={() => fetchNotes()}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Pagination */}
|
|
||||||
{totalPages > 1 && (
|
|
||||||
<div className="d-flex justify-content-end mt-2 align-items-center gap-2"
|
|
||||||
style={{ marginBottom: '70px' }}>
|
|
||||||
{/* Previous Button */}
|
|
||||||
<button
|
|
||||||
className="btn btn-sm rounded-circle border text-secondary"
|
|
||||||
onClick={() => handlePageClick(Math.max(1, currentPage - 1))}
|
|
||||||
disabled={currentPage === 1}
|
|
||||||
title="Previous"
|
|
||||||
style={{ width: "30px", height: "30px", padding: 0, fontSize: "0.75rem" }} // Adjusted width, height, and font size
|
|
||||||
>
|
|
||||||
«
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Page Number Buttons */}
|
|
||||||
{[...Array(totalPages)].map((_, i) => {
|
|
||||||
const page = i + 1;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={page}
|
|
||||||
className={`btn rounded-circle border ${page === currentPage ? "btn-primary text-white" : "btn-light text-secondary"}`}
|
|
||||||
style={{ width: "30px", height: "30px", padding: 0, fontSize: "0.75rem", lineHeight: "1" }} // Adjusted width, height, and font size
|
|
||||||
onClick={() => handlePageClick(page)}
|
|
||||||
>
|
|
||||||
{page}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{/* Next Button */}
|
|
||||||
<button
|
|
||||||
className="btn btn-sm rounded-circle border text-secondary"
|
|
||||||
onClick={() => handlePageClick(Math.min(totalPages, currentPage + 1))}
|
|
||||||
disabled={currentPage === totalPages}
|
|
||||||
title="Next"
|
|
||||||
style={{ width: "30px", height: "30px", padding: 0, fontSize: "0.75rem" }} // Adjusted width, height, and font size
|
|
||||||
>
|
|
||||||
»
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NotesCardViewDirectory;
|
|
||||||
@ -135,7 +135,7 @@ const NotesDirectory = ({
|
|||||||
|
|
||||||
<div className="d-flex justify-content-end">
|
<div className="d-flex justify-content-end">
|
||||||
<span
|
<span
|
||||||
className={`btn btn-sm ${addNote ? "btn-secondary" : "btn-primary"}`}
|
className={`btn btn-sm ${addNote ? "btn-danger" : "btn-primary"}`}
|
||||||
onClick={() => setAddNote(!addNote)}
|
onClick={() => setAddNote(!addNote)}
|
||||||
>
|
>
|
||||||
{addNote ? "Hide Editor" : "Add a Note"}
|
{addNote ? "Hide Editor" : "Add a Note"}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React from "react";
|
|||||||
const DemoTable = () => {
|
const DemoTable = () => {
|
||||||
return (
|
return (
|
||||||
<div className="content-wrapper">
|
<div className="content-wrapper">
|
||||||
<div className="container-fluid">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-datatable table-responsive">
|
<div className="card-datatable table-responsive">
|
||||||
<table className="datatables-basic table border-top">
|
<table className="datatables-basic table border-top">
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const mobileNumberRegex = /^[0-9]\d{9}$/;
|
|||||||
|
|
||||||
const ManageEmployee = ({ employeeId, onClosed,IsAllEmployee }) => {
|
const ManageEmployee = ({ employeeId, onClosed,IsAllEmployee }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
const { mutate: updateEmployee, isLoading } = useUpdateEmployee();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
employee,
|
employee,
|
||||||
@ -163,6 +163,40 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
});
|
});
|
||||||
|
|
||||||
const AadharNumberValue = watch("aadharNumber") || "";
|
const AadharNumberValue = watch("aadharNumber") || "";
|
||||||
|
|
||||||
|
// const onSubmit = (data) => {
|
||||||
|
// setLoading(true);
|
||||||
|
// if (data.email == "") {
|
||||||
|
// data.email = null;
|
||||||
|
// }
|
||||||
|
// EmployeeRepository.manageEmployee(data)
|
||||||
|
// .then((response) => {
|
||||||
|
// cacheData("employeeProfileInfo", data);
|
||||||
|
// showToast(
|
||||||
|
// `Employee details ${
|
||||||
|
// data.id == null ? "created" : "updated"
|
||||||
|
// } successfully.`,
|
||||||
|
// "success"
|
||||||
|
// );
|
||||||
|
// clearCacheKey("employeeListByProject");
|
||||||
|
// clearCacheKey("allEmployeeList");
|
||||||
|
// clearCacheKey("allInactiveEmployeeList");
|
||||||
|
// clearCacheKey("employeeProfile");
|
||||||
|
|
||||||
|
// setLoading(false);
|
||||||
|
// reset();
|
||||||
|
// // navigation("/employees");
|
||||||
|
// onClosed();
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// const message =
|
||||||
|
// error?.response?.data?.message ||
|
||||||
|
// error?.message ||
|
||||||
|
// "Error occured during api calling";
|
||||||
|
// showToast(message, "error");
|
||||||
|
// setLoading(false);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
const onSubmit = (data) => {
|
const onSubmit = (data) => {
|
||||||
if (data.email === "") {
|
if (data.email === "") {
|
||||||
@ -188,25 +222,25 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
reset(
|
reset(
|
||||||
currentEmployee
|
currentEmployee
|
||||||
? {
|
? {
|
||||||
id: currentEmployee.id || null,
|
id: currentEmployee.id || null,
|
||||||
firstName: currentEmployee.firstName || "",
|
firstName: currentEmployee.firstName || "",
|
||||||
middleName: currentEmployee.middleName || "",
|
middleName: currentEmployee.middleName || "",
|
||||||
lastName: currentEmployee.lastName || "",
|
lastName: currentEmployee.lastName || "",
|
||||||
email: currentEmployee.email || "",
|
email: currentEmployee.email || "",
|
||||||
currentAddress: currentEmployee.currentAddress || "",
|
currentAddress: currentEmployee.currentAddress || "",
|
||||||
birthDate: formatDate(currentEmployee.birthDate) || "",
|
birthDate: formatDate(currentEmployee.birthDate) || "",
|
||||||
joiningDate: formatDate(currentEmployee.joiningDate) || "",
|
joiningDate: formatDate(currentEmployee.joiningDate) || "",
|
||||||
emergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "",
|
emergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "",
|
||||||
emergencyContactPerson:
|
emergencyContactPerson:
|
||||||
currentEmployee.emergencyContactPerson || "",
|
currentEmployee.emergencyContactPerson || "",
|
||||||
aadharNumber: currentEmployee.aadharNumber || "",
|
aadharNumber: currentEmployee.aadharNumber || "",
|
||||||
gender: currentEmployee.gender || "",
|
gender: currentEmployee.gender || "",
|
||||||
panNumber: currentEmployee.panNumber || "",
|
panNumber: currentEmployee.panNumber || "",
|
||||||
permanentAddress: currentEmployee.permanentAddress || "",
|
permanentAddress: currentEmployee.permanentAddress || "",
|
||||||
phoneNumber: currentEmployee.phoneNumber || "",
|
phoneNumber: currentEmployee.phoneNumber || "",
|
||||||
jobRoleId: currentEmployee.jobRoleId?.toString() || "",
|
jobRoleId: currentEmployee.jobRoleId?.toString() || "",
|
||||||
}
|
}
|
||||||
: {}
|
: {} // Empty object resets the form
|
||||||
);
|
);
|
||||||
setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0);
|
setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0);
|
||||||
setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0);
|
setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0);
|
||||||
@ -240,378 +274,378 @@ const { mutate: updateEmployee, isPending } = useUpdateEmployee();
|
|||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Middle Name</div>
|
<div className="form-text text-start">Middle Name</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
{...register("middleName")}
|
{...register("middleName")}
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
id="middleName"
|
id="middleName"
|
||||||
placeholder="Middle Name"
|
placeholder="Middle Name"
|
||||||
/>
|
/>
|
||||||
{errors.middleName && (
|
{errors.middleName && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start "
|
className="danger-text text-start "
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.middleName.message}
|
{errors.middleName.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Last Name</div>
|
<div className="form-text text-start">Last Name</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
{...register("lastName")}
|
{...register("lastName")}
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
id="lastName"
|
id="lastName"
|
||||||
placeholder="Last Name"
|
placeholder="Last Name"
|
||||||
/>
|
/>
|
||||||
{errors.lastName && (
|
{errors.lastName && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.lastName.message}
|
{errors.lastName.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<div className="form-text text-start">Email</div>
|
<div className="form-text text-start">Email</div>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
id="email"
|
id="email"
|
||||||
{...register("email")}
|
{...register("email")}
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
placeholder="example@domain.com"
|
placeholder="example@domain.com"
|
||||||
maxLength={80}
|
maxLength={80}
|
||||||
aria-describedby="Email"
|
aria-describedby="Email"
|
||||||
disabled={!!currentEmployee?.email}
|
disabled={!!currentEmployee?.email}
|
||||||
/>
|
/>
|
||||||
{errors.email && (
|
{errors.email && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.email.message}
|
{errors.email.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<div className="form-text text-start">Phone Number</div>
|
<div className="form-text text-start">Phone Number</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
id="phoneNumber"
|
id="phoneNumber"
|
||||||
{...register("phoneNumber")}
|
{...register("phoneNumber")}
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
placeholder="Phone Number"
|
placeholder="Phone Number"
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
maxLength={10}
|
maxLength={10}
|
||||||
/>
|
/>
|
||||||
{errors.phoneNumber && (
|
{errors.phoneNumber && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.phoneNumber.message}
|
{errors.phoneNumber.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row mb-3"></div>
|
<div className="row mb-3"></div>
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Gender</div>
|
<div className="form-text text-start">Gender</div>
|
||||||
|
|
||||||
<div className="input-group input-group-merge ">
|
<div className="input-group input-group-merge ">
|
||||||
<select
|
<select
|
||||||
className="form-select form-select-sm "
|
className="form-select form-select-sm "
|
||||||
{...register("gender")}
|
{...register("gender")}
|
||||||
id="gender"
|
id="gender"
|
||||||
aria-label=""
|
aria-label=""
|
||||||
>
|
>
|
||||||
<option disabled value="">
|
<option disabled value="">
|
||||||
Select Gender
|
Select Gender
|
||||||
</option>
|
</option>
|
||||||
<option value="Male">Male </option>
|
<option value="Male">Male </option>
|
||||||
<option value="Female">Female</option>
|
<option value="Female">Female</option>
|
||||||
<option value="Other">Other</option>
|
<option value="Other">Other</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{errors.gender && (
|
{errors.gender && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.gender.message}
|
{errors.gender.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Birth Date</div>
|
<div className="form-text text-start">Birth Date</div>
|
||||||
|
|
||||||
<div className="input-group input-group-merge ">
|
<div className="input-group input-group-merge ">
|
||||||
<input
|
<input
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
type="date"
|
type="date"
|
||||||
{...register("birthDate")}
|
{...register("birthDate")}
|
||||||
id="birthDate"
|
id="birthDate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.birthDate && (
|
{errors.birthDate && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.birthDate.message}
|
{errors.birthDate.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Joining Date</div>
|
<div className="form-text text-start">Joining Date</div>
|
||||||
|
|
||||||
<div className="input-group input-group-merge ">
|
<div className="input-group input-group-merge ">
|
||||||
<input
|
<input
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
type="date"
|
type="date"
|
||||||
{...register("joiningDate")}
|
{...register("joiningDate")}
|
||||||
id="joiningDate"
|
id="joiningDate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.joiningDate && (
|
{errors.joiningDate && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.joiningDate.message}
|
{errors.joiningDate.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<div className="form-text text-start">Current Address</div>
|
<div className="form-text text-start">Current Address</div>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
id="currentAddress"
|
id="currentAddress"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
placeholder="Current Address"
|
placeholder="Current Address"
|
||||||
aria-label="Current Address"
|
aria-label="Current Address"
|
||||||
aria-describedby="basic-icon-default-message2"
|
aria-describedby="basic-icon-default-message2"
|
||||||
{...register("currentAddress")}
|
{...register("currentAddress")}
|
||||||
maxLength={500}
|
maxLength={500}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setCurrentAddressLength(e.target.value.length);
|
setCurrentAddressLength(e.target.value.length);
|
||||||
// let react-hook-form still handle it
|
// let react-hook-form still handle it
|
||||||
register("currentAddress").onChange(e);
|
register("currentAddress").onChange(e);
|
||||||
}}
|
}}
|
||||||
></textarea>
|
></textarea>
|
||||||
<div className="text-end muted">
|
<div className="text-end muted">
|
||||||
<small>
|
<small>
|
||||||
{" "}
|
{" "}
|
||||||
{500 - currentAddressLength} characters left
|
{500 - currentAddressLength} characters left
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
{errors.currentAddress && (
|
{errors.currentAddress && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.currentAddress.message}
|
{errors.currentAddress.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<div className="form-text text-start">
|
<div className="form-text text-start">
|
||||||
Permanent Address
|
Permanent Address
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
id="permanentAddress"
|
id="permanentAddress"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
placeholder="Permanent Address"
|
placeholder="Permanent Address"
|
||||||
aria-label="Permanent Address"
|
aria-label="Permanent Address"
|
||||||
aria-describedby="basic-icon-default-message2"
|
aria-describedby="basic-icon-default-message2"
|
||||||
{...register("permanentAddress")}
|
{...register("permanentAddress")}
|
||||||
maxLength={500}
|
maxLength={500}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPermanentAddressLength(e.target.value.length);
|
setPermanentAddressLength(e.target.value.length);
|
||||||
register("permanentAddress").onChange(e);
|
register("permanentAddress").onChange(e);
|
||||||
}}
|
}}
|
||||||
></textarea>
|
></textarea>
|
||||||
<div className="text-end muted">
|
<div className="text-end muted">
|
||||||
<small>
|
<small>
|
||||||
{500 - permanentAddressLength} characters left
|
{500 - permanentAddressLength} characters left
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
{errors.permanentAddress && (
|
{errors.permanentAddress && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.permanentAddress.message}
|
{errors.permanentAddress.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
{" "}
|
{" "}
|
||||||
<div className="divider">
|
<div className="divider">
|
||||||
<div className="divider-text">Other Information</div>
|
<div className="divider-text">Other Information</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">Role</div>
|
<div className="form-text text-start">Role</div>
|
||||||
<div className="input-group input-group-merge ">
|
<div className="input-group input-group-merge ">
|
||||||
<select
|
<select
|
||||||
className="form-select form-select-sm"
|
className="form-select form-select-sm"
|
||||||
{...register("jobRoleId")}
|
{...register("jobRoleId")}
|
||||||
id="jobRoleId"
|
id="jobRoleId"
|
||||||
aria-label=""
|
aria-label=""
|
||||||
>
|
>
|
||||||
<option disabled value="">
|
<option disabled value="">
|
||||||
Select Role
|
Select Role
|
||||||
</option>
|
</option>
|
||||||
{[...job_role]
|
{[...job_role]
|
||||||
.sort((a, b) => a?.name?.localeCompare(b.name))
|
.sort((a, b) => a?.name?.localeCompare(b.name))
|
||||||
.map((item) => (
|
.map((item) => (
|
||||||
<option value={item?.id} key={item.id}>
|
<option value={item?.id} key={item.id}>
|
||||||
{item?.name}{" "}
|
{item?.name}{" "}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{errors.jobRoleId && (
|
{errors.jobRoleId && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.jobRoleId.message}
|
{errors.jobRoleId.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">
|
<div className="form-text text-start">
|
||||||
Emergency Contact Person
|
Emergency Contact Person
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
{...register("emergencyContactPerson")}
|
{...register("emergencyContactPerson")}
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
id="emergencyContactPerson"
|
id="emergencyContactPerson"
|
||||||
maxLength={50}
|
maxLength={50}
|
||||||
placeholder="Contact Person"
|
placeholder="Contact Person"
|
||||||
/>
|
/>
|
||||||
{errors.emergencyContactPerson && (
|
{errors.emergencyContactPerson && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.emergencyContactPerson.message}
|
{errors.emergencyContactPerson.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-4">
|
<div className="col-sm-4">
|
||||||
<div className="form-text text-start">
|
<div className="form-text text-start">
|
||||||
Emergency Phone Number
|
Emergency Phone Number
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
{...register("emergencyPhoneNumber")}
|
{...register("emergencyPhoneNumber")}
|
||||||
className="form-control form-control-sm phone-mask"
|
className="form-control form-control-sm phone-mask"
|
||||||
id="emergencyPhoneNumber"
|
id="emergencyPhoneNumber"
|
||||||
placeholder="Phone Number"
|
placeholder="Phone Number"
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
maxLength={10}
|
maxLength={10}
|
||||||
/>
|
/>
|
||||||
{errors.emergencyPhoneNumber && (
|
{errors.emergencyPhoneNumber && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.emergencyPhoneNumber.message}
|
{errors.emergencyPhoneNumber.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row mb-3 d-none">
|
<div className="row mb-3 d-none">
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<div className="form-text text-start">AADHAR Number</div>
|
<div className="form-text text-start">AADHAR Number</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
{...register("aadharNumber")}
|
{...register("aadharNumber")}
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
id="aadharNumber"
|
id="aadharNumber"
|
||||||
placeholder="AADHAR Number"
|
placeholder="AADHAR Number"
|
||||||
maxLength={12}
|
maxLength={12}
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
/>
|
/>
|
||||||
{errors.aadharNumber && (
|
{errors.aadharNumber && (
|
||||||
<div className="danger-text text-start">
|
<div className="danger-text text-start">
|
||||||
{errors.aadharNumber.message}
|
{errors.aadharNumber.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6 d-none">
|
<div className="col-sm-6 d-none">
|
||||||
<div className="form-text text-start">PAN Number</div>
|
<div className="form-text text-start">PAN Number</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
{...register("panNumber")}
|
{...register("panNumber")}
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
id="panNumber"
|
id="panNumber"
|
||||||
placeholder="PAN Number"
|
placeholder="PAN Number"
|
||||||
maxLength={10}
|
maxLength={10}
|
||||||
/>
|
/>
|
||||||
{errors.panNumber && (
|
{errors.panNumber && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.panNumber.message}
|
{errors.panNumber.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{employeeId && (
|
{employeeId && (
|
||||||
<div className="row mb-3 d-none">
|
<div className="row mb-3 d-none">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
<input type="text" name="id" {...register("id")} />
|
<input type="text" name="id" {...register("id")} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="row justify-content-start">
|
<div className="row justify-content-start">
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
<button
|
<button
|
||||||
aria-label="manage employee"
|
aria-label="manage employee"
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
disabled={isPending}
|
disabled={isloading}
|
||||||
>
|
>
|
||||||
{isPending
|
{isloading
|
||||||
? "Please Wait..."
|
? "Please Wait..."
|
||||||
: employeeId
|
: employeeId
|
||||||
? "Update"
|
? "Update"
|
||||||
: "Create"}
|
: "Create"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
|
||||||
aria-label="manage employee"
|
|
||||||
type="reset"
|
|
||||||
className="btn btn-sm btn-primary ms-2"
|
|
||||||
disabled={isPending}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
<button
|
||||||
|
aria-label="manage employee"
|
||||||
|
type="reset"
|
||||||
|
className="btn btn-sm btn-primary ms-2"
|
||||||
|
disabled={isloading}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import { MANAGE_PROJECT } from "../../utils/constants";
|
|||||||
const Header = () => {
|
const Header = () => {
|
||||||
const {profile} = useProfile();
|
const {profile} = useProfile();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch(changeMaster("Job Role"));
|
||||||
const { data, loading } = useMaster();
|
const { data, loading } = useMaster();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
||||||
@ -103,8 +103,7 @@ const Header = () => {
|
|||||||
}, [projectNames]);
|
}, [projectNames]);
|
||||||
|
|
||||||
/** Check if current page id project details page */
|
/** Check if current page id project details page */
|
||||||
const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname)
|
const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname);
|
||||||
const isDirectoryPath = /^\/directory$/.test(location.pathname);
|
|
||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
async (data) => {
|
async (data) => {
|
||||||
@ -145,10 +144,7 @@ const Header = () => {
|
|||||||
// return () => eventBus.off("project", newProjectHandler);
|
// return () => eventBus.off("project", newProjectHandler);
|
||||||
// }, [handler]);
|
// }, [handler]);
|
||||||
|
|
||||||
useDispatch( () =>
|
|
||||||
{
|
|
||||||
dispatch(changeMaster("Job Role"))
|
|
||||||
},[])
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eventBus.on("assign_project_one", handler);
|
eventBus.on("assign_project_one", handler);
|
||||||
eventBus.on("project", newProjectHandler);
|
eventBus.on("project", newProjectHandler);
|
||||||
@ -162,7 +158,7 @@ const Header = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
className="layout-navbar container-fluid mb-3 navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
|
className="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
|
||||||
id="layout-navbar"
|
id="layout-navbar"
|
||||||
>
|
>
|
||||||
<div className="layout-menu-toggle navbar-nav align-items-xl-center me-3 me-xl-0 d-xl-none">
|
<div className="layout-menu-toggle navbar-nav align-items-xl-center me-3 me-xl-0 d-xl-none">
|
||||||
@ -179,11 +175,11 @@ const Header = () => {
|
|||||||
>
|
>
|
||||||
{projectNames?.length > 0 && (
|
{projectNames?.length > 0 && (
|
||||||
<div className=" align-items-center">
|
<div className=" align-items-center">
|
||||||
{(!isProjectPath && !isDirectoryPath) && (
|
{!isProjectPath && (
|
||||||
<>
|
<>
|
||||||
<i
|
<i
|
||||||
className="rounded-circle bx bx-building-house bx-sm-lg bx-md"
|
className="rounded-circle bx bx-building-house"
|
||||||
|
style={{ fontSize: "xx-large" }}
|
||||||
></i>
|
></i>
|
||||||
<div className="btn-group">
|
<div className="btn-group">
|
||||||
<button
|
<button
|
||||||
@ -203,7 +199,7 @@ const Header = () => {
|
|||||||
style={{ overflow: "auto", maxHeight: "300px" }}
|
style={{ overflow: "auto", maxHeight: "300px" }}
|
||||||
>
|
>
|
||||||
{[...projectNames]
|
{[...projectNames]
|
||||||
.sort((a, b) => a?.name?.localeCompare(b.name))
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
.map((project) => (
|
.map((project) => (
|
||||||
<li key={project?.id}>
|
<li key={project?.id}>
|
||||||
<button
|
<button
|
||||||
@ -214,7 +210,7 @@ const Header = () => {
|
|||||||
>
|
>
|
||||||
{project?.name}{" "}
|
{project?.name}{" "}
|
||||||
{project?.shortName ? (
|
{project?.shortName ? (
|
||||||
<span className="text-primary fw-semibold ">
|
<span className="text-primary fw-semibold">
|
||||||
{" "}
|
{" "}
|
||||||
({project?.shortName})
|
({project?.shortName})
|
||||||
</span>
|
</span>
|
||||||
@ -274,7 +270,7 @@ const Header = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="dropdown-shortcuts-item col">
|
<div className="dropdown-shortcuts-item col">
|
||||||
<a
|
<a
|
||||||
onClick={() => navigate(`/projects`)}
|
onClick={() => navigate(`/projectNames`)}
|
||||||
className="text-heading text-truncate cursor-pointer"
|
className="text-heading text-truncate cursor-pointer"
|
||||||
>
|
>
|
||||||
<span className="dropdown-shortcuts-icon rounded-circle mb-3">
|
<span className="dropdown-shortcuts-icon rounded-circle mb-3">
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { getProjectStatusName } from "../../utils/projectStatus";
|
import { getProjectStatusName } from "../../utils/projectStatus";
|
||||||
import {useProjectDetails} from "../../hooks/useProjects";
|
const AboutProject = ({ data }) => {
|
||||||
import {useParams} from "react-router-dom";
|
const [CurrentProject, setCurrentProject] = useState(data);
|
||||||
const AboutProject = () => {
|
|
||||||
const {projectId} = useParams();
|
useEffect(() => {
|
||||||
const {projects_Details,isLoading,error} = useProjectDetails(projectId)
|
setCurrentProject(data);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{projects_Details && (
|
{data && (
|
||||||
<div className="card mb-6">
|
<div className="card mb-6">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<small className="card-text text-uppercase text-muted small">
|
<small className="card-text text-uppercase text-muted small">
|
||||||
@ -19,8 +21,8 @@ const AboutProject = () => {
|
|||||||
<i className="bx bx-check"></i>
|
<i className="bx bx-check"></i>
|
||||||
<span className="fw-medium mx-2">Start Date:</span>{" "}
|
<span className="fw-medium mx-2">Start Date:</span>{" "}
|
||||||
<span>
|
<span>
|
||||||
{projects_Details.startDate
|
{data.startDate
|
||||||
? moment(projects_Details.startDate).format("DD-MMM-YYYY")
|
? moment(data.startDate).format("DD-MMM-YYYY")
|
||||||
: "N/A"}
|
: "N/A"}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
@ -28,32 +30,31 @@ const AboutProject = () => {
|
|||||||
<i className="bx bx-stop-circle"></i>{" "}
|
<i className="bx bx-stop-circle"></i>{" "}
|
||||||
<span className="fw-medium mx-2">End Date:</span>{" "}
|
<span className="fw-medium mx-2">End Date:</span>{" "}
|
||||||
<span>
|
<span>
|
||||||
{projects_Details.endDate
|
{data.endDate
|
||||||
? moment(projects_Details.endDate).format("DD-MMM-YYYY")
|
? moment(data.endDate).format("DD-MMM-YYYY")
|
||||||
: "N/A"}
|
: "N/A"}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="d-flex align-items-center mb-2">
|
<li className="d-flex align-items-center mb-2">
|
||||||
<i className="bx bx-trophy"></i>
|
<i className="bx bx-trophy"></i>
|
||||||
<span className="fw-medium mx-2">Status:</span>{" "}
|
<span className="fw-medium mx-2">Status:</span>{" "}
|
||||||
<span>{projects_Details?.projectStatus?.status
|
<span>{getProjectStatusName(data.projectStatusId)}</span>
|
||||||
}</span>
|
|
||||||
</li>
|
</li>
|
||||||
<li className="d-flex align-items-center mb-4">
|
<li className="d-flex align-items-center mb-4">
|
||||||
<i className="bx bx-user"></i>
|
<i className="bx bx-user"></i>
|
||||||
<span className="fw-medium mx-2">Contact:</span>{" "}
|
<span className="fw-medium mx-2">Contact:</span>{" "}
|
||||||
<span>{projects_Details.contactPerson}</span>
|
<span>{data.contactPerson}</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="d-flex flex-column align-items-start mb-4">
|
<li className="d-flex flex-column align-items-start mb-4">
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<i className="bx bx-flag"></i>
|
<i className="bx bx-flag"></i>
|
||||||
<span className="fw-medium mx-2">Address:</span>
|
<span className="fw-medium mx-2">Address:</span>
|
||||||
{projects_Details.projectAddress?.length <= 20 && (
|
{data.projectAddress?.length <= 20 && (
|
||||||
<span>{projects_Details.projectAddress}</span>
|
<span>{data.projectAddress}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{projects_Details.projectAddress?.length > 20 && (
|
{data.projectAddress?.length > 20 && (
|
||||||
<div className="ms-4 text-start">{projects_Details.projectAddress}</div>
|
<div className="ms-4 text-start">{data.projectAddress}</div>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -61,7 +62,7 @@ const AboutProject = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isLoading && <span>loading...</span>}
|
{!data && <span>loading...</span>}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,8 +15,10 @@ import { useCreateTask } from "../../hooks/useTasks";
|
|||||||
|
|
||||||
const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
||||||
const maxPlanned =
|
const maxPlanned =
|
||||||
assignData?.workItem?.plannedWork -
|
assignData?.workItem?.workItem?.plannedWork -
|
||||||
assignData?.workItem?.completedWork;
|
assignData?.workItem?.workItem?.completedWork;
|
||||||
|
|
||||||
|
// Zod schema for form validation
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
selectedEmployees: z
|
selectedEmployees: z
|
||||||
.array(z.string())
|
.array(z.string())
|
||||||
@ -49,7 +51,9 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
const infoRef = useRef(null);
|
const infoRef = useRef(null);
|
||||||
const infoRef1 = useRef(null);
|
const infoRef1 = useRef(null);
|
||||||
|
|
||||||
|
// Initialize Bootstrap Popovers on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Check if Bootstrap is available globally
|
||||||
if (typeof bootstrap !== "undefined") {
|
if (typeof bootstrap !== "undefined") {
|
||||||
if (infoRef.current) {
|
if (infoRef.current) {
|
||||||
new bootstrap.Popover(infoRef.current, {
|
new bootstrap.Popover(infoRef.current, {
|
||||||
@ -71,7 +75,8 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
} else {
|
} else {
|
||||||
console.warn("Bootstrap is not available. Popovers might not function.");
|
console.warn("Bootstrap is not available. Popovers might not function.");
|
||||||
}
|
}
|
||||||
}, []);
|
}, []); // Empty dependency array ensures this runs once on mount
|
||||||
|
// Redux state and hooks
|
||||||
const selectedProject = useSelector(
|
const selectedProject = useSelector(
|
||||||
(store) => store.localVariables.projectId
|
(store) => store.localVariables.projectId
|
||||||
);
|
);
|
||||||
@ -81,11 +86,14 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
recallEmployeeData,
|
recallEmployeeData,
|
||||||
} = useEmployeesAllOrByProjectId(selectedProject, false);
|
} = useEmployeesAllOrByProjectId(selectedProject, false);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { loading } = useMaster();
|
const { loading } = useMaster(); // Assuming this is for jobRoleData loading
|
||||||
const {data:jobRoleData} = useMaster();
|
const jobRoleData = getCachedData("Job Role");
|
||||||
|
|
||||||
|
// Local component states
|
||||||
const [selectedRole, setSelectedRole] = useState("all");
|
const [selectedRole, setSelectedRole] = useState("all");
|
||||||
const [displayedSelection, setDisplayedSelection] = useState("");
|
const [displayedSelection, setDisplayedSelection] = useState(""); // This state is not updated in the provided code, consider if it's still needed or how it should be updated
|
||||||
|
|
||||||
|
// React Hook Form setup
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
@ -93,43 +101,50 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
watch,
|
watch,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
reset,
|
reset,
|
||||||
trigger,
|
trigger, // <--- IMPORTANT: Destructure 'trigger' here
|
||||||
} = useForm({
|
} = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
selectedEmployees: [],
|
selectedEmployees: [],
|
||||||
description: "",
|
description: "",
|
||||||
plannedTask: "",
|
plannedTask: "",
|
||||||
},
|
},
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema), // Integrate Zod schema with react-hook-form
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handler for employee checkbox changes
|
||||||
const handleCheckboxChange = (event, user) => {
|
const handleCheckboxChange = (event, user) => {
|
||||||
const isChecked = event.target.checked;
|
const isChecked = event.target.checked;
|
||||||
let updatedSelectedEmployees = watch("selectedEmployees") || [];
|
let updatedSelectedEmployees = watch("selectedEmployees") || []; // Get current selected employees from form state
|
||||||
|
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
|
// Add employee if checked and not already in the list
|
||||||
if (!updatedSelectedEmployees.includes(user.id)) {
|
if (!updatedSelectedEmployees.includes(user.id)) {
|
||||||
updatedSelectedEmployees = [...updatedSelectedEmployees, user.id];
|
updatedSelectedEmployees = [...updatedSelectedEmployees, user.id];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updatedSelectedEmployees = updatedSelectedEmployees?.filter(
|
// Remove employee if unchecked
|
||||||
|
updatedSelectedEmployees = updatedSelectedEmployees.filter(
|
||||||
(id) => id !== user.id
|
(id) => id !== user.id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Update the form state with the new list of selected employees
|
||||||
setValue("selectedEmployees", updatedSelectedEmployees);
|
setValue("selectedEmployees", updatedSelectedEmployees);
|
||||||
trigger("selectedEmployees");
|
trigger("selectedEmployees"); // <--- IMPORTANT: Trigger validation here
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Effect to dispatch action for Job Role master data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(changeMaster("Job Role"));
|
dispatch(changeMaster("Job Role"));
|
||||||
|
// Cleanup function to reset selected role when component unmounts or dispatch changes
|
||||||
return () => setSelectedRole("all");
|
return () => setSelectedRole("all");
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
// Handler for role filter change
|
||||||
const handleRoleChange = (event) => {
|
const handleRoleChange = (event) => {
|
||||||
setSelectedRole(event.target.value);
|
setSelectedRole(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Filter employees based on selected role
|
||||||
const filteredEmployees =
|
const filteredEmployees =
|
||||||
selectedRole === "all"
|
selectedRole === "all"
|
||||||
? employees
|
? employees
|
||||||
@ -137,6 +152,36 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
(emp) => String(emp.jobRoleId || "") === selectedRole
|
(emp) => String(emp.jobRoleId || "") === selectedRole
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Form submission handler
|
||||||
|
// const onSubmit = async (data) => {
|
||||||
|
// const selectedEmployeeIds = data.selectedEmployees;
|
||||||
|
// setIsSubmitting(true);
|
||||||
|
// // Prepare taskTeam data (only IDs are needed for the backend based on previous context)
|
||||||
|
// const taskTeamWithDetails = selectedEmployeeIds
|
||||||
|
// .map((empId) => {
|
||||||
|
// return empId; // Return just the ID as per previous discussions
|
||||||
|
// })
|
||||||
|
// .filter(Boolean); // Ensure no nulls if employee not found (though unlikely with current logic)
|
||||||
|
|
||||||
|
// // Format data for API call
|
||||||
|
// const formattedData = {
|
||||||
|
// taskTeam: taskTeamWithDetails,
|
||||||
|
// plannedTask: data.plannedTask,
|
||||||
|
// description: data.description,
|
||||||
|
// assignmentDate: new Date().toISOString(), // Current date/time
|
||||||
|
// workItemId: assignData?.workItem?.workItem.id,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// await TasksRepository.assignTask(formattedData);
|
||||||
|
// setIsSubmitting( false );
|
||||||
|
// showToast("Task Assined Successfully.", "success");
|
||||||
|
// closedModel();
|
||||||
|
// } catch (error) {
|
||||||
|
// setIsSubmitting(false);
|
||||||
|
// showToast("Something went wrong. Please try again.", "error");
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
const onSubmit = (data) => {
|
const onSubmit = (data) => {
|
||||||
const selectedEmployeeIds = data.selectedEmployees;
|
const selectedEmployeeIds = data.selectedEmployees;
|
||||||
@ -147,12 +192,13 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
plannedTask: data.plannedTask,
|
plannedTask: data.plannedTask,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
assignmentDate: new Date().toISOString(),
|
assignmentDate: new Date().toISOString(),
|
||||||
workItemId: assignData?.workItem.id,
|
workItemId: assignData?.workItem?.workItem.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
assignTask(formattedData);
|
assignTask(formattedData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handler to close the modal and reset form
|
||||||
const closedModel = () => {
|
const closedModel = () => {
|
||||||
reset();
|
reset();
|
||||||
onClose();
|
onClose();
|
||||||
@ -166,10 +212,10 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
<span className="text-dark text-start d-flex align-items-center flex-wrap form-text">
|
<span className="text-dark text-start d-flex align-items-center flex-wrap form-text">
|
||||||
<span className="me-2 m-0 font-bold">Work Location :</span>
|
<span className="me-2 m-0 font-bold">Work Location :</span>
|
||||||
{[
|
{[
|
||||||
assignData?.building?.buildingName,
|
assignData?.building?.name,
|
||||||
assignData?.floor?.floorName,
|
assignData?.floor?.floorName,
|
||||||
assignData?.workArea?.areaName,
|
assignData?.workArea?.areaName,
|
||||||
assignData?.workItem?.activityMaster?.activityName,
|
assignData?.workItem?.workItem?.activityMaster?.activityName,
|
||||||
]
|
]
|
||||||
.filter(Boolean) // Filter out any undefined/null values
|
.filter(Boolean) // Filter out any undefined/null values
|
||||||
.map((item, index, array) => (
|
.map((item, index, array) => (
|
||||||
@ -332,7 +378,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
"selectedEmployees",
|
"selectedEmployees",
|
||||||
updatedSelected
|
updatedSelected
|
||||||
);
|
);
|
||||||
trigger("selectedEmployees");
|
trigger("selectedEmployees"); // <--- IMPORTANT: Trigger validation on removing badge
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i className="icon-base bx bx-x icon-md "></i>
|
<i className="icon-base bx bx-x icon-md "></i>
|
||||||
@ -349,10 +395,11 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
{!loading && errors.selectedEmployees && (
|
{!loading && errors.selectedEmployees && (
|
||||||
<div className="danger-text mt-1">
|
<div className="danger-text mt-1">
|
||||||
<p>{errors.selectedEmployees.message}</p>{" "}
|
<p>{errors.selectedEmployees.message}</p>{" "}
|
||||||
|
{/* Use message from Zod schema */}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Pending Task of Activity section */}
|
||||||
<div className="col-md text-start mx-0 px-0">
|
<div className="col-md text-start mx-0 px-0">
|
||||||
<div className="form-check form-check-inline mt-3 px-1">
|
<div className="form-check form-check-inline mt-3 px-1">
|
||||||
<label
|
<label
|
||||||
@ -365,12 +412,12 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
|
|||||||
htmlFor="inlineCheckbox1"
|
htmlFor="inlineCheckbox1"
|
||||||
>
|
>
|
||||||
<strong>
|
<strong>
|
||||||
{assignData?.workItem?.plannedWork -
|
{assignData?.workItem?.workItem?.plannedWork -
|
||||||
assignData?.workItem?.completedWork}
|
assignData?.workItem?.workItem?.completedWork}
|
||||||
</strong>{" "}
|
</strong>{" "}
|
||||||
<u>
|
<u>
|
||||||
{
|
{
|
||||||
assignData?.workItem?.activityMaster
|
assignData?.workItem?.workItem?.activityMaster
|
||||||
?.unitOfMeasurement
|
?.unitOfMeasurement
|
||||||
}
|
}
|
||||||
</u>
|
</u>
|
||||||
|
|||||||
@ -4,8 +4,7 @@ const Building = ({
|
|||||||
toggleBuilding,
|
toggleBuilding,
|
||||||
expandedBuildings,
|
expandedBuildings,
|
||||||
getContent,
|
getContent,
|
||||||
} ) =>
|
}) => {
|
||||||
{
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={building.id}>
|
<React.Fragment key={building.id}>
|
||||||
<tr className="overflow-auto">
|
<tr className="overflow-auto">
|
||||||
@ -21,7 +20,7 @@ const Building = ({
|
|||||||
>
|
>
|
||||||
<div className="row table-responsive">
|
<div className="row table-responsive">
|
||||||
<h6 style={{ marginBottom: "0px", fontSize: "14px" }}>
|
<h6 style={{ marginBottom: "0px", fontSize: "14px" }}>
|
||||||
{building.buildingName}
|
{building.name}
|
||||||
{expandedBuildings.includes(building.id) ? (
|
{expandedBuildings.includes(building.id) ? (
|
||||||
<i className="bx bx-chevron-down"></i>
|
<i className="bx bx-chevron-down"></i>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import React, { useEffect, useMemo } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import ProjectRepository from "../../../repositories/ProjectRepository";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
import { useProjectDetails } from "../../../hooks/useProjects";
|
||||||
import { getCachedData } from "../../../slices/apiDataManager";
|
import { getCachedData } from "../../../slices/apiDataManager";
|
||||||
import showToast from "../../../services/toastService";
|
import showToast from "../../../services/toastService";
|
||||||
import { useManageProjectInfra } from "../../../hooks/useProjects";
|
|
||||||
import useSelect from "../../common/useSelect";
|
|
||||||
|
|
||||||
|
// Zod validation schema
|
||||||
const buildingSchema = z.object({
|
const buildingSchema = z.object({
|
||||||
Id: z.string().optional(),
|
Id: z.string().optional(),
|
||||||
name: z.string().min(1, "Building name is required"),
|
name: z.string().min(1, "Building name is required"),
|
||||||
@ -17,165 +18,194 @@ const buildingSchema = z.object({
|
|||||||
.max(160, "Description cannot exceed 160 characters"),
|
.max(160, "Description cannot exceed 160 characters"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const BuildingModel = ({ project, onClose, editingBuilding = null }) => {
|
const BuildingModel = ({
|
||||||
|
project,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
clearTrigger,
|
||||||
|
onClearComplete,
|
||||||
|
editingBuilding = null,
|
||||||
|
}) => {
|
||||||
const selectedProject = useSelector(
|
const selectedProject = useSelector(
|
||||||
(store) => store.localVariables.projectId
|
(store) => store.localVariables.projectId
|
||||||
);
|
);
|
||||||
|
const [buildings, setBuildings] = useState([]);
|
||||||
|
const projects_Details = getCachedData("projectInfo");
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
id: "",
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
projectId: project?.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (clearTrigger) {
|
||||||
|
setFormData({ id: null, name: "", description: "", projectId: project.id });
|
||||||
|
onClearComplete();
|
||||||
|
} else if (editingBuilding) {
|
||||||
|
setFormData({ ...editingBuilding, projectId: project.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setValue("name", "");
|
||||||
|
};
|
||||||
|
}, [clearTrigger, onClearComplete, editingBuilding, project?.id]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
setValue,
|
setValue,
|
||||||
watch,
|
|
||||||
reset,
|
reset,
|
||||||
|
getValues,
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(buildingSchema),
|
resolver: zodResolver(buildingSchema),
|
||||||
defaultValues: {
|
defaultValues: formData, // Set default values from formData state
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBuildingChange = (e) => {
|
||||||
|
const selectedBuilding = project.buildings.find(
|
||||||
|
(b) => b.id === +e.target.value
|
||||||
|
);
|
||||||
|
if (selectedBuilding) {
|
||||||
|
setFormData({ ...selectedBuilding, projectId: project.id });
|
||||||
|
setValue("name", selectedBuilding.name); // Update name field
|
||||||
|
setValue("description", selectedBuilding.description); // Update description field
|
||||||
|
} else {
|
||||||
|
setFormData({ id: null, name: "", description: "", projectId: project.id });
|
||||||
|
setValue("name", "");
|
||||||
|
setValue("description", "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmitHandler = async (data) => {
|
||||||
|
if (String(data.Id) === "0") {
|
||||||
|
data.Id = null;
|
||||||
|
}
|
||||||
|
onSubmit({ ...data, projectId: project.id });
|
||||||
|
reset({
|
||||||
Id: "0",
|
Id: "0",
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
},
|
});
|
||||||
});
|
if (data.Id !== null) {
|
||||||
const watchedId = watch("Id");
|
showToast("Building updated successfully.", "success");
|
||||||
|
|
||||||
const { mutate: ManageBuilding, isPending } = useManageProjectInfra({
|
|
||||||
onSuccessCallback: (data, variables) => {
|
|
||||||
showToast(
|
|
||||||
watchedId != "0"
|
|
||||||
? "Building updated Successfully"
|
|
||||||
: "Building created Successfully",
|
|
||||||
"success"
|
|
||||||
);
|
|
||||||
reset({ Id: "0", name: "", description: "" });
|
|
||||||
// onClose?.();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const sortedBuildings = useMemo(() => {
|
|
||||||
return (project || [])
|
|
||||||
.filter((b) => b?.buildingName)
|
|
||||||
.sort((a, b) => a.buildingName.localeCompare(b?.buildingName));
|
|
||||||
}, [project]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!watchedId || watchedId === "0") {
|
|
||||||
setValue("name", "");
|
|
||||||
setValue("description", "");
|
|
||||||
} else {
|
} else {
|
||||||
const selected = sortedBuildings.find((b) => String(b.id) === watchedId);
|
showToast("Building created successfully.", "success");
|
||||||
if (selected) {
|
|
||||||
setValue("name", selected.buildingName || "");
|
|
||||||
setValue("description", selected.description || "");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [watchedId, sortedBuildings, setValue]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (editingBuilding) {
|
|
||||||
reset({
|
|
||||||
Id: String(editingBuilding.id),
|
|
||||||
name: editingBuilding.name,
|
|
||||||
description: editingBuilding.description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [editingBuilding]);
|
|
||||||
|
|
||||||
const onSubmitHandler = (data) => {
|
|
||||||
const payload = {
|
|
||||||
...data,
|
|
||||||
Id: data.Id === "0" ? null : data.Id,
|
|
||||||
projectId: selectedProject,
|
|
||||||
};
|
|
||||||
|
|
||||||
let infraObject = [
|
|
||||||
{
|
|
||||||
building: payload,
|
|
||||||
floor: null,
|
|
||||||
workArea: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
ManageBuilding({ infraObject, projectId: selectedProject });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(projects_Details){
|
||||||
|
setBuildings(projects_Details.data?.buildings);
|
||||||
|
}
|
||||||
|
}, [projects_Details]);
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmitHandler)} className="row g-2">
|
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
|
||||||
<h5 className="text-center mb-2">Manage Buildings </h5>
|
<div className="modal-content">
|
||||||
<div className="col-12">
|
<div className="modal-body">
|
||||||
<label className="form-label">Select Building</label>
|
<button
|
||||||
<select
|
type="button"
|
||||||
{...register("Id")}
|
className="btn-close"
|
||||||
className="select2 form-select form-select-sm"
|
data-bs-dismiss="modal"
|
||||||
>
|
aria-label="Close"
|
||||||
<option value="0">Add New Building</option>
|
onClick={() => {
|
||||||
{sortedBuildings.length > 0 ? (
|
onClose();
|
||||||
sortedBuildings.map((b) => (
|
reset(); // Call reset here
|
||||||
<option key={b.id} value={b.id}>
|
}}
|
||||||
{b.buildingName}
|
></button>
|
||||||
</option>
|
<h5 className="text-center mb-2">
|
||||||
))
|
Manage Buildings - {project?.name}
|
||||||
) : (
|
</h5>
|
||||||
<option disabled>No buildings found</option>
|
<form onSubmit={handleSubmit(onSubmitHandler)} className="row g-2">
|
||||||
)}
|
<div className="col-12">
|
||||||
</select>
|
<label className="form-label">Select Building</label>
|
||||||
{errors.Id && <span className="danger-text">{errors.Id.message}</span>}
|
<select
|
||||||
</div>
|
{...register("Id")}
|
||||||
|
className="select2 form-select form-select-sm"
|
||||||
|
onChange={(e) => {
|
||||||
|
handleBuildingChange(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="0">Add New Building</option>
|
||||||
|
|
||||||
{/* Name */}
|
{project?.buildings?.length > 0 ? (
|
||||||
<div className="col-12">
|
project.buildings
|
||||||
<label className="form-label">
|
.filter((building) => building?.name)
|
||||||
{watchedId !== "0" ? "Rename Building Name" : "New Building Name"}
|
.sort((a, b) => {
|
||||||
</label>
|
const nameA = a.name || "";
|
||||||
<input
|
const nameB = b.name || "";
|
||||||
{...register("name")}
|
return nameA?.localeCompare(nameB);
|
||||||
type="text"
|
})
|
||||||
className="form-control form-control-sm"
|
.map((building) => (
|
||||||
/>
|
<option key={building.id} value={building.id}>
|
||||||
{errors.name && (
|
{building.name}
|
||||||
<span className="danger-text">{errors.name.message}</span>
|
</option>
|
||||||
)}
|
))
|
||||||
</div>
|
) : (
|
||||||
|
<option disabled>No buildings found</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
{errors.Id && (
|
||||||
|
<span className="danger-text">{errors.Id.message}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
<div className="col-12">
|
||||||
<div className="col-12">
|
<label className="form-label">
|
||||||
<label className="form-label">Description</label>
|
{formData.id ? "Rename Building Name" : "New Building Name"}
|
||||||
<textarea
|
</label>
|
||||||
{...register("description")}
|
<input
|
||||||
rows="5"
|
{...register("name")}
|
||||||
maxLength="160"
|
type="text"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
/>
|
/>
|
||||||
{errors.description && (
|
{errors.name && (
|
||||||
<span className="danger-text">{errors.description.message}</span>
|
<span className="danger-text">{errors.name.message}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-center">
|
<div className="col-12">
|
||||||
<button
|
<label className="form-label">Description</label>
|
||||||
type="submit"
|
<textarea
|
||||||
className="btn btn-sm btn-primary me-3"
|
{...register("description")}
|
||||||
disabled={isPending}
|
maxLength="160"
|
||||||
>
|
rows="5"
|
||||||
{isPending
|
className="form-control form-control-sm"
|
||||||
? "Please wait..."
|
/>
|
||||||
: watchedId !== "0"
|
{errors.description && (
|
||||||
? "Edit Building"
|
<span className="danger-text">
|
||||||
: "Add Building"}
|
{errors.description.message}
|
||||||
</button>
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<div className="col-12 text-center">
|
||||||
type="reset"
|
<button type="submit" className="btn btn-sm btn-primary me-3">
|
||||||
className="btn btn-sm btn-label-secondary"
|
{formData.id && getValues("name")
|
||||||
disabled={isPending}
|
? "Edit Building"
|
||||||
onClick={() => {
|
: "Add Building"}
|
||||||
onClose();
|
</button>
|
||||||
reset();
|
<button
|
||||||
}}
|
type="reset"
|
||||||
>
|
className="btn btn-sm btn-label-secondary"
|
||||||
Cancel
|
data-bs-dismiss="modal"
|
||||||
</button>
|
aria-label="Close"
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
reset(); // Call reset here
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BuildingModel;
|
export default BuildingModel;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
|
|
||||||
|
import { useActivitiesMaster, useWorkCategoriesMaster } from "../../../hooks/masterHook/useMaster";
|
||||||
|
import { useProjectDetails } from "../../../hooks/useProjects";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import ProjectRepository from "../../../repositories/ProjectRepository";
|
||||||
import {
|
import {
|
||||||
useActivitiesMaster,
|
cacheData,
|
||||||
useWorkCategoriesMaster,
|
clearCacheKey,
|
||||||
} from "../../../hooks/masterHook/useMaster";
|
getCachedData,
|
||||||
import { useManageTask } from "../../../hooks/useProjects";
|
} from "../../../slices/apiDataManager";
|
||||||
import { cacheData, getCachedData } from "../../../slices/apiDataManager";
|
|
||||||
import { refreshData } from "../../../slices/localVariablesSlice";
|
import { refreshData } from "../../../slices/localVariablesSlice";
|
||||||
import showToast from "../../../services/toastService";
|
import showToast from "../../../services/toastService";
|
||||||
|
|
||||||
@ -18,13 +20,18 @@ const taskSchema = z
|
|||||||
activityID: z.string().min(1, "Activity is required"),
|
activityID: z.string().min(1, "Activity is required"),
|
||||||
workCategoryId: z.string().min(1, "Work Category is required"),
|
workCategoryId: z.string().min(1, "Work Category is required"),
|
||||||
plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
|
plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
|
||||||
completedWork: z.number().min(0, "Completed Work must be ≥ 0"),
|
completedWork: z.number().min( 0, "Completed Work must be greater than 0" ),
|
||||||
comment: z.string(),
|
comment:z.string()
|
||||||
})
|
})
|
||||||
.refine((data) => data.completedWork <= data.plannedWork, {
|
.refine(
|
||||||
message: "Completed Work cannot be greater than Planned Work",
|
(data) =>
|
||||||
path: ["completedWork"],
|
data.completedWork === undefined ||
|
||||||
});
|
data.completedWork <= data.plannedWork,
|
||||||
|
{
|
||||||
|
message: "Completed Work cannot be greater than Planned Work",
|
||||||
|
path: ["completedWork"], // error will show next to this field
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const EditActivityModal = ({
|
const EditActivityModal = ({
|
||||||
workItem,
|
workItem,
|
||||||
@ -32,12 +39,29 @@ const EditActivityModal = ({
|
|||||||
building,
|
building,
|
||||||
floor,
|
floor,
|
||||||
onClose,
|
onClose,
|
||||||
} ) =>
|
}) => {
|
||||||
{
|
const selectedProject = useSelector(
|
||||||
|
(store) => store.localVariables.projectId
|
||||||
const { activities, loading: loadingActivities } = useActivitiesMaster();
|
);
|
||||||
const { categories, loading: loadingCategories } = useWorkCategoriesMaster();
|
const defaultModel = {
|
||||||
|
activityID: 0,
|
||||||
|
workCategoryId: 0,
|
||||||
|
plannedWork: 0,
|
||||||
|
completedWork: 0,
|
||||||
|
comment:""
|
||||||
|
};
|
||||||
|
|
||||||
|
const { projects_Details, refetch } = useProjectDetails(selectedProject);
|
||||||
|
const [ActivityUnit, setActivityUnit] = useState("");
|
||||||
|
const { activities, loading, error } = useActivitiesMaster();
|
||||||
|
const { categories, categoryLoading, categoryError } =
|
||||||
|
useWorkCategoriesMaster();
|
||||||
|
const [formData, setFormData] = useState(defaultModel);
|
||||||
const [selectedActivity, setSelectedActivity] = useState(null);
|
const [selectedActivity, setSelectedActivity] = useState(null);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [activityData, setActivityData] = useState([]);
|
||||||
|
const [categoryData, setCategoryData] = useState([]);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -49,207 +73,359 @@ const EditActivityModal = ({
|
|||||||
watch,
|
watch,
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(taskSchema),
|
resolver: zodResolver(taskSchema),
|
||||||
defaultValues: {
|
defaultValues: defaultModel,
|
||||||
activityID: "",
|
|
||||||
workCategoryId: "",
|
|
||||||
plannedWork: 0,
|
|
||||||
completedWork: 0,
|
|
||||||
comment: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const { mutate: UpdateTask, isPending } = useManageTask({
|
|
||||||
onSuccessCallback: () => onClose?.()
|
|
||||||
});
|
|
||||||
|
|
||||||
const activityID = watch("activityID");
|
const handleActivityChange = (e) => {
|
||||||
|
const selectedId = String(e.target.value);
|
||||||
const sortedActivities = useMemo(
|
const selected = activityData.find((a) => a.id === selectedId);
|
||||||
() =>
|
|
||||||
[...(activities || [])].sort((a, b) =>
|
|
||||||
a.activityName?.localeCompare(b.activityName)
|
|
||||||
),
|
|
||||||
[activities]
|
|
||||||
);
|
|
||||||
|
|
||||||
const sortedCategories = useMemo(
|
|
||||||
() => [...(categories || [])].sort((a, b) => a.name?.localeCompare(b.name)),
|
|
||||||
[categories]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!workItem) return;
|
|
||||||
|
|
||||||
reset({
|
|
||||||
activityID: String(
|
|
||||||
workItem?.workItem?.activityId || workItem?.activityId || ""
|
|
||||||
),
|
|
||||||
workCategoryId: String(
|
|
||||||
workItem?.workItem?.workCategoryId || workItem?.workCategoryId || ""
|
|
||||||
),
|
|
||||||
plannedWork:
|
|
||||||
workItem?.workItem?.plannedWork || workItem?.plannedWork || 0,
|
|
||||||
completedWork:
|
|
||||||
workItem?.workItem?.completedWork || workItem?.completedWork || 0,
|
|
||||||
comment: workItem?.workItem?.description || workItem?.description || "",
|
|
||||||
});
|
|
||||||
}, [workItem?.id]);
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const selected = activities?.find((a) => a.id === activityID);
|
|
||||||
setSelectedActivity(selected || null);
|
setSelectedActivity(selected || null);
|
||||||
}, [activityID, activities]);
|
setValue("activityID", selectedId);
|
||||||
|
};
|
||||||
|
|
||||||
const onSubmitForm = (data) =>
|
const handleCategoryChange = (e) => {
|
||||||
|
const selectedId = String(e.target.value);
|
||||||
|
const category = categoryData.find((b) => b.id === selectedId);
|
||||||
|
setSelectedCategory(category || null);
|
||||||
|
setValue("workCategoryId", selectedId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmitForm = async ( data ) =>
|
||||||
{
|
{
|
||||||
const payload = {
|
setIsSubmitting(true)
|
||||||
|
const updatedProject = { ...projects_Details };
|
||||||
|
const finalData = {
|
||||||
...data,
|
...data,
|
||||||
id: workItem?.workItem?.id ?? workItem?.id,
|
id: workItem?.workItem?.id ?? workItem?.id,
|
||||||
buildingID: building?.id,
|
buildingID: building?.id,
|
||||||
floorId: floor?.id,
|
floorId: floor?.id,
|
||||||
workAreaId: workArea?.id,
|
workAreaId: workArea?.id,
|
||||||
};
|
};
|
||||||
UpdateTask([payload])
|
|
||||||
}
|
ProjectRepository.manageProjectTasks([finalData])
|
||||||
|
.then((response) => {
|
||||||
|
if (response?.data[0]) {
|
||||||
|
const { workItemId, workItem } = response.data[0];
|
||||||
|
|
||||||
|
let finalUpdatedWorkItem = null;
|
||||||
|
const newProject = {
|
||||||
|
...updatedProject,
|
||||||
|
buildings: updatedProject.buildings.map((building) =>
|
||||||
|
building.id === finalData.buildingID
|
||||||
|
? {
|
||||||
|
...building,
|
||||||
|
floors: building.floors.map((floor) =>
|
||||||
|
floor.id === finalData.floorId
|
||||||
|
? {
|
||||||
|
...floor,
|
||||||
|
workAreas: floor.workAreas.map((workArea) =>
|
||||||
|
workArea.id === workItem?.workAreaId
|
||||||
|
? {
|
||||||
|
...workArea,
|
||||||
|
workItems: (() => {
|
||||||
|
const exists = workArea.workItems.some(
|
||||||
|
(item) =>
|
||||||
|
String(
|
||||||
|
item?.workItem?.id ?? item?.id
|
||||||
|
) === String(finalData.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
finalUpdatedWorkItem = workItem;
|
||||||
|
|
||||||
|
return exists
|
||||||
|
? workArea.workItems.map((item) =>
|
||||||
|
String(
|
||||||
|
item?.workItem?.id ?? item?.id
|
||||||
|
) === String(finalData.id)
|
||||||
|
? workItem
|
||||||
|
: item
|
||||||
|
)
|
||||||
|
: [...workArea.workItems, workItem];
|
||||||
|
})(),
|
||||||
|
}
|
||||||
|
: workArea
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: floor
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: building
|
||||||
|
),
|
||||||
|
};
|
||||||
|
cacheData("projectInfo", {
|
||||||
|
projectId: newProject.id,
|
||||||
|
data: newProject,
|
||||||
|
});
|
||||||
|
resetForm();
|
||||||
|
dispatch( refreshData( true ) );
|
||||||
|
setIsSubmitting(false)
|
||||||
|
showToast("Activity Updated Successfully","success")
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch( ( error ) =>
|
||||||
|
{
|
||||||
|
setIsSubmitting(false)
|
||||||
|
const message = error.response.data.message || error.message || "Error Occured During Api Call"
|
||||||
|
showToast( message, "error" );
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setFormData(defaultModel);
|
||||||
|
setSelectedActivity(null);
|
||||||
|
reset(defaultModel);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
activityID: workItem?.workItem?.activityId || workItem?.activityId || 0,
|
||||||
|
workCategoryId:
|
||||||
|
workItem?.workItem?.workCategoryId || workItem?.workCategoryId || 0,
|
||||||
|
plannedWork:
|
||||||
|
workItem?.workItem?.plannedWork || workItem?.plannedWork || 0,
|
||||||
|
completedWork:
|
||||||
|
workItem?.workItem?.completedWork || workItem?.completedWork || 0,
|
||||||
|
comment:
|
||||||
|
workItem?.workItem?.description || workItem?.description || ""
|
||||||
|
});
|
||||||
|
return () => reset();
|
||||||
|
}, [activities, workItem]);
|
||||||
|
|
||||||
|
const ISselectedActivity = watch("activityID");
|
||||||
|
useEffect(() => {
|
||||||
|
if (ISselectedActivity) {
|
||||||
|
const selected = activities.find((a) => a.id === ISselectedActivity);
|
||||||
|
setSelectedActivity(selected || null);
|
||||||
|
setActivityUnit(selected?.unitOfMeasurement);
|
||||||
|
}
|
||||||
|
}, [ISselectedActivity, activities]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="row g-2 p-2 p-md-1" onSubmit={handleSubmit(onSubmitForm)}>
|
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
|
||||||
<div className="text-center mb-1">
|
<div className="modal-content">
|
||||||
<h5 className="mb-1">Manage Task</h5>
|
<div className="modal-body">
|
||||||
</div>
|
<div className="row">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-close"
|
||||||
|
aria-label="Close"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
<div className="text-center mb-1">
|
||||||
|
<h5 className="mb-1">Manage Task</h5>
|
||||||
|
</div>
|
||||||
|
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
|
||||||
|
{/* Select Building */}
|
||||||
|
<div className="col-6 col-md-6">
|
||||||
|
<label className="form-label" htmlFor="buildingID">
|
||||||
|
Select Building
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
value={building?.name}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="row g-2">
|
{/* Select Floor */}
|
||||||
<div className="col-12 col-md-6">
|
<div className="col-6 col-md-6">
|
||||||
<label className="form-label">Select Building</label>
|
<label className="form-label" htmlFor="floorId">
|
||||||
<input
|
Select Floor
|
||||||
className="form-control form-control-sm"
|
</label>
|
||||||
value={building?.buildingName}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12 col-md-6">
|
<input
|
||||||
<label className="form-label">Select Floor</label>
|
type="text"
|
||||||
<input
|
className="form-control form-control-sm"
|
||||||
className="form-control form-control-sm"
|
value={floor?.floorName}
|
||||||
value={floor?.floorName}
|
disabled
|
||||||
disabled
|
/>
|
||||||
/>
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label" htmlFor="workAreaId">
|
||||||
|
Select Work Area
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
value={workArea?.areaName}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Select Activity */}
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label" htmlFor="activityID">
|
||||||
|
Select Activity
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="activityID"
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
{...register("activityID")}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<option value="">Loading...</option>
|
||||||
|
) : (
|
||||||
|
<option disabled>Select Activity</option>
|
||||||
|
)}
|
||||||
|
{activities &&
|
||||||
|
activities.length > 0 &&
|
||||||
|
activities
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) =>
|
||||||
|
(a.activityName || "")?.localeCompare(
|
||||||
|
b.activityName || ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((activity) => (
|
||||||
|
<option key={activity.id} value={activity.id}>
|
||||||
|
{activity.activityName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
{!loading && activities.length === 0 && (
|
||||||
|
<option disabled>No activities available</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{errors.activityID && (
|
||||||
|
<p className="danger-text">{errors.activityID.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Select Category */}
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label" htmlFor="activityID">
|
||||||
|
Select Work Category
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="workCategoryId"
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
{...register("workCategoryId")}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<option value="">Loading...</option>
|
||||||
|
) : (
|
||||||
|
<option disabled>Select Category</option>
|
||||||
|
)}
|
||||||
|
{categories &&
|
||||||
|
categories.length > 0 &&
|
||||||
|
categories
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) =>
|
||||||
|
(a.name || "")?.localeCompare(
|
||||||
|
b.name || ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((category) => (
|
||||||
|
<option key={category.id} value={category.id}>
|
||||||
|
{category.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
{!loading && categories.length === 0 && (
|
||||||
|
<option disabled>No categories available</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{errors.workCategoryId && (
|
||||||
|
<p className="danger-text">{errors.workCategoryId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Planned Work */}
|
||||||
|
{/* {ISselectedActivity && ( */}
|
||||||
|
<div className="col-5 col-md-5">
|
||||||
|
<label className="form-label" htmlFor="plannedWork">
|
||||||
|
Planned Work
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
{...register("plannedWork", { valueAsNumber: true })}
|
||||||
|
type="number"
|
||||||
|
className="form-control form-control-sm me-2"
|
||||||
|
placeholder="Planned Work"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{errors.plannedWork && (
|
||||||
|
<p className="danger-text">{errors.plannedWork.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* )} */}
|
||||||
|
|
||||||
|
{/* Completed Work */}
|
||||||
|
{/* {ISselectedActivity && ( */}
|
||||||
|
<div className="col-5 col-md-5">
|
||||||
|
<label className="form-label" htmlFor="completedWork">
|
||||||
|
Completed Work
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
{...register("completedWork", { valueAsNumber: true })}
|
||||||
|
type="number"
|
||||||
|
className="form-control form-control-sm me-2"
|
||||||
|
placeholder="Completed Work"
|
||||||
|
disabled={getValues("completedWork") > 0}
|
||||||
|
/>
|
||||||
|
{errors.completedWork && (
|
||||||
|
<p className="danger-text">{errors.completedWork.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* )} */}
|
||||||
|
|
||||||
|
{/* Unit */}
|
||||||
|
{/* {ISselectedActivity && ( */}
|
||||||
|
<div className="col-2 col-md-2">
|
||||||
|
<label className="form-label" htmlFor="unit">
|
||||||
|
Unit
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
className="form-control form-control-sm me-2"
|
||||||
|
value={selectedActivity?.unitOfMeasurement || ""}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* )} */}
|
||||||
|
|
||||||
|
|
||||||
|
<div className="col-12">
|
||||||
|
<label
|
||||||
|
className="form-text fs-7 m-1 text-lg text-dark"
|
||||||
|
htmlFor="descriptionTextarea"
|
||||||
|
>
|
||||||
|
Comment
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
{...register("comment")}
|
||||||
|
className="form-control"
|
||||||
|
id="descriptionTextarea"
|
||||||
|
rows="2"
|
||||||
|
/>
|
||||||
|
{errors.comment && (
|
||||||
|
<div className="danger-text">
|
||||||
|
{errors.comment.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<button type="submit" className="btn btn-sm btn-primary me-3" disabled={activities.length === 0 || isSubmitting}>
|
||||||
|
{isSubmitting ? "Please Wait.." : "Edit Task"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-label-secondary"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">Select Work Area</label>
|
|
||||||
<input
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
value={workArea?.areaName}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">Select Activity</label>
|
|
||||||
<select
|
|
||||||
{...register("activityID")}
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
>
|
|
||||||
<option disabled>Select Activity</option>
|
|
||||||
{loadingActivities ? (
|
|
||||||
<option>Loading...</option>
|
|
||||||
) : (
|
|
||||||
sortedActivities.map((a) => (
|
|
||||||
<option key={a.id} value={a.id}>
|
|
||||||
{a.activityName}
|
|
||||||
</option>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
{errors.activityID && (
|
|
||||||
<p className="danger-text">{errors.activityID.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">Select Work Category</label>
|
|
||||||
<select
|
|
||||||
{...register("workCategoryId")}
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
>
|
|
||||||
<option disabled>Select Category</option>
|
|
||||||
{loadingCategories ? (
|
|
||||||
<option>Loading...</option>
|
|
||||||
) : (
|
|
||||||
sortedCategories.map((c) => (
|
|
||||||
<option key={c.id} value={c.id}>
|
|
||||||
{c.name}
|
|
||||||
</option>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
{errors.workCategoryId && (
|
|
||||||
<p className="danger-text">{errors.workCategoryId.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-5">
|
|
||||||
<label className="form-label">Planned Work</label>
|
|
||||||
<input
|
|
||||||
{...register("plannedWork", { valueAsNumber: true })}
|
|
||||||
type="number"
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
/>
|
|
||||||
{errors.plannedWork && (
|
|
||||||
<p className="danger-text">{errors.plannedWork.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-5">
|
|
||||||
<label className="form-label">Completed Work</label>
|
|
||||||
<input
|
|
||||||
{...register("completedWork", { valueAsNumber: true })}
|
|
||||||
type="number"
|
|
||||||
disabled={getValues("completedWork") > 0}
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
/>
|
|
||||||
{errors.completedWork && (
|
|
||||||
<p className="danger-text">{errors.completedWork.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-2">
|
|
||||||
<label className="form-label">Unit</label>
|
|
||||||
<input
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
disabled
|
|
||||||
value={selectedActivity?.unitOfMeasurement || ""}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">Comment</label>
|
|
||||||
<textarea {...register("comment")} rows="2" className="form-control" />
|
|
||||||
{errors.comment && (
|
|
||||||
<div className="danger-text">{errors.comment.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12 text-center">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-sm btn-primary me-2"
|
|
||||||
disabled={isPending}
|
|
||||||
>
|
|
||||||
{isPending ? "Please Wait..." : "Edit Task"}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-sm btn-label-secondary"
|
|
||||||
onClick={onClose}
|
|
||||||
disabled={isPending}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,13 @@ const Floor = ({ floor, workAreas, forBuilding }) => {
|
|||||||
<tr>
|
<tr>
|
||||||
<td colSpan="4" className="text-start table-cell">
|
<td colSpan="4" className="text-start table-cell">
|
||||||
<div className="row ps-2">
|
<div className="row ps-2">
|
||||||
|
{/* <div className="col-1 col-md-1 d-flex justify-content-between align-items-center " >
|
||||||
|
<button
|
||||||
|
className="btn me-2"
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div> */}
|
||||||
<div className="col-12 ps-8">
|
<div className="col-12 ps-8">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="d-flex col-5">
|
<div className="d-flex col-5">
|
||||||
|
|||||||
@ -1,189 +1,236 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import showToast from "../../../services/toastService";
|
import showToast from "../../../services/toastService";
|
||||||
import { useManageProjectInfra } from "../../../hooks/useProjects";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
|
|
||||||
// Schema
|
|
||||||
const floorSchema = z.object({
|
const floorSchema = z.object({
|
||||||
buildingId: z
|
buildingId: z
|
||||||
.string()
|
.string()
|
||||||
.refine((val) => val !== "0", { message: "Building is required" }),
|
.refine((val) => val !== "0", {
|
||||||
|
message: "Building is required",
|
||||||
|
}),
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
floorName: z.string().min(1, "Floor Name is required"),
|
floorName: z.string().min(1, "Floor Name is required"),
|
||||||
});
|
});
|
||||||
const defaultValues = {
|
|
||||||
|
const defaultModel = {
|
||||||
id: "0",
|
id: "0",
|
||||||
floorName: "",
|
floorName: "",
|
||||||
buildingId: "0",
|
buildingId: "0",
|
||||||
};
|
};
|
||||||
|
|
||||||
const FloorModel = ({ project, onClose, onSubmit }) => {
|
const FloorModel = ({
|
||||||
const selectedProject = useSelector(
|
project,
|
||||||
(store) => store.localVariables.projectId
|
onClose,
|
||||||
);
|
onSubmit,
|
||||||
const [selectedBuilding, setSelectedBuilding] = useState(null);
|
clearTrigger,
|
||||||
|
onClearComplete,
|
||||||
|
}) => {
|
||||||
|
const [formData, setFormData] = useState(defaultModel);
|
||||||
|
const [selectedBuilding, setSelectedBuilding] = useState({});
|
||||||
|
const [buildings, setBuildings] = useState(project?.buildings || []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
watch,
|
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
defaultValues,
|
|
||||||
resolver: zodResolver(floorSchema),
|
resolver: zodResolver(floorSchema),
|
||||||
});
|
defaultValues: defaultModel,
|
||||||
const watchId = watch("id");
|
|
||||||
const watchBuildingId = watch("buildingId");
|
|
||||||
const { mutate: ManageFloor, isPending } = useManageProjectInfra({
|
|
||||||
onSuccessCallback: (data, variables) => {
|
|
||||||
showToast(
|
|
||||||
watchId != "0"
|
|
||||||
? "Floor updated Successfully"
|
|
||||||
: "Floor created Successfully",
|
|
||||||
"success"
|
|
||||||
);
|
|
||||||
reset({ Id: "0", name: "", description: "" });
|
|
||||||
// onClose?.();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reset(defaultValues);
|
if (clearTrigger) {
|
||||||
}, []);
|
reset(defaultModel);
|
||||||
useEffect(() => {
|
onClearComplete();
|
||||||
const building = project?.find((b) => b.id === watchBuildingId);
|
}
|
||||||
setSelectedBuilding(building || null);
|
}, [clearTrigger, onClearComplete, reset]);
|
||||||
}, [watchBuildingId, project]);
|
|
||||||
|
|
||||||
const handleBuildingChange = (e) => {
|
const handleBuildigChange = (e) => {
|
||||||
const id = e.target.value;
|
const buildingId = e.target.value;
|
||||||
setValue("buildingId", id, { shouldValidate: true });
|
const building = buildings.find((b) => b.id === String(buildingId));
|
||||||
setValue("id", "0");
|
|
||||||
setValue("floorName", "");
|
if (building) {
|
||||||
|
setSelectedBuilding(building);
|
||||||
|
setFormData({
|
||||||
|
id: "",
|
||||||
|
floorName: "",
|
||||||
|
buildingId: building.id,
|
||||||
|
});
|
||||||
|
setValue("buildingId", building.id, { shouldValidate: true }); // ✅ trigger validation
|
||||||
|
setValue("id", "0");
|
||||||
|
} else {
|
||||||
|
setSelectedBuilding({});
|
||||||
|
setFormData({
|
||||||
|
id: "",
|
||||||
|
floorName: "",
|
||||||
|
buildingId: "0",
|
||||||
|
});
|
||||||
|
setValue("buildingId", "0", { shouldValidate: true }); // ✅ trigger validation
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFloorChange = (e) => {
|
const handleFloorChange = (e) => {
|
||||||
const id = e.target.value;
|
const id = e.target.value;
|
||||||
setValue("id", id);
|
const floor = selectedBuilding.floors?.find((b) => b.id === String(id));
|
||||||
|
|
||||||
const floor = selectedBuilding?.floors?.find((f) => f.id === id);
|
|
||||||
if (floor) {
|
if (floor) {
|
||||||
|
setFormData({
|
||||||
|
id: floor.id,
|
||||||
|
floorName: floor.floorName,
|
||||||
|
buildingId: selectedBuilding.id,
|
||||||
|
});
|
||||||
setValue("floorName", floor.floorName);
|
setValue("floorName", floor.floorName);
|
||||||
} else {
|
} else {
|
||||||
|
setFormData({
|
||||||
|
id: "0",
|
||||||
|
floorName: "",
|
||||||
|
buildingId: selectedBuilding.id,
|
||||||
|
});
|
||||||
setValue("floorName", "");
|
setValue("floorName", "");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFormSubmit = (data) => {
|
const onFormSubmit = (data) => {
|
||||||
const isEdit = data.id !== "0";
|
if (data.id === "0") {
|
||||||
const payload = {
|
data.id = null;
|
||||||
...data,
|
}
|
||||||
id: isEdit ? data.id : null,
|
|
||||||
};
|
|
||||||
let infraObject = [
|
|
||||||
{
|
|
||||||
building: null,
|
|
||||||
floor: payload,
|
|
||||||
workArea: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
ManageFloor({ infraObject, projectId: selectedProject });
|
onSubmit(data);
|
||||||
|
reset({ floorName: "" });
|
||||||
|
|
||||||
|
if (data.id !== null) {
|
||||||
|
showToast("Floor updated successfully.", "success");
|
||||||
|
} else {
|
||||||
|
showToast("Floor created successfully.", "success");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onFormSubmit)}>
|
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
|
||||||
<div className="text-center mb-1">
|
<div className="modal-content">
|
||||||
<h5 className="mb-1">Manage Floor</h5>
|
<div className="modal-body">
|
||||||
</div>
|
<div className="row">
|
||||||
<div className="col-12">
|
<button
|
||||||
<label className="form-label">Select Building</label>
|
type="button"
|
||||||
<select
|
className="btn-close"
|
||||||
{...register("buildingId")}
|
aria-label="Close"
|
||||||
className="form-select form-select-sm"
|
onClick={onClose}
|
||||||
onChange={handleBuildingChange}
|
|
||||||
>
|
|
||||||
<option value="0">Select Building</option>
|
|
||||||
{project?.length > 0 &&
|
|
||||||
project
|
|
||||||
.filter((b) => b.buildingName)
|
|
||||||
.sort((a, b) => a.buildingName.localeCompare(b.buildingName))
|
|
||||||
.map((b) => (
|
|
||||||
<option key={b.id} value={b.id}>
|
|
||||||
{b.buildingName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.buildingId && (
|
|
||||||
<p className="danger-text">{errors.buildingId.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{watchBuildingId !== "0" && (
|
|
||||||
<>
|
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">Select Floor</label>
|
|
||||||
<select
|
|
||||||
{...register("id")}
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
onChange={handleFloorChange}
|
|
||||||
>
|
|
||||||
<option value="0">Add New Floor</option>
|
|
||||||
{selectedBuilding?.floors?.length > 0 &&
|
|
||||||
selectedBuilding.floors
|
|
||||||
.filter((f) => f.floorName)
|
|
||||||
.sort((a, b) => a.floorName.localeCompare(b.floorName))
|
|
||||||
.map((f) => (
|
|
||||||
<option key={f.id} value={f.id}>
|
|
||||||
{f.floorName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">
|
|
||||||
{watchId !== "0" ? "Edit Floor Name" : "New Floor Name"}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
{...register("floorName")}
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
placeholder="Floor Name"
|
|
||||||
/>
|
/>
|
||||||
{errors.floorName && (
|
<div className="text-center mb-1">
|
||||||
<p className="danger-text">{errors.floorName.message}</p>
|
<h5 className="mb-1">Manage Floors - {project.name}</h5>
|
||||||
)}
|
</div>
|
||||||
</div>
|
<form className="row g-2" onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
</>
|
<div className="col-12 col-md-12">
|
||||||
)}
|
<label className="form-label" htmlFor="buildingId">
|
||||||
|
Select Building
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="buildingId"
|
||||||
|
className="select2 form-select form-select-sm"
|
||||||
|
aria-label="Select Building"
|
||||||
|
{...register("buildingId")}
|
||||||
|
onChange={handleBuildigChange}
|
||||||
|
>
|
||||||
|
<option value="0">Select Building</option>
|
||||||
|
{buildings?.length > 0 &&
|
||||||
|
buildings
|
||||||
|
.filter((building) => building?.name)
|
||||||
|
.sort((a, b) =>
|
||||||
|
(a.name || "")?.localeCompare(b.name || "")
|
||||||
|
)
|
||||||
|
.map((building) => (
|
||||||
|
<option key={building.id} value={building.id}>
|
||||||
|
{building.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
{buildings?.length === 0 && (
|
||||||
|
<option disabled>No buildings found</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
{errors.buildingId && (
|
||||||
|
<p className="text-danger">{errors.buildingId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-center">
|
{formData.buildingId !== "0" && (
|
||||||
<button
|
<>
|
||||||
type="submit"
|
<div className="col-12 col-md-12">
|
||||||
className="btn btn-sm btn-primary me-3"
|
<label className="form-label">Select Floor</label>
|
||||||
disabled={isPending}
|
<select
|
||||||
>
|
id="id"
|
||||||
{isPending
|
className="select2 form-select form-select-sm"
|
||||||
? "Please Wait"
|
aria-label="Select Floor"
|
||||||
: watchId !== "0"
|
{...register("id")}
|
||||||
? "Edit Floor"
|
onChange={handleFloorChange}
|
||||||
: "Add Floor"}
|
>
|
||||||
</button>
|
<option value="0">Add New Floor</option>
|
||||||
<button
|
{selectedBuilding?.floors?.length > 0 &&
|
||||||
type="button"
|
[...selectedBuilding.floors]
|
||||||
className="btn btn-sm btn-label-secondary"
|
.filter((floor) => floor?.floorName)
|
||||||
disabled={isPending}
|
.sort((a, b) =>
|
||||||
onClick={onClose}
|
(a.floorName || "")?.localeCompare(
|
||||||
>
|
b.floorName || ""
|
||||||
Cancel
|
)
|
||||||
</button>
|
)
|
||||||
|
.map((floor) => (
|
||||||
|
<option key={floor.id} value={floor.id}>
|
||||||
|
{floor.floorName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
{selectedBuilding?.floors?.length === 0 && (
|
||||||
|
<option disabled>No floors found</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
{errors.id && (
|
||||||
|
<p className="text-danger">{errors.id.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">
|
||||||
|
{formData.id !== "0" ? "Modify " : "Enter "} Floor Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="floorName"
|
||||||
|
className="form-control form-control-sm me-2"
|
||||||
|
placeholder="Floor Name"
|
||||||
|
{...register("floorName")}
|
||||||
|
/>
|
||||||
|
{errors.floorName && (
|
||||||
|
<p className="text-danger">
|
||||||
|
{errors.floorName.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<button type="submit" className="btn btn-sm btn-primary me-3">
|
||||||
|
{formData.id !== "0" && formData.id !== ""
|
||||||
|
? "Edit Floor"
|
||||||
|
: "Add Floor"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-label-secondary"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -100,7 +100,14 @@ const InfraTable = ({ buildings, projectId, signalRHandler }) => {
|
|||||||
No floors have been added yet. Start by adding floors to manage
|
No floors have been added yet. Start by adding floors to manage
|
||||||
this building.
|
this building.
|
||||||
</p>
|
</p>
|
||||||
|
{/* <button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-xs btn-primary"
|
||||||
|
onClick={() => handleAddFloor(building)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
|
Add Floors
|
||||||
|
</button> */}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
useActivitiesMaster,
|
useActivitiesMaster,
|
||||||
useWorkCategoriesMaster,
|
useWorkCategoriesMaster,
|
||||||
} from "../../../hooks/masterHook/useMaster";
|
} from "../../../hooks/masterHook/useMaster";
|
||||||
import { useManageTask } from "../../../hooks/useProjects";
|
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
buildingID: z.string().min(1, "Building is required"),
|
buildingID: z.string().min(1, "Building is required"),
|
||||||
@ -15,262 +14,430 @@ const taskSchema = z.object({
|
|||||||
activityID: z.string().min(1, "Activity is required"),
|
activityID: z.string().min(1, "Activity is required"),
|
||||||
workCategoryId: z.string().min(1, "Work Category is required"),
|
workCategoryId: z.string().min(1, "Work Category is required"),
|
||||||
plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
|
plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
|
||||||
completedWork: z.number().min(0, "Completed Work must be 0 or more"),
|
completedWork: z.number().min( 0, "Completed Work must be greater than 0" ),
|
||||||
comment: z.string(),
|
comment:z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultModel = {
|
const defaultModel = {
|
||||||
id: null,
|
id: null,
|
||||||
buildingID: "",
|
buildingID: "0",
|
||||||
floorId: "",
|
floorId: "0",
|
||||||
workAreaId: "",
|
workAreaId: "0",
|
||||||
activityID: "",
|
activityID: null,
|
||||||
workCategoryId: "",
|
workCategoryId: "",
|
||||||
plannedWork: 0,
|
plannedWork: 0,
|
||||||
completedWork: 0,
|
completedWork: 0,
|
||||||
comment: "",
|
comment:""
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskModel = ({ project, onSubmit, onClose }) => {
|
const TaskModel = ({
|
||||||
const { activities, loading: activityLoading } = useActivitiesMaster();
|
project,
|
||||||
const { categories, categoryLoading } = useWorkCategoriesMaster();
|
onSubmit,
|
||||||
|
clearTrigger,
|
||||||
|
onClearComplete,
|
||||||
|
onClose,
|
||||||
|
}) => {
|
||||||
|
const [formData, setFormData] = useState(defaultModel);
|
||||||
|
const [selectedBuilding, setSelectedBuilding] = useState(null);
|
||||||
|
const [selectedFloor, setSelectedFloor] = useState(null);
|
||||||
|
const [selectedWorkArea, setSelectedWorkArea] = useState(null);
|
||||||
|
const [selectedActivity, setSelectedActivity] = useState(null);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [activityData, setActivityData] = useState([]);
|
||||||
|
const [categoryData, setCategoryData] = useState([]);
|
||||||
|
const { activities, loading, error } = useActivitiesMaster();
|
||||||
|
const { categories, categoryLoading, categoryError } =
|
||||||
|
useWorkCategoriesMaster();
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
watch,
|
|
||||||
setValue,
|
|
||||||
reset,
|
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
setValue,
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(taskSchema),
|
resolver: zodResolver(taskSchema),
|
||||||
defaultValues: defaultModel,
|
defaultValues: defaultModel,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
||||||
const [activityData, setActivityData] = useState([]);
|
|
||||||
const [categoryData, setCategoryData] = useState([]);
|
|
||||||
|
|
||||||
const watchBuildingId = watch("buildingID");
|
|
||||||
const watchFloorId = watch("floorId");
|
|
||||||
const watchWorkAreaId = watch("workAreaId");
|
|
||||||
const watchActivityId = watch("activityID");
|
|
||||||
const watchCategoryId = watch("workCategoryId");
|
|
||||||
|
|
||||||
const selectedBuilding = project?.find((b) => b.id === watchBuildingId);
|
|
||||||
const selectedFloor = selectedBuilding?.floors?.find(
|
|
||||||
(f) => f.id === watchFloorId
|
|
||||||
);
|
|
||||||
const selectedWorkArea = selectedFloor?.workAreas?.find(
|
|
||||||
(w) => w.id === watchWorkAreaId
|
|
||||||
);
|
|
||||||
const selectedActivity = activityData?.find((a) => a.id === watchActivityId);
|
|
||||||
const selectedCategory = categoryData?.find((c) => c.id === watchCategoryId);
|
|
||||||
|
|
||||||
const { mutate: CreateTask, isPending } = useManageTask({
|
|
||||||
onSuccessCallback: () => onClose?.(),
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reset(defaultModel);
|
resetForm();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleBuildingChange = (e) => {
|
||||||
if (Array.isArray(activities) && activities.length > 0) {
|
const { value } = e.target;
|
||||||
setActivityData(activities);
|
const building = project.buildings.find((b) => b.id === String(value));
|
||||||
}
|
setSelectedBuilding(building);
|
||||||
}, [activities]);
|
setSelectedFloor(null);
|
||||||
|
setSelectedWorkArea(null);
|
||||||
useEffect(() => {
|
setSelectedActivity(null);
|
||||||
if (Array.isArray(categories) && categories.length > 0) {
|
reset({
|
||||||
const sorted = [...categories].sort((a, b) =>
|
...defaultModel,
|
||||||
(a?.name || "").localeCompare(b?.name || "")
|
buildingID: value,
|
||||||
);
|
});
|
||||||
setCategoryData(sorted);
|
|
||||||
setValue("workCategoryId", sorted?.[0]?.id?.toString() ?? "");
|
|
||||||
}
|
|
||||||
}, [categories]);
|
|
||||||
|
|
||||||
const onSubmitForm = async (data) => {
|
|
||||||
const payload = [data];
|
|
||||||
CreateTask(payload);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFloorChange = (e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
const floor = selectedBuilding.floors.find((b) => b.id === String(value));
|
||||||
|
setSelectedFloor(floor);
|
||||||
|
setSelectedWorkArea(null);
|
||||||
|
setSelectedActivity(null);
|
||||||
|
reset((prev) => ({
|
||||||
|
...prev,
|
||||||
|
floorId: value,
|
||||||
|
workAreaId: 0,
|
||||||
|
activityID: "",
|
||||||
|
workCategoryId: categoryData?.[0]?.id?.toString() ?? "",
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWorkAreaChange = (e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
const workArea = selectedFloor.workAreas.find(
|
||||||
|
(b) => b.id === String(value)
|
||||||
|
);
|
||||||
|
setSelectedWorkArea(workArea);
|
||||||
|
reset((prev) => ({
|
||||||
|
...prev,
|
||||||
|
workAreaId: String(value),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleActivityChange = (e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
const activity = activityData.find((b) => b.id === String(value));
|
||||||
|
setSelectedActivity(activity);
|
||||||
|
reset((prev) => ({
|
||||||
|
...prev,
|
||||||
|
activityID: String(value),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCategoryChange = (e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
const category = categoryData.find((b) => b.id === String(value));
|
||||||
|
setSelectedCategory(category);
|
||||||
|
reset((prev) => ({
|
||||||
|
...prev,
|
||||||
|
workCategoryId: String(value),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmitForm = async (data) => {
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
await onSubmit(data);
|
||||||
|
setValue("plannedWork", 0);
|
||||||
|
setValue("completedWork", 0);
|
||||||
|
setValue("activityID", 0);
|
||||||
|
setValue("workCategoryId", categoryData?.[0]?.id?.toString() ?? "");
|
||||||
|
setIsSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setFormData(defaultModel);
|
||||||
|
setSelectedBuilding(null);
|
||||||
|
setSelectedFloor(null);
|
||||||
|
setSelectedWorkArea(null);
|
||||||
|
setSelectedActivity(null);
|
||||||
|
setSelectedCategory(categoryData?.[0]?.id?.toString() ?? "");
|
||||||
|
reset(defaultModel);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && Array.isArray(activities) && activities.length > 0) {
|
||||||
|
setActivityData(activities);
|
||||||
|
}
|
||||||
|
}, [activities, loading]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!categoryLoading &&
|
||||||
|
Array.isArray(categories) &&
|
||||||
|
categories.length > 0
|
||||||
|
) {
|
||||||
|
const newCategories = categories?.slice()?.sort((a, b) => {
|
||||||
|
const nameA = a?.name || "";
|
||||||
|
const nameB = b?.name || "";
|
||||||
|
return nameA?.localeCompare(nameB);
|
||||||
|
});
|
||||||
|
setCategoryData(newCategories);
|
||||||
|
setSelectedCategory(newCategories[0])
|
||||||
|
}
|
||||||
|
}, [categories, categoryLoading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
|
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
|
||||||
<div className="text-center mb-1">
|
<div className="modal-content">
|
||||||
<h5 className="mb-1">Manage Task</h5>
|
<div className="modal-body">
|
||||||
</div>
|
<div className="row">
|
||||||
<div className="col-6">
|
<button
|
||||||
<label className="form-label">Select Building</label>
|
type="button"
|
||||||
<select
|
className="btn-close"
|
||||||
className="form-select form-select-sm"
|
aria-label="Close"
|
||||||
{...register("buildingID")}
|
onClick={onClose}
|
||||||
>
|
|
||||||
<option value="">Select Building</option>
|
|
||||||
{project
|
|
||||||
?.filter((b) => b?.buildingName)
|
|
||||||
?.sort((a, b) => a?.buildingName.localeCompare(b.buildingName))
|
|
||||||
?.map((b) => (
|
|
||||||
<option key={b.id} value={b.id}>
|
|
||||||
{b.buildingName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.buildingID && (
|
|
||||||
<p className="danger-text">{errors.buildingID.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedBuilding && (
|
|
||||||
<div className="col-6">
|
|
||||||
<label className="form-label">Select Floor</label>
|
|
||||||
<select
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
{...register("floorId")}
|
|
||||||
>
|
|
||||||
<option value="">Select Floor</option>
|
|
||||||
{selectedBuilding.floors
|
|
||||||
?.sort((a, b) => a.floorName.localeCompare(b.floorName))
|
|
||||||
?.map((f) => (
|
|
||||||
<option key={f.id} value={f.id}>
|
|
||||||
{f.floorName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.floorId && (
|
|
||||||
<p className="danger-text">{errors.floorId.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedFloor && (
|
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">Select Work Area</label>
|
|
||||||
<select
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
{...register("workAreaId")}
|
|
||||||
>
|
|
||||||
<option value="">Select Work Area</option>
|
|
||||||
{selectedFloor.workAreas
|
|
||||||
?.sort((a, b) => a.areaName.localeCompare(b.areaName))
|
|
||||||
?.map((w) => (
|
|
||||||
<option key={w.id} value={w.id}>
|
|
||||||
{w.areaName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.workAreaId && (
|
|
||||||
<p className="danger-text">{errors.workAreaId.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedWorkArea && (
|
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">Select Activity</label>
|
|
||||||
<select
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
{...register("activityID")}
|
|
||||||
>
|
|
||||||
<option value="">Select Activity</option>
|
|
||||||
{activityData.map((a) => (
|
|
||||||
<option key={a.id} value={a.id}>
|
|
||||||
{a.activityName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.activityID && (
|
|
||||||
<p className="danger-text">{errors.activityID.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedWorkArea && (
|
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">Select Work Category</label>
|
|
||||||
<select
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
{...register("workCategoryId")}
|
|
||||||
>
|
|
||||||
{categoryData.map((c) => (
|
|
||||||
<option key={c.id} value={c.id}>
|
|
||||||
{c.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.workCategoryId && (
|
|
||||||
<p className="danger-text">{errors.workCategoryId.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedActivity && selectedCategory && (
|
|
||||||
<>
|
|
||||||
<div className="col-5">
|
|
||||||
<label className="form-label">Planned Work</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
{...register("plannedWork", { valueAsNumber: true })}
|
|
||||||
/>
|
/>
|
||||||
{errors.plannedWork && (
|
<div className="text-center mb-1">
|
||||||
<p className="danger-text">{errors.plannedWork.message}</p>
|
<h5 className="mb-1">Manage Task</h5>
|
||||||
)}
|
</div>
|
||||||
</div>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
|
||||||
<div className="col-5">
|
{/* Select Building */}
|
||||||
<label className="form-label">Completed Work</label>
|
<div className="col-6 col-md-6">
|
||||||
<input
|
<label className="form-label" htmlFor="buildingID">
|
||||||
type="number"
|
Select Building
|
||||||
className="form-control form-control-sm"
|
</label>
|
||||||
{...register("completedWork", { valueAsNumber: true })}
|
<select
|
||||||
/>
|
id="buildingID"
|
||||||
{errors.completedWork && (
|
className="form-select form-select-sm"
|
||||||
<p className="danger-text">{errors.completedWork.message}</p>
|
{...register("buildingID")}
|
||||||
)}
|
onChange={handleBuildingChange}
|
||||||
</div>
|
>
|
||||||
<div className="col-2">
|
<option value="0">Select Building</option>
|
||||||
<label className="form-label">Unit</label>
|
{project.buildings
|
||||||
<input
|
?.filter((building) => building?.name) // Ensure valid name
|
||||||
type="text"
|
?.sort((a, b) => a.name?.localeCompare(b.name))
|
||||||
className="form-control form-control-sm"
|
?.map((building) => (
|
||||||
disabled
|
<option key={building.id} value={building.id}>
|
||||||
value={selectedActivity?.unitOfMeasurement || ""}
|
{building.name}
|
||||||
/>
|
</option>
|
||||||
</div>
|
))}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedActivity && selectedCategory && (
|
{project.buildings?.filter((building) => building?.name)
|
||||||
<div className="col-12">
|
.length === 0 && (
|
||||||
<label className="form-label">Comment</label>
|
<option disabled>No buildings found</option>
|
||||||
<textarea
|
)}
|
||||||
className="form-control"
|
</select>
|
||||||
rows="2"
|
{errors.buildingID && (
|
||||||
{...register("comment")}
|
<p className="danger-text">{errors.buildingID.message}</p>
|
||||||
/>
|
)}
|
||||||
{errors.comment && (
|
</div>
|
||||||
<p className="danger-text">{errors.comment.message}</p>
|
|
||||||
)}
|
{/* Select Floor */}
|
||||||
|
{selectedBuilding && (
|
||||||
|
<div className="col-6 col-md-6">
|
||||||
|
<label className="form-label" htmlFor="floorId">
|
||||||
|
Select Floor
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="floorId"
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
{...register("floorId")}
|
||||||
|
onChange={handleFloorChange}
|
||||||
|
>
|
||||||
|
<option value="0">Select Floor</option>
|
||||||
|
{selectedBuilding.floors
|
||||||
|
?.filter(
|
||||||
|
(floor) =>
|
||||||
|
floor?.floorName && Array.isArray(floor.workAreas)
|
||||||
|
)
|
||||||
|
?.sort((a, b) => a.floorName?.localeCompare(b.floorName))
|
||||||
|
?.map((floor) => (
|
||||||
|
<option key={floor.id} value={floor.id}>
|
||||||
|
{floor.floorName} - ({floor.workAreas.length} Work
|
||||||
|
Areas)
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{selectedBuilding.floors?.filter(
|
||||||
|
(floor) =>
|
||||||
|
floor?.floorName && Array.isArray(floor.workAreas)
|
||||||
|
).length === 0 && <option disabled>No floors found</option>}
|
||||||
|
</select>
|
||||||
|
{errors.floorId && (
|
||||||
|
<p className="danger-text">{errors.floorId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedFloor && (
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label" htmlFor="workAreaId">
|
||||||
|
Select Work Area
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="workAreaId"
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
{...register("workAreaId")}
|
||||||
|
onChange={handleWorkAreaChange}
|
||||||
|
>
|
||||||
|
<option value="0">Select Work Area</option>
|
||||||
|
{selectedFloor.workAreas
|
||||||
|
?.filter((workArea) => workArea?.areaName)
|
||||||
|
?.sort((a, b) => a.areaName?.localeCompare(b.areaName))
|
||||||
|
?.map((workArea) => (
|
||||||
|
<option key={workArea.id} value={workArea.id}>
|
||||||
|
{workArea.areaName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{selectedFloor.workAreas?.filter(
|
||||||
|
(workArea) => workArea?.areaName
|
||||||
|
).length === 0 && (
|
||||||
|
<option disabled>No work areas found</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
{errors.workAreaId && (
|
||||||
|
<p className="danger-text">{errors.workAreaId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedWorkArea && (
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">Select Activity</label>
|
||||||
|
<select
|
||||||
|
id="activityID"
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
{...register("activityID")}
|
||||||
|
onChange={handleActivityChange}
|
||||||
|
>
|
||||||
|
<option value="">Select Activity</option>
|
||||||
|
{activityData &&
|
||||||
|
activityData.length > 0 &&
|
||||||
|
activityData
|
||||||
|
?.slice()
|
||||||
|
?.sort((a, b) => {
|
||||||
|
const nameA = a?.activityName || "";
|
||||||
|
const nameB = b?.activityName || "";
|
||||||
|
return nameA?.localeCompare(nameB);
|
||||||
|
})
|
||||||
|
?.map((activity) => (
|
||||||
|
<option key={activity.id} value={activity.id}>
|
||||||
|
{activity.activityName ||
|
||||||
|
`Unnamed (id: ${activity.id})`}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
{!loading && activities.length === 0 && (
|
||||||
|
<option disabled>No activities available</option>
|
||||||
|
)}
|
||||||
|
{loading && <option disabled>Loading...</option>}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{errors.activityID && (
|
||||||
|
<p className="danger-text">{errors.activityID.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedWorkArea && (
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">Select Work Category</label>
|
||||||
|
<select
|
||||||
|
id="workCategoryId"
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
{...register("workCategoryId")}
|
||||||
|
onChange={handleCategoryChange}
|
||||||
|
>
|
||||||
|
{categoryData &&
|
||||||
|
categoryData.length > 0 &&
|
||||||
|
categoryData
|
||||||
|
?.map((category) => (
|
||||||
|
<option key={category.id} value={category.id}>
|
||||||
|
{category.name || `Unnamed (id: ${category.id})`}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
{!categoryLoading && categories.length === 0 && (
|
||||||
|
<option disabled>No activities available</option>
|
||||||
|
)}
|
||||||
|
{categoryLoading && <option disabled>Loading...</option>}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{errors.workCategoryId && (
|
||||||
|
<p className="danger-text">
|
||||||
|
{errors.workCategoryId.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedActivity && selectedCategory && (
|
||||||
|
<div className="col-5 col-md-5">
|
||||||
|
<label className="form-label" htmlFor="plannedWork">
|
||||||
|
{formData.id !== "0" ? "Modify " : "Enter "} Planned Work
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
{...register("plannedWork", { valueAsNumber: true })}
|
||||||
|
type="number"
|
||||||
|
className="form-control form-control-sm me-2"
|
||||||
|
placeholder="Planned Work"
|
||||||
|
/>
|
||||||
|
{errors.plannedWork && (
|
||||||
|
<p className="danger-text">{errors.plannedWork.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedActivity && selectedCategory && (
|
||||||
|
<div className="col-5 col-md-5">
|
||||||
|
<label className="form-label" htmlFor="completedWork">
|
||||||
|
{formData.id !== "0" ? "Modify " : "Enter "} Completed Work
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
{...register("completedWork", { valueAsNumber: true })}
|
||||||
|
type="number"
|
||||||
|
className="form-control form-control-sm me-2"
|
||||||
|
placeholder="Completed Work"
|
||||||
|
/>
|
||||||
|
{errors.completedWork && (
|
||||||
|
<p className="danger-text">
|
||||||
|
{errors.completedWork.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedActivity && selectedCategory && (
|
||||||
|
<div className="col-2 col-md-2">
|
||||||
|
<label className="form-label" htmlFor="unit">
|
||||||
|
Unit
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
className="form-control form-control-sm me-2"
|
||||||
|
value={selectedActivity?.unitOfMeasurement || ""}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{selectedActivity && selectedCategory && (
|
||||||
|
<div className="col-12">
|
||||||
|
<label
|
||||||
|
className="form-text fs-7 m-1 text-lg text-dark"
|
||||||
|
htmlFor="descriptionTextarea"
|
||||||
|
>
|
||||||
|
Comment
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
{...register("comment")}
|
||||||
|
className="form-control"
|
||||||
|
id="descriptionTextarea"
|
||||||
|
rows="2"
|
||||||
|
/>
|
||||||
|
{errors.comment && (
|
||||||
|
<div className="danger-text">
|
||||||
|
{errors.comment.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<button type="submit" className="btn btn-sm btn-primary me-3">
|
||||||
|
{isSubmitting ? "Please Wait.." : "Add Task"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-label-secondary"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="col-12 text-center">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-sm btn-primary me-3"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
|
||||||
{isSubmitting ? "Please Wait..." : "Add Task"}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-sm btn-label-secondary"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import WorkItem from "./WorkItem";
|
import WorkItem from "./WorkItem";
|
||||||
import { useProjectDetails, useProjectTasks } from "../../../hooks/useProjects";
|
import { useProjectDetails } from "../../../hooks/useProjects";
|
||||||
import { cacheData } from "../../../slices/apiDataManager";
|
import { cacheData, getCachedData } from "../../../slices/apiDataManager";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { refreshData } from "../../../slices/localVariablesSlice";
|
import { refreshData } from "../../../slices/localVariablesSlice";
|
||||||
import ProjectRepository from "../../../repositories/ProjectRepository";
|
import ProjectRepository from "../../../repositories/ProjectRepository";
|
||||||
@ -13,122 +13,222 @@ import {
|
|||||||
MANAGE_TASK,
|
MANAGE_TASK,
|
||||||
} from "../../../utils/constants";
|
} from "../../../utils/constants";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
import ProgressDonutChart from "../../Charts/ProgressDonutChart";
|
||||||
import ProgressBar from "../../common/ProgressBar";
|
import ProgressBar from "../../common/ProgressBar";
|
||||||
|
import { componentsToColor } from "pdf-lib";
|
||||||
|
|
||||||
const WorkArea = ({ workArea, floor, forBuilding }) => {
|
const WorkArea = ( {workArea, floor, forBuilding} ) =>
|
||||||
const selectedProject = useSelector((store) => store.localVariables.projectId);
|
{
|
||||||
const { projects_Details, loading } = useProjectDetails(selectedProject);
|
const selectedProject = useSelector( ( store ) => store.localVariables.projectId )
|
||||||
const [IsExpandedArea, setIsExpandedArea] = useState(false);
|
const { projects_Details, loading, error, refetch } = useProjectDetails(
|
||||||
|
selectedProject
|
||||||
|
);
|
||||||
|
const [workItems, setWorkItems] = useState([]);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [Project, setProject] = useState();
|
const [Project, setProject] = useState();
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
|
|
||||||
|
const ManageTasks = useHasUserPermission(MANAGE_TASK);
|
||||||
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
||||||
const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK);
|
const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK);
|
||||||
|
|
||||||
const { ProjectTaskList, isLoading } = useProjectTasks(workArea.id, IsExpandedArea);
|
|
||||||
|
|
||||||
const [workAreaStatus, setWorkAreaStatus] = useState({
|
const [workAreaStatus, setWorkAreaStatus] = useState({
|
||||||
completed: 0,
|
completed: 0,
|
||||||
planned: 100,
|
planned: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setProject(projects_Details);
|
const totalCompleted = workItems.reduce(
|
||||||
}, [projects_Details]);
|
(sum, i) => sum + (i.workItem?.completedWork || 0),
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const totalCompleted = ProjectTaskList?.reduce(
|
|
||||||
(sum, i) => sum + (i?.workItem?.completedWork || 0),
|
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
const totalPlanned = ProjectTaskList?.reduce(
|
const totalPlanned = workItems.reduce(
|
||||||
(sum, i) => sum + (i?.workItem?.plannedWork || 0),
|
(sum, i) => sum + (i.workItem?.plannedWork || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
const percent =
|
||||||
|
totalPlanned > 0 ? (totalCompleted / totalPlanned) * 100 : 0;
|
||||||
|
//setPercentComplete(Math.min(percent, 100)); // cap at 100%
|
||||||
setWorkAreaStatus({ completed: totalCompleted, planned: totalPlanned });
|
setWorkAreaStatus({ completed: totalCompleted, planned: totalPlanned });
|
||||||
}, [ProjectTaskList]);
|
}, [workItems]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const collapseElement = document.getElementById(`collapse-${workArea.id}`);
|
// const project = getCachedData("projectInfo");
|
||||||
|
setProject( projects_Details );
|
||||||
|
|
||||||
|
|
||||||
const handleShown = () => setIsExpandedArea(true);
|
if (!projects_Details || !forBuilding?.id || !floor?.id || !workArea?.id) return;
|
||||||
const handleHidden = () => setIsExpandedArea(false);
|
const building = projects_Details.buildings?.find((b) => b.id === forBuilding.id);
|
||||||
|
const floors = building?.floors?.find((f) => f.id === floor.id);
|
||||||
|
const workAreas = floor?.workAreas?.find((wa) => wa.id === workArea.id);
|
||||||
|
setWorkItems(workAreas?.workItems || []);
|
||||||
|
}, [workArea, floor, floor,loading]);
|
||||||
|
|
||||||
collapseElement?.addEventListener("shown.bs.collapse", handleShown);
|
const HanldeDeleteActivity = async (workItemId) => {
|
||||||
collapseElement?.addEventListener("hidden.bs.collapse", handleHidden);
|
try {
|
||||||
|
const updatedProject = { ...Project.data };
|
||||||
|
const response = await ProjectRepository.deleteProjectTask(workItemId);
|
||||||
|
const newProject = {
|
||||||
|
...updatedProject,
|
||||||
|
buildings: updatedProject?.buildings.map((building) =>
|
||||||
|
building?.id === building?.id
|
||||||
|
? {
|
||||||
|
...building,
|
||||||
|
floors: building?.floors?.map((floor) =>
|
||||||
|
floor.id === floor?.id
|
||||||
|
? {
|
||||||
|
...floor,
|
||||||
|
workAreas: floor.workAreas.map((workArea) =>
|
||||||
|
workArea.id === workArea?.id
|
||||||
|
? {
|
||||||
|
...workArea,
|
||||||
|
workItems: workArea.workItems.filter(
|
||||||
|
(item) =>
|
||||||
|
String(item?.workItem?.id ?? item?.id) !==
|
||||||
|
String(workItemId)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: workArea
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: floor
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: building
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
cacheData("projectInfo", {
|
||||||
|
projectId: newProject.id,
|
||||||
|
data: newProject,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(refreshData(true));
|
||||||
|
|
||||||
|
showToast("Activity Deleted Successfully", "success");
|
||||||
|
} catch (error) {
|
||||||
|
const message =
|
||||||
|
error.response?.data?.message ||
|
||||||
|
error.message ||
|
||||||
|
"An unexpected error occurred";
|
||||||
|
showToast(message, "error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const toggleButtons = document.querySelectorAll(".accordion-button");
|
||||||
|
|
||||||
|
toggleButtons.forEach((btn) => {
|
||||||
|
const icon = btn.querySelector(".toggle-icon");
|
||||||
|
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (btn.classList.contains("collapsed")) {
|
||||||
|
icon.classList.remove("bx-minus-circle");
|
||||||
|
icon.classList.add("bx-plus-circle");
|
||||||
|
} else {
|
||||||
|
icon.classList.remove("bx-plus-circle");
|
||||||
|
icon.classList.add("bx-minus-circle");
|
||||||
|
}
|
||||||
|
}, 300); // allow Bootstrap collapse to complete
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
collapseElement?.removeEventListener("shown.bs.collapse", handleShown);
|
toggleButtons.forEach((btn) => {
|
||||||
collapseElement?.removeEventListener("hidden.bs.collapse", handleHidden);
|
btn.removeEventListener("click", () => {});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}, [workArea.id]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={workArea.id}>
|
<React.Fragment key={workArea.id}>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="4" className="p-0">
|
<td colSpan="4" className="p-0">
|
||||||
<div className="accordion border-none" id={`accordion-${workArea.id}`}>
|
<div
|
||||||
|
className="accordion border-none"
|
||||||
|
id={`accordion-${workArea.id}`}
|
||||||
|
>
|
||||||
<div className="accordion-item background border-0">
|
<div className="accordion-item background border-0">
|
||||||
<p className="accordion-header mb-0" id={`heading-${workArea.id}`}>
|
{/* Accordion Header */}
|
||||||
|
<p
|
||||||
|
className="accordion-header mb-0"
|
||||||
|
id={`heading-${workArea.id}`}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="accordion-button text-start px-2 py-2 custom-accordion-btn collapsed"
|
className={`accordion-button text-start px-2 py-2 custom-accordion-btn ${
|
||||||
|
workItems && workItems.length > 0 ? "collapsed" : "disabled"
|
||||||
|
}`}
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="collapse"
|
data-bs-toggle={
|
||||||
data-bs-target={`#collapse-${workArea.id}`}
|
workItems && workItems.length > 0 ? "collapse" : ""
|
||||||
|
}
|
||||||
|
data-bs-target={
|
||||||
|
workItems && workItems.length > 0
|
||||||
|
? `#collapse-${workArea.id}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-controls={`collapse-${workArea.id}`}
|
aria-controls={`collapse-${workArea.id}`}
|
||||||
|
disabled={!(workItems && workItems.length > 0)}
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={`bx me-2 toggle-icon ${
|
className={`bx me-2 toggle-icon ${
|
||||||
IsExpandedArea ? "bx-minus-circle" : "bx-plus-circle"
|
workItems && workItems.length > 0
|
||||||
|
? "bx-plus-circle"
|
||||||
|
: "bx-block"
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
fontSize: "1.2rem",
|
fontSize: "1.2rem",
|
||||||
color: "black",
|
color:
|
||||||
|
workItems && workItems.length > 0 ? "" : "transparent",
|
||||||
}}
|
}}
|
||||||
></i>
|
></i>
|
||||||
|
|
||||||
<div className="d-flex justify-content-start row w-100 align-items-center">
|
<div className="d-flex justify-content-start row w-100 align-items-center">
|
||||||
<div className="d-flex col-5">
|
<div className="d-flex col-5">
|
||||||
<span className="fw-semibold text-primary small">Floor:</span>
|
<span className="fw-semibold text-primary small">
|
||||||
<span className="fw-normal text-darkgreen small px-2">
|
Floor:
|
||||||
|
</span>
|
||||||
|
<span className="fw-normal text-darkgreen small px-2">
|
||||||
{floor.floorName}
|
{floor.floorName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-start col-5">
|
<div className="text-start col-5">
|
||||||
<span className="fw-semibold text-primary small">Work Area:</span>
|
<span className="fw-semibold text-primary small">
|
||||||
<span className="fw-normal text-darkgreen small px-2">
|
Work Area:
|
||||||
|
</span>
|
||||||
|
<span className="fw-normal text-darkgreen small px-2">
|
||||||
{workArea.areaName}
|
{workArea.areaName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{ProjectTaskList?.length > 0 && (
|
{workArea?.workItems?.length > 0 && (
|
||||||
<div className="col-2">
|
<div className="col-2">
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
completedWork={workAreaStatus.completed}
|
completedWork={workAreaStatus.completed}
|
||||||
plannedWork={workAreaStatus.planned}
|
plannedWork={workAreaStatus.planned}
|
||||||
className="m-0 text-info"
|
className="m-0 text-info"
|
||||||
/>
|
></ProgressBar>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div
|
{/* Accordion Body */}
|
||||||
id={`collapse-${workArea.id}`}
|
{workItems && workItems.length > 0 && (
|
||||||
className="accordion-collapse collapse"
|
<div
|
||||||
aria-labelledby={`heading-${workArea.id}`}
|
id={`collapse-${workArea.id}`}
|
||||||
>
|
className="accordion-collapse collapse"
|
||||||
<div className="accordion-body px-1">
|
aria-labelledby={`heading-${workArea.id}`}
|
||||||
{isLoading ? (
|
>
|
||||||
<div className="text-center py-2 text-muted">Loading activities...</div>
|
<div className="accordion-body px-1">
|
||||||
) : ProjectTaskList?.length > 0 ? (
|
|
||||||
<table className="table table-sm mx-1">
|
<table className="table table-sm mx-1">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="infra-activity-table-header-first">Activity</th>
|
<th className="infra-activity-table-header-first">
|
||||||
|
Activity
|
||||||
|
</th>
|
||||||
<th className="infra-activity-table-header d-sm-table-cell d-md-none">
|
<th className="infra-activity-table-header d-sm-table-cell d-md-none">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
@ -141,8 +241,11 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
|
|||||||
<th className="infra-activity-table-header d-none d-md-table-cell">
|
<th className="infra-activity-table-header d-none d-md-table-cell">
|
||||||
Today's Planned
|
Today's Planned
|
||||||
</th>
|
</th>
|
||||||
<th className="infra-activity-table-header">Progress</th>
|
<th className="infra-activity-table-header">
|
||||||
{(ManageInfra || (!projectId && ManageAndAssignTak)) && (
|
Progress
|
||||||
|
</th>
|
||||||
|
{(ManageInfra ||
|
||||||
|
(!projectId && ManageAndAssignTak)) && (
|
||||||
<th className="infra-activity-table-header text-end">
|
<th className="infra-activity-table-header text-end">
|
||||||
<span className="px-2">Actions</span>
|
<span className="px-2">Actions</span>
|
||||||
</th>
|
</th>
|
||||||
@ -150,24 +253,21 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="table-border-bottom-0">
|
<tbody className="table-border-bottom-0">
|
||||||
{ProjectTaskList.map((workItem,index) => (
|
{workArea.workItems.map((workItem) => (
|
||||||
<WorkItem
|
<WorkItem
|
||||||
key={workItem.workItemId || `fallback-${index}`}
|
key={workItem.workItemId}
|
||||||
workItem={workItem}
|
workItem={workItem}
|
||||||
forBuilding={forBuilding}
|
forBuilding={forBuilding}
|
||||||
forFloor={floor}
|
forFloor={floor}
|
||||||
forWorkArea={workArea}
|
forWorkArea={workArea}
|
||||||
|
deleteHandleTask={HanldeDeleteActivity}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
) : (
|
</div>
|
||||||
<div className="text-center text-muted py-3">
|
|
||||||
No activities available for this work area.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -175,5 +275,4 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WorkArea;
|
export default WorkArea;
|
||||||
|
|||||||
@ -1,29 +1,39 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { set, useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import showToast from "../../../services/toastService";
|
import showToast from "../../../services/toastService";
|
||||||
import { useManageProjectInfra } from "../../../hooks/useProjects";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
|
|
||||||
|
// Zod schema for form validation
|
||||||
const workAreaSchema = z.object({
|
const workAreaSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().nonempty("Floor is required"),
|
||||||
buildingId: z.string().min(1, "Building is required"),
|
|
||||||
floorId: z.string().min(1, "Floor is required"),
|
buildingId: z.string().nonempty("Building is required"),
|
||||||
areaName: z.string().min(3, "Work Area Name must be at least 3 characters"),
|
floorId: z.string().nonempty("Floor is required"),
|
||||||
|
areaName: z
|
||||||
|
.string()
|
||||||
|
.nonempty("Work Area Name is required")
|
||||||
|
.min(3, "Name must be at least 3 characters long"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Default form data
|
||||||
const defaultModel = {
|
const defaultModel = {
|
||||||
id: "0",
|
id: "0",
|
||||||
buildingId: "0",
|
|
||||||
floorId: "0",
|
|
||||||
areaName: "",
|
areaName: "",
|
||||||
|
floorId: "0",
|
||||||
};
|
};
|
||||||
|
|
||||||
const WorkAreaModel = ({ project, onSubmit, onClose }) => {
|
const WorkAreaModel = ({
|
||||||
|
project,
|
||||||
|
onSubmit,
|
||||||
|
clearTrigger,
|
||||||
|
onClearComplete,
|
||||||
|
onClose,
|
||||||
|
}) => {
|
||||||
const [selectedBuilding, setSelectedBuilding] = useState(null);
|
const [selectedBuilding, setSelectedBuilding] = useState(null);
|
||||||
const [selectedFloor, setSelectedFloor] = useState(null);
|
const [selectedFloor, setSelectedFloor] = useState(null);
|
||||||
const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
const [selectdWorkArea, setWorkArea] = useState();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -32,169 +42,259 @@ const WorkAreaModel = ({ project, onSubmit, onClose }) => {
|
|||||||
reset,
|
reset,
|
||||||
watch,
|
watch,
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(workAreaSchema),
|
resolver: zodResolver(workAreaSchema), // Use Zod resolver for validation
|
||||||
defaultValues: defaultModel,
|
defaultValues: defaultModel,
|
||||||
});
|
});
|
||||||
|
|
||||||
const watchBuildingId = watch("buildingId");
|
const floorId = watch("floorId"); // Watch the floorId for conditional rendering
|
||||||
const watchFloorId = watch("floorId");
|
|
||||||
const watchWorkAreaId = watch("id");
|
|
||||||
const { mutate: ManageWorkArea, isPending } = useManageProjectInfra({
|
|
||||||
onSuccessCallback: (data, variables) => {
|
|
||||||
showToast(
|
|
||||||
watchWorkAreaId != "0"
|
|
||||||
? "Wrok Area updated Successfully"
|
|
||||||
: "Work Area created Successfully",
|
|
||||||
"success"
|
|
||||||
);
|
|
||||||
reset({ id: "0", buildingId: "0", areaName: "", floorId: "0" });
|
|
||||||
// onClose?.();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const building = project?.find((b) => b.id === watchBuildingId);
|
if (clearTrigger) {
|
||||||
setSelectedBuilding(building || null);
|
reset(defaultModel); // Reset form to initial state
|
||||||
|
setSelectedBuilding(null);
|
||||||
if (building) {
|
|
||||||
const floor = building.floors?.find((f) => f.id === watchFloorId);
|
|
||||||
setSelectedFloor(floor || null);
|
|
||||||
setValue("areaName", "");
|
|
||||||
} else {
|
|
||||||
setSelectedFloor(null);
|
setSelectedFloor(null);
|
||||||
setValue("floorId", "0");
|
onClearComplete();
|
||||||
setValue("areaName", "");
|
|
||||||
}
|
}
|
||||||
}, [watchBuildingId, watchFloorId]);
|
}, [clearTrigger, onClearComplete, reset]);
|
||||||
|
|
||||||
const handleWrokAreaChange = (e) => {
|
const handleWorkAreaChange = (e) => {
|
||||||
const workAreaId = e.target.value;
|
const { value } = e.target;
|
||||||
setValue("id", workAreaId);
|
|
||||||
const area = selectedFloor?.workAreas.find((w) => w.id === workAreaId);
|
if (value === "0") {
|
||||||
if (area) {
|
setValue("id", "0"); // Create New Work Area
|
||||||
setValue("areaName", area.areaName);
|
|
||||||
} else {
|
|
||||||
setValue("areaName", "");
|
setValue("areaName", "");
|
||||||
|
|
||||||
|
setWorkArea(String(0));
|
||||||
|
} else {
|
||||||
|
const workArea = selectedFloor?.workAreas.find(
|
||||||
|
(b) => b.id === String(value)
|
||||||
|
);
|
||||||
|
if (workArea) {
|
||||||
|
setValue("id", String(workArea.id)); // Set id as a string
|
||||||
|
setValue("areaName", workArea.areaName);
|
||||||
|
setWorkArea(String(workArea.id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleFloorChange = (e) => {
|
||||||
reset(defaultModel);
|
const { value } = e.target;
|
||||||
}, []);
|
const floor = selectedBuilding?.floors.find((b) => b.id === String(value));
|
||||||
|
|
||||||
const onSubmitForm = ( data ) =>
|
if (floor) {
|
||||||
{
|
setSelectedFloor(floor);
|
||||||
const payload = {
|
setValue("floorId", floor.id); // Update floorId
|
||||||
id: data.id === "0" ? null : data.id,
|
setValue("id", "0"); // Reset Work Area ID for new area creation
|
||||||
|
setValue("areaName", ""); // Reset Work Area Name when changing floor
|
||||||
|
} else {
|
||||||
|
setSelectedFloor(null);
|
||||||
|
setValue("floorId", "0");
|
||||||
|
setValue("id", "0"); // Reset Work Area ID
|
||||||
|
setValue("areaName", ""); // Reset Work Area Name
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBuildingChange = (e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
const building = project?.buildings.find((b) => b.id === String(value));
|
||||||
|
setSelectedBuilding(building);
|
||||||
|
setSelectedFloor(null); // Reset selected floor on building change
|
||||||
|
reset(defaultModel); // Reset the form when a new building is selected
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmitForm = (data) => {
|
||||||
|
let WorkArea = {
|
||||||
|
id: data.id == "0" ? null : data.id,
|
||||||
areaName: data.areaName,
|
areaName: data.areaName,
|
||||||
floorId: data.floorId,
|
floorId: data.floorId,
|
||||||
buildingId: data.buildingId,
|
buildingId: data.buildingId,
|
||||||
};
|
};
|
||||||
let infraObject = [
|
onSubmit(WorkArea);
|
||||||
{
|
|
||||||
building: null,
|
|
||||||
floor: null,
|
|
||||||
workArea: payload,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
ManageWorkArea({ infraObject, projectId: selectedProject });
|
reset({
|
||||||
|
id: "0",
|
||||||
|
areaName: "",
|
||||||
|
});
|
||||||
|
if (WorkArea.id !== null) {
|
||||||
|
showToast("WorkArea updated successfully.", "success");
|
||||||
|
} else {
|
||||||
|
showToast("WorkArea created successfully.", "success");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
reset(defaultModel);
|
||||||
|
setSelectedFloor(null);
|
||||||
|
setSelectedBuilding(null);
|
||||||
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="row g-2 p-2 p-md-1" onSubmit={handleSubmit(onSubmitForm)}>
|
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
|
||||||
<div className="text-center mb-1">
|
<div className="modal-content">
|
||||||
<h5 className="mb-1">Manage Work Area</h5>
|
<div className="modal-body">
|
||||||
</div>
|
<div className="row">
|
||||||
<div className="col-12 col-sm-6">
|
<button
|
||||||
<label className="form-label">Select Building</label>
|
type="button"
|
||||||
<select
|
className="btn-close"
|
||||||
{...register("buildingId")}
|
aria-label="Close"
|
||||||
className="form-select form-select-sm"
|
onClick={onClose}
|
||||||
>
|
|
||||||
<option value="0">Select Building</option>
|
|
||||||
{project?.map((b) => (
|
|
||||||
<option key={b.id} value={b.id}>
|
|
||||||
{b.buildingName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.buildingId && (
|
|
||||||
<p className="danger-text">{errors.buildingId.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{watchBuildingId !== "0" && (
|
|
||||||
<div className="col-12 col-sm-6">
|
|
||||||
<label className="form-label">Select Floor</label>
|
|
||||||
<select
|
|
||||||
{...register("floorId")}
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
>
|
|
||||||
<option value="0">
|
|
||||||
{selectedBuilding?.floor?.length > 0
|
|
||||||
? "NO Floor Found"
|
|
||||||
: "Select Floor"}
|
|
||||||
</option>
|
|
||||||
|
|
||||||
{selectedBuilding?.floors?.map((f) => (
|
|
||||||
<option key={f.id} value={f.id}>
|
|
||||||
{f.floorName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.floorId && (
|
|
||||||
<p className="danger-text">{errors.floorId.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{watchFloorId !== "0" && (
|
|
||||||
<>
|
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">Select Work Area</label>
|
|
||||||
<select
|
|
||||||
{...register("id")}
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
onChange={handleWrokAreaChange}
|
|
||||||
>
|
|
||||||
<option value="0">Create New Work Area</option>
|
|
||||||
{selectedFloor?.workAreas?.length > 0 &&
|
|
||||||
selectedFloor?.workAreas?.map((w) => (
|
|
||||||
<option key={w.id} value={w.id}>
|
|
||||||
{w.areaName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12">
|
|
||||||
<label className="form-label">
|
|
||||||
{watchWorkAreaId === "0"
|
|
||||||
? "Enter Work Area Name"
|
|
||||||
: "Edit Work Area Name"}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
placeholder="Work Area"
|
|
||||||
{...register("areaName")}
|
|
||||||
/>
|
/>
|
||||||
{errors.areaName && (
|
<div className="text-center mb-1">
|
||||||
<p className="danger-text">{errors.areaName.message}</p>
|
<h5 className="mb-1">Manage Work Area</h5>
|
||||||
)}
|
</div>
|
||||||
</div>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
|
||||||
|
{/* Building Selection */}
|
||||||
|
<div className="col-6 col-md-6">
|
||||||
|
<label className="form-label" htmlFor="buildingId">
|
||||||
|
Select Building
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="buildingId"
|
||||||
|
name="buildingId"
|
||||||
|
className="select2 form-select form-select-sm"
|
||||||
|
{...register("buildingId")}
|
||||||
|
onChange={handleBuildingChange}
|
||||||
|
>
|
||||||
|
<option value="0">Select Building</option>
|
||||||
|
{project?.buildings
|
||||||
|
?.filter((building) => building?.name)
|
||||||
|
?.sort((a, b) => {
|
||||||
|
const nameA = a.name || "";
|
||||||
|
const nameB = b.name || "";
|
||||||
|
return nameA?.localeCompare(nameB);
|
||||||
|
})
|
||||||
|
?.map((building) => (
|
||||||
|
<option key={building.id} value={building.id}>
|
||||||
|
{building.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
|
||||||
<div className="col-12 text-center">
|
{project?.buildings?.filter((building) => building?.name)
|
||||||
<button type="submit" className="btn btn-sm btn-primary me-3" disabled={isPending}>
|
.length === 0 && (
|
||||||
{isPending ? "Please Wait.." : watchWorkAreaId === "0" ? "Add Work Area" : "Update Work Area"}
|
<option disabled>No buildings found</option>
|
||||||
</button>
|
)}
|
||||||
<button type="button" className="btn btn-sm btn-label-secondary" disabled={isPending} onClick={onClose}>
|
</select>
|
||||||
Cancel
|
{errors.buildingId && <span>{errors.buildingId.message}</span>}
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
|
{/* Floor Selection */}
|
||||||
|
{selectedBuilding && selectedBuilding.buildingId !== "0" && (
|
||||||
|
<div className="col-6 col-md-6">
|
||||||
|
<label className="form-label" htmlFor="floorId">
|
||||||
|
Select Floor
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="floorId"
|
||||||
|
name="floorId"
|
||||||
|
className="select2 form-select form-select-sm"
|
||||||
|
{...register("floorId")}
|
||||||
|
onChange={handleFloorChange}
|
||||||
|
>
|
||||||
|
<option value="0">Select Floor</option>
|
||||||
|
{selectedBuilding.floors
|
||||||
|
?.filter((floor) => floor?.floorName)
|
||||||
|
?.sort((a, b) => {
|
||||||
|
const nameA = a.floorName || "";
|
||||||
|
const nameB = b.floorName || "";
|
||||||
|
return nameA?.localeCompare(nameB);
|
||||||
|
})
|
||||||
|
?.map((floor) => (
|
||||||
|
<option key={floor.id} value={floor.id}>
|
||||||
|
{floor.floorName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{selectedBuilding.floors?.filter(
|
||||||
|
(floor) => floor?.floorName
|
||||||
|
).length === 0 && <option disabled>No floors found</option>}
|
||||||
|
</select>
|
||||||
|
{errors.floorId && <span>{errors.floorId.message}</span>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Work Area Selection or Creation */}
|
||||||
|
{floorId !== "0" && (
|
||||||
|
<>
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label">Select Work Area</label>
|
||||||
|
<select
|
||||||
|
id="workAreaId"
|
||||||
|
name="workAreaId"
|
||||||
|
className="select2 form-select form-select-sm"
|
||||||
|
{...register("id")}
|
||||||
|
onChange={handleWorkAreaChange}
|
||||||
|
>
|
||||||
|
<option value="0">Create New Work Area</option>
|
||||||
|
{selectedFloor?.workAreas
|
||||||
|
?.filter((workArea) => workArea?.areaName)
|
||||||
|
?.sort((a, b) => {
|
||||||
|
const nameA = a.areaName || "";
|
||||||
|
const nameB = b.areaName || "";
|
||||||
|
return nameA?.localeCompare(nameB);
|
||||||
|
})
|
||||||
|
?.map((workArea) => (
|
||||||
|
<option key={workArea.id} value={workArea.id}>
|
||||||
|
{workArea.areaName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{selectedFloor?.workAreas?.filter(
|
||||||
|
(workArea) => workArea?.areaName
|
||||||
|
).length === 0 && (
|
||||||
|
<option disabled>No work areas found</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
{errors.id && <span>{errors.id.message}</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Work Area Name Input */}
|
||||||
|
<div className="col-12 col-md-12">
|
||||||
|
<label className="form-label" htmlFor="areaName">
|
||||||
|
{watch("id") === "0"
|
||||||
|
? "Enter Work Area Name"
|
||||||
|
: "Modify Work Area Name"}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="areaName"
|
||||||
|
name="areaName"
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
placeholder="Work Area"
|
||||||
|
{...register("areaName")}
|
||||||
|
/>
|
||||||
|
{errors.areaName && (
|
||||||
|
<span className="danger-text">
|
||||||
|
{errors.areaName.message}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Submit and Cancel Buttons */}
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-sm btn-primary me-3"
|
||||||
|
>
|
||||||
|
{watch("id") === "0" ? "Add Work Area" : "Edit Work Area"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-label-secondary"
|
||||||
|
onClick={handleCancel}
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</form>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -10,16 +10,16 @@ import {
|
|||||||
} from "../../../utils/constants";
|
} from "../../../utils/constants";
|
||||||
import ConfirmModal from "../../common/ConfirmModal";
|
import ConfirmModal from "../../common/ConfirmModal";
|
||||||
import ProjectRepository from "../../../repositories/ProjectRepository";
|
import ProjectRepository from "../../../repositories/ProjectRepository";
|
||||||
import { useDeleteProjectTask, useProjectDetails } from "../../../hooks/useProjects";
|
import { useProjectDetails } from "../../../hooks/useProjects";
|
||||||
import showToast from "../../../services/toastService";
|
import showToast from "../../../services/toastService";
|
||||||
import {
|
import {
|
||||||
cacheData,
|
cacheData,
|
||||||
clearCacheKey,
|
clearCacheKey,
|
||||||
getCachedData,
|
getCachedData,
|
||||||
} from "../../../slices/apiDataManager";
|
} from "../../../slices/apiDataManager";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
import { refreshData } from "../../../slices/localVariablesSlice";
|
import { refreshData } from "../../../slices/localVariablesSlice";
|
||||||
import GlobalModel from "../../common/GlobalModel";
|
import GlobalModel from "../../common/GlobalModel";
|
||||||
import {useDeleteMasterItem} from "../../../hooks/masterHook/useMaster";
|
|
||||||
|
|
||||||
const WorkItem = ({
|
const WorkItem = ({
|
||||||
workItem,
|
workItem,
|
||||||
@ -39,24 +39,18 @@ const WorkItem = ({
|
|||||||
const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK);
|
const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK);
|
||||||
const [loadingDelete, setLoadingDelete] = useState(false);
|
const [loadingDelete, setLoadingDelete] = useState(false);
|
||||||
const project = getCachedData("projectInfo");
|
const project = getCachedData("projectInfo");
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const openModal = () => setIsModalOpen(true);
|
const openModal = () => setIsModalOpen(true);
|
||||||
const closeModal = () => setIsModalOpen( false );
|
const closeModal = () => setIsModalOpen(false);
|
||||||
|
|
||||||
const showModalDelete = () => setShowModal2(true);
|
|
||||||
const closeModalDelete = () => setShowModal2(false);
|
|
||||||
const getProgress = (planned, completed) => {
|
const getProgress = (planned, completed) => {
|
||||||
return (completed * 100) / planned + "%";
|
return (completed * 100) / planned + "%";
|
||||||
};
|
};
|
||||||
|
|
||||||
const {mutate:DeleteTask,isPending } = useDeleteProjectTask(() => {
|
|
||||||
closeModalDelete?.();
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleAssignTask = () => {
|
const handleAssignTask = () => {
|
||||||
setItemName("");
|
setItemName("");
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNewWorkItem(workItem);
|
setNewWorkItem(workItem);
|
||||||
}, [workItem]);
|
}, [workItem]);
|
||||||
@ -85,15 +79,17 @@ const WorkItem = ({
|
|||||||
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
|
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const showModal1 = () => setShowModal(true);
|
||||||
|
const closeModal1 = () => setShowModal(false);
|
||||||
|
const showModalDelete = () => setShowModal2(true);
|
||||||
|
const closeModalDelete = () => setShowModal2(false);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
setLoadingDelete(true);
|
||||||
let WorkItemId = workItem.workItemId || workItem.id;
|
let WorkItemId = workItem.workItemId || workItem.id;
|
||||||
DeleteTask({workItemId:WorkItemId,workAreaId:forWorkArea?.id})
|
deleteHandleTask(WorkItemId);
|
||||||
|
setLoadingDelete(false);
|
||||||
|
closeModalDelete();
|
||||||
};
|
};
|
||||||
|
|
||||||
const PlannedWork =
|
const PlannedWork =
|
||||||
@ -109,15 +105,21 @@ const WorkItem = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<GlobalModel isOpen={showModal} size="lg" closeModal={()=>setShowModal(false)}>
|
<div
|
||||||
|
className={`modal fade ${showModal ? "show" : ""}`}
|
||||||
|
tabIndex="-1"
|
||||||
|
role="dialog"
|
||||||
|
style={{ display: showModal ? "block" : "none" }}
|
||||||
|
aria-hidden={!showModal}
|
||||||
|
>
|
||||||
<EditActivityModal
|
<EditActivityModal
|
||||||
workItem={workItem}
|
workItem={workItem}
|
||||||
workArea={forWorkArea}
|
workArea={forWorkArea}
|
||||||
building={forBuilding}
|
building={forBuilding}
|
||||||
floor={forFloor}
|
floor={forFloor}
|
||||||
onClose={()=>setShowModal(false)}
|
onClose={closeModal1}
|
||||||
/>
|
/>
|
||||||
</GlobalModel>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showModal2 && (
|
{showModal2 && (
|
||||||
@ -167,7 +169,7 @@ const WorkItem = ({
|
|||||||
: "NA"}
|
: "NA"}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
{/* Category - visible on medium and above */}
|
||||||
<td className="text-center table-cell-small d-none d-md-table-cell">
|
<td className="text-center table-cell-small d-none d-md-table-cell">
|
||||||
<span className="fw-light">
|
<span className="fw-light">
|
||||||
{hasWorkItem
|
{hasWorkItem
|
||||||
@ -202,6 +204,7 @@ const WorkItem = ({
|
|||||||
: "NA"}
|
: "NA"}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
{/* Progress Bar - always visible */}
|
||||||
<td className="text-center " style={{ width: "15%" }}>
|
<td className="text-center " style={{ width: "15%" }}>
|
||||||
<div className="progress p-0">
|
<div className="progress p-0">
|
||||||
<div
|
<div
|
||||||
@ -228,6 +231,7 @@ const WorkItem = ({
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
{/* Actions - always visible */}
|
||||||
{(ManageInfra ||
|
{(ManageInfra ||
|
||||||
(!projectId &&
|
(!projectId &&
|
||||||
ManageAndAssignTak &&
|
ManageAndAssignTak &&
|
||||||
@ -251,7 +255,7 @@ const WorkItem = ({
|
|||||||
<i
|
<i
|
||||||
className="bx bxs-edit text-secondary cursor-pointer"
|
className="bx bxs-edit text-secondary cursor-pointer"
|
||||||
title="Edit"
|
title="Edit"
|
||||||
onClick={()=>setShowModal(true)}
|
onClick={showModal1}
|
||||||
role="button"
|
role="button"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
@ -293,7 +297,7 @@ const WorkItem = ({
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
className="dropdown-item d-flex align-items-center"
|
className="dropdown-item d-flex align-items-center"
|
||||||
onClick={()=>setShowModal(true) }
|
onClick={showModal1}
|
||||||
>
|
>
|
||||||
<i className="bx bxs-edit text-secondary me-2"></i> Edit
|
<i className="bx bxs-edit text-secondary me-2"></i> Edit
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useForm, Controller } from "react-hook-form";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// const currentDate = new Date().toISOString().split("T")[0];
|
||||||
const currentDate = new Date().toLocaleDateString('en-CA');
|
const currentDate = new Date().toLocaleDateString('en-CA');
|
||||||
const formatDate = (date) => {
|
const formatDate = (date) => {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
@ -12,6 +13,7 @@ const formatDate = (date) => {
|
|||||||
if (isNaN(d.getTime())) {
|
if (isNaN(d.getTime())) {
|
||||||
return currentDate;
|
return currentDate;
|
||||||
}
|
}
|
||||||
|
// return d.toISOString().split("T")[0];
|
||||||
return d.toLocaleDateString('en-CA');
|
return d.toLocaleDateString('en-CA');
|
||||||
};
|
};
|
||||||
const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
|
const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
|
||||||
@ -93,7 +95,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
|
|||||||
projectAddress: project?.projectAddress || "",
|
projectAddress: project?.projectAddress || "",
|
||||||
startDate: formatDate(project?.startDate) || "",
|
startDate: formatDate(project?.startDate) || "",
|
||||||
endDate: formatDate(project?.endDate) || "",
|
endDate: formatDate(project?.endDate) || "",
|
||||||
projectStatusId: String(project?.projectStatus?.id) || "00000000-0000-0000-0000-000000000000",
|
projectStatusId: String(project.projectStatusId) || "00000000-0000-0000-0000-000000000000",
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
);
|
);
|
||||||
@ -114,7 +116,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
|
|||||||
projectAddress: project?.projectAddress || "",
|
projectAddress: project?.projectAddress || "",
|
||||||
startDate: formatDate(project?.startDate) || currentDate,
|
startDate: formatDate(project?.startDate) || currentDate,
|
||||||
endDate: formatDate(project?.endDate) || currentDate,
|
endDate: formatDate(project?.endDate) || currentDate,
|
||||||
projectStatusId: String(project?.projectStatus?.id || "00000000-0000-0000-0000-000000000000"),
|
projectStatusId: String(project?.projectStatusId || "00000000-0000-0000-0000-000000000000"),
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -69,6 +69,45 @@ const ProjectCard = ({ projectData, recall }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const handleFormSubmit = (updatedProject) => {
|
||||||
|
// if (projectInfo?.id) {
|
||||||
|
// ProjectRepository.updateProject(projectInfo.id, updatedProject)
|
||||||
|
// .then((response) => {
|
||||||
|
// const updatedProjectData = {
|
||||||
|
// ...projectInfo,
|
||||||
|
// ...response.data,
|
||||||
|
// building: projectDetails?.building,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// setProjectInfo(updatedProject);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
// }
|
||||||
|
// recall(getCachedData("projectslist"));
|
||||||
|
// showToast("Project updated successfully.", "success");
|
||||||
|
// setShowModal(false);
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// showToast(error.message, "error");
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import TaskModel from "./Infrastructure/TaskModel";
|
|||||||
import ProjectRepository, {
|
import ProjectRepository, {
|
||||||
TasksRepository,
|
TasksRepository,
|
||||||
} from "../../repositories/ProjectRepository";
|
} from "../../repositories/ProjectRepository";
|
||||||
|
import ProjectModal from "./ProjectModal";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { MANAGE_PROJECT_INFRA } from "../../utils/constants";
|
import { MANAGE_PROJECT_INFRA } from "../../utils/constants";
|
||||||
import InfraTable from "./Infrastructure/InfraTable";
|
import InfraTable from "./Infrastructure/InfraTable";
|
||||||
@ -16,32 +17,328 @@ import {
|
|||||||
clearCacheKey,
|
clearCacheKey,
|
||||||
getCachedData,
|
getCachedData,
|
||||||
} from "../../slices/apiDataManager";
|
} from "../../slices/apiDataManager";
|
||||||
import { useProjectDetails, useProjectInfra } from "../../hooks/useProjects";
|
import { useProjectDetails } from "../../hooks/useProjects";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { refreshData } from "../../slices/localVariablesSlice";
|
import { refreshData } from "../../slices/localVariablesSlice";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import {useParams} from "react-router-dom";
|
|
||||||
import GlobalModel from "../common/GlobalModel";
|
|
||||||
|
|
||||||
const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
|
||||||
{
|
|
||||||
const {projectId} = useParams()
|
|
||||||
const reloadedData = useSelector((store) => store.localVariables.reload);
|
const reloadedData = useSelector((store) => store.localVariables.reload);
|
||||||
const [ expandedBuildings, setExpandedBuildings ] = useState( [] );
|
const [expandedBuildings, setExpandedBuildings] = useState([]);
|
||||||
const {projectInfra,isLoading,error} = useProjectInfra(projectId)
|
|
||||||
const { projects_Details, refetch, loading } = useProjectDetails(data?.id);
|
const { projects_Details, refetch, loading } = useProjectDetails(data?.id);
|
||||||
const [ project, setProject ] = useState( projects_Details );
|
const [project, setProject] = useState(projects_Details);
|
||||||
|
const [modalConfig, setModalConfig] = useState({ type: null, data: null });
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
||||||
const [showModalFloor, setshowModalFloor] = useState(false);
|
|
||||||
const [showModalWorkArea, setshowModalWorkArea] = useState(false);
|
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false);
|
||||||
const [showModalTask, setshowModalTask] = useState(false);
|
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false);
|
||||||
const [showModalBuilding, setshowModalBuilding] = useState(false);
|
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false);
|
||||||
|
const [isTaskModelOpen, setIsTaskModalOpen] = useState(false);
|
||||||
|
const [isAssignRoleModal, setIsAssingRoleModal] = useState(false);
|
||||||
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
|
const [clearFormTrigger, setClearFormTrigger] = useState(false);
|
||||||
|
const [CurrentBuilding, setCurrentBuilding] = useState("");
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setProject(projectInfra);
|
setProject(projects_Details);
|
||||||
}, [data, projects_Details]);
|
}, [data, projects_Details]);
|
||||||
|
|
||||||
|
const openFloorModel = (projectData) => {
|
||||||
|
setIsFloorModalOpen(true);
|
||||||
|
};
|
||||||
|
const closeFloorModel = () => {
|
||||||
|
setIsFloorModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openAssignModel = (assignData) => {
|
||||||
|
setCurrentBuilding(assignData);
|
||||||
|
setIsAssingRoleModal(true);
|
||||||
|
};
|
||||||
|
const openBuildingModel = (projectData) => {
|
||||||
|
setIsBuildingModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeBuildingModel = () => {
|
||||||
|
setIsBuildingModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBuildingModelFormSubmit = (buildingmodel) => {
|
||||||
|
if (buildingmodel.id == "" || buildingmodel.id == 0)
|
||||||
|
delete buildingmodel.id;
|
||||||
|
let data = [
|
||||||
|
{
|
||||||
|
building: buildingmodel,
|
||||||
|
floor: null,
|
||||||
|
workArea: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
submitData(data);
|
||||||
|
};
|
||||||
|
const handleFloorModelFormSubmit = (updatedFloor) => {
|
||||||
|
if (updatedFloor.id == "") delete updatedFloor.id;
|
||||||
|
submitData([
|
||||||
|
{
|
||||||
|
building: null,
|
||||||
|
floor: updatedFloor,
|
||||||
|
workArea: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openWorkAreaModel = (projectData) => {
|
||||||
|
setIsWorkAreaModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeWorkAreaModel = () => {
|
||||||
|
setIsWorkAreaModalOpen(false);
|
||||||
|
};
|
||||||
|
const handleWorkAreaModelFormSubmit = (updatedModel) => {
|
||||||
|
if (updatedModel.id == "") delete updatedModel.id;
|
||||||
|
submitData([
|
||||||
|
{
|
||||||
|
building: null,
|
||||||
|
floor: null,
|
||||||
|
workArea: updatedModel,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openTaskModel = (projectData) => {
|
||||||
|
setIsTaskModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeTaskModel = () => {
|
||||||
|
setIsTaskModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTaskModelFormSubmit = (updatedModel) => {
|
||||||
|
if (updatedModel.id == "") updatedModel.id = null;
|
||||||
|
const updatedProject = { ...project };
|
||||||
|
|
||||||
|
ProjectRepository.manageProjectTasks([updatedModel])
|
||||||
|
.then((response) => {
|
||||||
|
onDataChange("task-change");
|
||||||
|
showToast("Details updated successfully.", "success");
|
||||||
|
// setClearFormTrigger( true );
|
||||||
|
if (response?.data[0]) {
|
||||||
|
const { workItemId, workItem } = response.data[0];
|
||||||
|
|
||||||
|
const updatedBuildings = updatedProject.buildings.map((building) =>
|
||||||
|
building.id == updatedModel.buildingID
|
||||||
|
? {
|
||||||
|
...building,
|
||||||
|
floors: building.floors.map((floor) =>
|
||||||
|
floor.id == updatedModel.floorId
|
||||||
|
? {
|
||||||
|
...floor,
|
||||||
|
workAreas: floor.workAreas.map((workArea) =>
|
||||||
|
workArea.id === workItem?.workAreaId
|
||||||
|
? {
|
||||||
|
...workArea,
|
||||||
|
workItems: workArea.workItems.some(
|
||||||
|
(existingItem) =>
|
||||||
|
existingItem.workItemId ===
|
||||||
|
workItem.workItemId
|
||||||
|
)
|
||||||
|
? [...workArea.workItems] // Create a new array to trigger re-render
|
||||||
|
: [...workArea.workItems, workItem],
|
||||||
|
}
|
||||||
|
: workArea
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: floor
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: building
|
||||||
|
);
|
||||||
|
updatedProject.buildings = updatedBuildings;
|
||||||
|
// workItem update, but having local state issue there for needed to calling api
|
||||||
|
clearCacheKey("projectInfo");
|
||||||
|
refetch();
|
||||||
|
|
||||||
|
cacheData("projectInfo", {
|
||||||
|
projectId: updatedProject.id,
|
||||||
|
data: updatedProject,
|
||||||
|
});
|
||||||
|
|
||||||
|
setProject(updatedProject);
|
||||||
|
// closeTaskModel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showToast(error.message, "error");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitData = async (infraObject) => {
|
||||||
|
try {
|
||||||
|
let response = await ProjectRepository.manageProjectInfra(infraObject);
|
||||||
|
const entity = response.data;
|
||||||
|
|
||||||
|
const updatedProject = { ...project };
|
||||||
|
// Handle the building data
|
||||||
|
if (entity.building) {
|
||||||
|
const { id, name, description } = entity.building;
|
||||||
|
const updatedBuildings = updatedProject?.buildings?.map((building) =>
|
||||||
|
building.id === id ? { ...building, name, description } : building
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add building if it doesn't exist
|
||||||
|
if (!updatedProject.buildings.some((building) => building.id === id)) {
|
||||||
|
updatedBuildings.push({
|
||||||
|
id: id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
floors: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedProject.buildings = updatedBuildings;
|
||||||
|
|
||||||
|
// Update the cache for buildings
|
||||||
|
cacheData("projectInfo", {
|
||||||
|
projectId: updatedProject.id,
|
||||||
|
data: updatedProject,
|
||||||
|
});
|
||||||
|
setProject((prevProject) => ({
|
||||||
|
...prevProject,
|
||||||
|
buildings: updatedBuildings,
|
||||||
|
}));
|
||||||
|
// closeBuildingModel()
|
||||||
|
}
|
||||||
|
// Handle the floor data
|
||||||
|
else if (entity.floor) {
|
||||||
|
const { buildingId, id, floorName } = entity.floor;
|
||||||
|
const updatedBuildings = updatedProject?.buildings?.map((building) =>
|
||||||
|
building.id == buildingId
|
||||||
|
? {
|
||||||
|
...building,
|
||||||
|
floors: building.floors
|
||||||
|
.map((floor) =>
|
||||||
|
floor.id === id
|
||||||
|
? {
|
||||||
|
...floor,
|
||||||
|
floorName, // Update the floor name only
|
||||||
|
// Keep other properties as they are (including workArea)
|
||||||
|
}
|
||||||
|
: floor
|
||||||
|
)
|
||||||
|
// Add the new floor if it doesn't already exist
|
||||||
|
.concat(
|
||||||
|
!building.floors.some((floor) => floor.id === id)
|
||||||
|
? [{ id: id, floorName, workAreas: [] }] // New floor added with workArea set to null
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: building
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedProject.buildings = updatedBuildings;
|
||||||
|
|
||||||
|
// Cache the updated project
|
||||||
|
cacheData("projectInfo", {
|
||||||
|
projectId: updatedProject.id,
|
||||||
|
data: updatedProject,
|
||||||
|
});
|
||||||
|
setProject(updatedProject);
|
||||||
|
// closeFloorModel()
|
||||||
|
}
|
||||||
|
// Handle the work area data
|
||||||
|
else if (entity.workArea) {
|
||||||
|
let buildingId = infraObject[0].workArea.buildingId;
|
||||||
|
|
||||||
|
const { floorId, areaName, id } = entity.workArea;
|
||||||
|
// Check if the workArea exists, otherwise create a new one
|
||||||
|
const updatedBuildings = updatedProject.buildings.map((building) =>
|
||||||
|
building.id == buildingId
|
||||||
|
? {
|
||||||
|
...building,
|
||||||
|
floors: building.floors.map((floor) =>
|
||||||
|
floor.id == floorId
|
||||||
|
? {
|
||||||
|
...floor,
|
||||||
|
workAreas: floor.workAreas.some(
|
||||||
|
(workArea) => workArea.id === id
|
||||||
|
)
|
||||||
|
? floor.workAreas.map((workArea) =>
|
||||||
|
workArea.id === id
|
||||||
|
? { ...workArea, areaName }
|
||||||
|
: workArea
|
||||||
|
)
|
||||||
|
: [
|
||||||
|
...floor.workAreas,
|
||||||
|
{ id, areaName, workItems: [] },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: floor
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: building
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedProject.buildings = updatedBuildings;
|
||||||
|
|
||||||
|
// Update the cache for work areas
|
||||||
|
cacheData("projectInfo", {
|
||||||
|
projectId: updatedProject.id,
|
||||||
|
data: updatedProject,
|
||||||
|
});
|
||||||
|
setProject(updatedProject);
|
||||||
|
// closeWorkAreaModel()
|
||||||
|
}
|
||||||
|
// Handle the task (workItem) data
|
||||||
|
else {
|
||||||
|
console.error("Unsupported data type for submitData", entity);
|
||||||
|
}
|
||||||
|
} catch (Err) {
|
||||||
|
showToast("Somthing wrong", "error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleBuilding = (id) => {
|
||||||
|
setExpandedBuildings((prev) =>
|
||||||
|
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalData = (type, modaldata) => {
|
||||||
|
setModalConfig({ type: type, data: modaldata });
|
||||||
|
};
|
||||||
|
const openModal = () => {
|
||||||
|
const modalElement = document.getElementById("building-model");
|
||||||
|
const modal = new Modal(modalElement, {
|
||||||
|
backdrop: false,
|
||||||
|
keyboard: true,
|
||||||
|
focus: true,
|
||||||
|
});
|
||||||
|
modal.show();
|
||||||
|
};
|
||||||
|
const closeModal = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setModalConfig(null);
|
||||||
|
|
||||||
|
const modalElement = document.getElementById("building-model");
|
||||||
|
if (modalElement) {
|
||||||
|
modalElement.classList.remove("show"); // Remove modal visibility class
|
||||||
|
modalElement.style.display = "none"; // Hide the modal element
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.classList.remove("modal-open"); // Remove modal-open class from body
|
||||||
|
|
||||||
|
// Remove the modal backdrop
|
||||||
|
const backdropElement = document.querySelector(".modal-backdrop");
|
||||||
|
if (backdropElement) {
|
||||||
|
backdropElement.classList.remove("modal-backdrop"); // Remove backdrop class
|
||||||
|
backdropElement.style.display = "none"; // Hide the backdrop element
|
||||||
|
}
|
||||||
|
document.body.style.overflow = "auto";
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShow = () => setShowModal(true);
|
||||||
|
const handleClose = () => setShowModal(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reloadedData) {
|
if (reloadedData) {
|
||||||
refetch();
|
refetch();
|
||||||
@ -55,30 +352,82 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showModalBuilding && <GlobalModel isOpen={showModalBuilding} size="md" closeModal={() => setshowModalBuilding( false )}>
|
<div
|
||||||
|
className={`modal fade ${showModal ? "show" : ""}`}
|
||||||
|
tabIndex="-1"
|
||||||
|
role="dialog"
|
||||||
|
style={{ display: showModal ? "block" : "none" }}
|
||||||
|
aria-hidden={!showModal}
|
||||||
|
>
|
||||||
<BuildingModel
|
<BuildingModel
|
||||||
project={projectInfra}
|
project={project}
|
||||||
onClose={() => setshowModalBuilding( false )}
|
onClose={handleClose}
|
||||||
/>
|
onSubmit={handleBuildingModelFormSubmit}
|
||||||
</GlobalModel>}
|
clearTrigger={clearFormTrigger}
|
||||||
{showModalFloor && <GlobalModel isOpen={showModalFloor} size="md" closeModal={()=>setshowModalFloor(false)}>
|
onClearComplete={() => setClearFormTrigger(false)}
|
||||||
<FloorModel
|
></BuildingModel>
|
||||||
project={projectInfra}
|
</div>
|
||||||
onClose={()=>setshowModalFloor(false)}
|
{isFloorModalOpen && (
|
||||||
|
<div
|
||||||
|
className="modal fade show"
|
||||||
|
id="floor-model"
|
||||||
|
tabIndex="-1"
|
||||||
|
role="dialog"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
aria-hidden="false"
|
||||||
|
>
|
||||||
|
<FloorModel
|
||||||
|
project={project}
|
||||||
|
onClose={closeFloorModel}
|
||||||
|
onSubmit={handleFloorModelFormSubmit}
|
||||||
|
clearTrigger={clearFormTrigger}
|
||||||
|
onClearComplete={() => setClearFormTrigger(false)}
|
||||||
/>
|
/>
|
||||||
</GlobalModel>}
|
</div>
|
||||||
{showModalWorkArea && <GlobalModel isOpen={showModalWorkArea} size="lg" closeModal={()=>setshowModalWorkArea(false)} >
|
)}
|
||||||
|
|
||||||
|
{isWorkAreaModelOpen && (
|
||||||
|
<div
|
||||||
|
className="modal fade show"
|
||||||
|
id="work-area-model"
|
||||||
|
tabIndex="-1"
|
||||||
|
role="dialog"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
aria-hidden="false"
|
||||||
|
>
|
||||||
<WorkAreaModel
|
<WorkAreaModel
|
||||||
project={projectInfra}
|
project={project}
|
||||||
onClose={()=>setshowModalWorkArea(false)}
|
onClose={closeWorkAreaModel}
|
||||||
|
onSubmit={handleWorkAreaModelFormSubmit}
|
||||||
|
clearTrigger={clearFormTrigger}
|
||||||
|
onClearComplete={() => setClearFormTrigger(false)}
|
||||||
/>
|
/>
|
||||||
</GlobalModel>}
|
</div>
|
||||||
{showModalTask && ( <GlobalModel isOpen={showModalTask} size="lg" closeModal={()=>setshowModalTask(false)}>
|
)}
|
||||||
<TaskModel
|
|
||||||
project={projectInfra}
|
{isTaskModelOpen && (
|
||||||
onClose={()=>setshowModalTask(false)}
|
<div
|
||||||
|
className="modal fade show"
|
||||||
|
id="task-model"
|
||||||
|
tabIndex="-1"
|
||||||
|
role="dialog"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
aria-hidden="false"
|
||||||
|
>
|
||||||
|
<TaskModel
|
||||||
|
project={project}
|
||||||
|
onClose={closeTaskModel}
|
||||||
|
onSubmit={handleTaskModelFormSubmit}
|
||||||
|
clearTrigger={clearFormTrigger}
|
||||||
|
onClearComplete={() => setClearFormTrigger(false)}
|
||||||
/>
|
/>
|
||||||
</GlobalModel>)}
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isModalOpen && (
|
||||||
|
<ProjectModal modalConfig={modalConfig} closeModal={closeModal} />
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||||
@ -92,15 +441,15 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="link-button link-button-sm m-1 "
|
className="link-button link-button-sm m-1 "
|
||||||
onClick={()=>setshowModalBuilding(true)}
|
onClick={handleShow}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
Manage Building
|
Manage Building
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="link-button m-1"
|
className="link-button m-1"
|
||||||
onClick={()=>setshowModalFloor(true)}
|
onClick={() => openFloorModel()}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
Manage Floors
|
Manage Floors
|
||||||
@ -108,7 +457,7 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="link-button m-1"
|
className="link-button m-1"
|
||||||
onClick={() => setshowModalWorkArea(true)}
|
onClick={() => openWorkAreaModel()}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
Manage Work Areas
|
Manage Work Areas
|
||||||
@ -116,7 +465,7 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="link-button m-1"
|
className="link-button m-1"
|
||||||
onClick={()=>setshowModalTask(true)}
|
onClick={() => openTaskModel()}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
Create Tasks
|
Create Tasks
|
||||||
@ -124,16 +473,15 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row ">
|
<div className="row ">
|
||||||
{isLoading && <p>Loading....</p>}
|
{loading && <p>Loading....</p>}
|
||||||
{projectInfra && projectInfra?.length > 0 && (
|
{project && project.buildings?.length > 0 && (
|
||||||
<InfraTable
|
<InfraTable
|
||||||
buildings={projectInfra}
|
buildings={project?.buildings}
|
||||||
projectId={projectId}
|
projectId={project.id}
|
||||||
// handleFloor={submitData}
|
handleFloor={submitData}
|
||||||
signalRHandler ={signalRHandler}
|
signalRHandler = {signalRHandler}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isLoading && projectInfra?.length == 0 && <div className="mt-5"><p>No Infra Avaiable</p></div>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -23,6 +23,7 @@ const ProjectModal = ({modalConfig,closeModal}) => {
|
|||||||
></button>
|
></button>
|
||||||
<div className="text-center mb-2"></div>
|
<div className="text-center mb-2"></div>
|
||||||
|
|
||||||
|
{/* Modal Component */}
|
||||||
|
|
||||||
{modalConfig?.type === "assignRole" && <AssignRole assignData={modalConfig?.data} onClose={closeModal} />}
|
{modalConfig?.type === "assignRole" && <AssignRole assignData={modalConfig?.data} onClose={closeModal} />}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
|
|||||||
className={`nav-link ${activePill === "profile" ? "active" : ""}`}
|
className={`nav-link ${activePill === "profile" ? "active" : ""}`}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault(); // Prevent page reload
|
||||||
onPillClick("profile");
|
onPillClick("profile");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -29,7 +29,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
|
|||||||
className={`nav-link ${activePill === "teams" ? "active" : ""}`}
|
className={`nav-link ${activePill === "teams" ? "active" : ""}`}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault(); // Prevent page reload
|
||||||
onPillClick("teams");
|
onPillClick("teams");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -41,14 +41,27 @@ const ProjectNav = ({ onPillClick, activePill }) => {
|
|||||||
className={`nav-link ${activePill === "infra" ? "active" : ""}`}
|
className={`nav-link ${activePill === "infra" ? "active" : ""}`}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault(); // Prevent page reload
|
||||||
onPillClick("infra");
|
onPillClick("infra");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i className="bx bx-grid-alt bx-sm me-1_5"></i> <span className="d-none d-md-inline">Infrastructure</span>
|
<i className="bx bx-grid-alt bx-sm me-1_5"></i> <span className="d-none d-md-inline">Infrastructure</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{/* <li className="nav-item">
|
||||||
|
<a
|
||||||
|
className={`nav-link ${
|
||||||
|
activePill === "workplan" ? "active" : ""
|
||||||
|
}`}
|
||||||
|
href="#"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault(); // Prevent page reload
|
||||||
|
onPillClick("workplan");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="bx bx-link bx-sm me-1_5"></i> Work Plan
|
||||||
|
</a>
|
||||||
|
</li> */}
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<a
|
<a
|
||||||
className={`nav-link ${
|
className={`nav-link ${
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {useEmployeesByProjectAllocated, useProjects} from "../../hooks/useProjects";
|
import {useEmployeesByProjectAllocated, useProjects} from "../../hooks/useProjects";
|
||||||
|
|
||||||
const ProjectOverview = ({project}) =>{
|
const ProjectOverview = ({project}) =>
|
||||||
const {projects} = useProjects()
|
{
|
||||||
|
const {projects} = useProjects()
|
||||||
const project_detail = projects.find( ( pro ) => pro.id == project )
|
const project_detail = projects.find( ( pro ) => pro.id == project )
|
||||||
return (
|
return (
|
||||||
<div className="card mb-6">
|
<div className="card mb-6">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import MapUsers from "./MapUsers";
|
import MapUsers from "./MapUsers";
|
||||||
import { Link, NavLink, useNavigate, useParams } from "react-router-dom";
|
import { Link, NavLink, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
import Avatar from "../common/Avatar";
|
import Avatar from "../common/Avatar";
|
||||||
@ -16,9 +16,7 @@ import ConfirmModal from "../common/ConfirmModal";
|
|||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import {useEmployeesByProjectAllocated, useManageProjectAllocation} from "../../hooks/useProjects";
|
import {useEmployeesByProjectAllocated, useManageProjectAllocation} from "../../hooks/useProjects";
|
||||||
|
|
||||||
const Teams = () =>
|
const Teams = ({ project }) => {
|
||||||
{
|
|
||||||
const {projectId} = useParams()
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const { data, loading } = useMaster();
|
const { data, loading } = useMaster();
|
||||||
@ -37,7 +35,7 @@ const Teams = () =>
|
|||||||
|
|
||||||
const HasAssignUserPermission = useHasUserPermission( ASSIGN_TO_PROJECT );
|
const HasAssignUserPermission = useHasUserPermission( ASSIGN_TO_PROJECT );
|
||||||
const [ IsDeleteModal, setIsDeleteModal ] = useState( false )
|
const [ IsDeleteModal, setIsDeleteModal ] = useState( false )
|
||||||
const {projectEmployees, loading:employeeLodaing, refetch} = useEmployeesByProjectAllocated( projectId )
|
const {projectEmployees, loading:employeeLodaing, refetch} = useEmployeesByProjectAllocated( project.id )
|
||||||
const {
|
const {
|
||||||
mutate: submitAllocations,
|
mutate: submitAllocations,
|
||||||
isPending,
|
isPending,
|
||||||
@ -218,11 +216,11 @@ const {
|
|||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if (msg.projectIds.some((item) => item === projectId)) {
|
if (msg.projectIds.some((item) => item === project.id)) {
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[projectId, refetch]
|
[project.id, refetch]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -253,7 +251,7 @@ const {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<MapUsers
|
<MapUsers
|
||||||
projectId={projectId}
|
projectId={project?.id}
|
||||||
onClose={onModelClose}
|
onClose={onModelClose}
|
||||||
empJobRoles={empJobRoles}
|
empJobRoles={empJobRoles}
|
||||||
onSubmit={handleEmpAlicationFormSubmit}
|
onSubmit={handleEmpAlicationFormSubmit}
|
||||||
|
|||||||
@ -5,8 +5,8 @@ const Breadcrumb = ({ data }) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav aria-label="breadcrumb" >
|
<nav aria-label="breadcrumb">
|
||||||
<ol className="breadcrumb breadcrumb-custom-icon my-3">
|
<ol className="breadcrumb breadcrumb-custom-icon">
|
||||||
{data.map((item, index) => (
|
{data.map((item, index) => (
|
||||||
item.link ? (
|
item.link ? (
|
||||||
<li className="breadcrumb-item cursor-pointer" key={index}>
|
<li className="breadcrumb-item cursor-pointer" key={index}>
|
||||||
|
|||||||
@ -199,10 +199,10 @@ useEffect(() => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 col-md-12 mx-2s" >
|
<div className="col-12 col-md-12 mx-2s " >
|
||||||
|
|
||||||
{masterFeatures.map((feature, featureIndex) => (
|
{masterFeatures.map((feature, featureIndex) => (
|
||||||
<div className="row my-1" key={feature.id} style={{ marginLeft: "0px" }}>
|
<div className="row my-3" key={feature.id} style={{ marginLeft: "0px" }}>
|
||||||
|
|
||||||
<div className="col-12 col-md-3 d-flex text-start align-items-center" style={{ wordWrap: 'break-word' }}>
|
<div className="col-12 col-md-3 d-flex text-start align-items-center" style={{ wordWrap: 'break-word' }}>
|
||||||
<span className="fs">{feature.name}</span>
|
<span className="fs">{feature.name}</span>
|
||||||
@ -210,7 +210,7 @@ useEffect(() => {
|
|||||||
<div className="col-12 col-md-1">
|
<div className="col-12 col-md-1">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-md-8 d-flex justify-content-start align-items-center flex-wrap ">
|
<div className="col-12 col-md-8 d-flex justify-content-start align-items-center flex-wrap">
|
||||||
{feature.featurePermissions.map((perm, permIndex) => {
|
{feature.featurePermissions.map((perm, permIndex) => {
|
||||||
const refIndex = (featureIndex * 10) + permIndex;
|
const refIndex = (featureIndex * 10) + permIndex;
|
||||||
return (
|
return (
|
||||||
@ -262,7 +262,6 @@ useEffect(() => {
|
|||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<hr className="hr my-1 py-1" />
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{errors.permissions && (
|
{errors.permissions && (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {useState,useEffect, useCallback} from "react"
|
import {useState,useEffect} from "react"
|
||||||
import { MasterRespository } from "../../repositories/MastersRepository";
|
import { MasterRespository } from "../../repositories/MastersRepository";
|
||||||
import { cacheData,getCachedData } from "../../slices/apiDataManager";
|
import { cacheData,getCachedData } from "../../slices/apiDataManager";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
@ -344,14 +344,14 @@ const fetchMasterData = async (masterType) => {
|
|||||||
|
|
||||||
const useMaster = () => {
|
const useMaster = () => {
|
||||||
const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
|
const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
|
||||||
const queryFn = useCallback(() => fetchMasterData(selectedMaster), [selectedMaster]);
|
|
||||||
const {
|
const {
|
||||||
data = [],
|
data = [],
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["masterData", selectedMaster],
|
queryKey: ["masterData", selectedMaster],
|
||||||
queryFn,
|
queryFn: () => fetchMasterData(selectedMaster),
|
||||||
enabled: !!selectedMaster,
|
enabled: !!selectedMaster,
|
||||||
staleTime: 1000 * 60 * 10,
|
staleTime: 1000 * 60 * 10,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
|
|||||||
@ -10,11 +10,260 @@ import {store} from "../store/store";
|
|||||||
import {queryClient} from "../layouts/AuthLayout";
|
import {queryClient} from "../layouts/AuthLayout";
|
||||||
|
|
||||||
|
|
||||||
|
// export const useAllEmployees = (showInactive) => {
|
||||||
|
// const [employeesList, setEmployeeList] = useState([]);
|
||||||
|
// const [loading, setLoading] = useState(false);
|
||||||
|
// const [error, setError] = useState();
|
||||||
|
|
||||||
|
// const fetchData = async () => {
|
||||||
|
// try {
|
||||||
|
// let EmployeeList_cached = getCachedData("AllEmployees");
|
||||||
|
// if (!EmployeeList_cached) {
|
||||||
|
// setLoading(true);
|
||||||
|
// const response = await EmployeeRepository.getAllEmployeeList(showInactive);
|
||||||
|
// cacheData("AllEmployees", response.data);
|
||||||
|
// setEmployeeList(response.data);
|
||||||
|
// setLoading(false);
|
||||||
|
// } else {
|
||||||
|
// setEmployeeList(EmployeeList_cached);
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// setError("Failed to fetch data.");
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// fetchData();
|
||||||
|
// }, []);
|
||||||
|
// return { employeesList, loading, error };
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// export const useEmployees = (selectedProject) => {
|
||||||
|
// const [employees, setEmployeeList] = useState([]);
|
||||||
|
// const [loading, setLoading] = useState(true);
|
||||||
|
// const [projects, setProjects] = useState([]);
|
||||||
|
|
||||||
|
// const fetchData = async (projectid) => {
|
||||||
|
// try {
|
||||||
|
// let EmployeeByProject_Cache = getCachedData("employeeListByProject");
|
||||||
|
// if (
|
||||||
|
// !EmployeeByProject_Cache ||
|
||||||
|
// !EmployeeByProject_Cache.projectId === projectid
|
||||||
|
// ) {
|
||||||
|
// EmployeeRepository.getEmployeeListByproject(projectid)
|
||||||
|
// .then((response) => {
|
||||||
|
// setEmployeeList(response);
|
||||||
|
// cacheData("employeeListByProject", {
|
||||||
|
// data: response,
|
||||||
|
// projectId: projectid,
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// setError("Failed to fetch data.");
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// setEmployeeList(EmployeeByProject_Cache.data);
|
||||||
|
// }
|
||||||
|
// setLoading(false);
|
||||||
|
// } catch (err) {
|
||||||
|
// setError("Failed to fetch data.");
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (selectedProject) {
|
||||||
|
// fetchData(selectedProject);
|
||||||
|
// }
|
||||||
|
// }, [selectedProject]);
|
||||||
|
|
||||||
|
// return { employees, loading, projects, reCallAllEmployee };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export const useEmployeeRoles = (employeeId) => {
|
||||||
|
// const [loading, setLoading] = useState(true);
|
||||||
|
// const [error, setError] = useState();
|
||||||
|
// const [employeeRoles, setEmployeeRoles] = useState([]);
|
||||||
|
// const fetchData = async (employeeid) => {
|
||||||
|
// try {
|
||||||
|
// let response = await RolesRepository.getEmployeeRoles(employeeid);
|
||||||
|
// setEmployeeRoles(response.data);
|
||||||
|
// cacheData("employeelist", response.data);
|
||||||
|
// } catch (err) {
|
||||||
|
// setError("Failed to fetch data.");
|
||||||
|
// setEmployeeRoles([]);
|
||||||
|
// } finally {
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (employeeId) {
|
||||||
|
// fetchData(employeeId);
|
||||||
|
// }
|
||||||
|
// }, [employeeId]);
|
||||||
|
|
||||||
|
// return { employeeRoles, loading, error };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export const useEmployeesByProject = (projectId) => {
|
||||||
|
// const [loading, setLoading] = useState(false);
|
||||||
|
// const [error, setError] = useState();
|
||||||
|
// const [employees, setEmployees] = useState([]);
|
||||||
|
|
||||||
|
// const fetchData = async () => {
|
||||||
|
// const Employees_cache = getCachedData("employeeListByProject");
|
||||||
|
// if (!Employees_cache || Employees_cache.projectId !== projectId) {
|
||||||
|
// setEmployees(true);
|
||||||
|
// ProjectRepository.getEmployeesByProject(projectId)
|
||||||
|
// .then((response) => {
|
||||||
|
// setEmployees(response.data);
|
||||||
|
// cacheData("employeeListByProject", {
|
||||||
|
// data: response.data,
|
||||||
|
// projectId,
|
||||||
|
// });
|
||||||
|
// setLoading(false);
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// setError("Failed to fetch data.");
|
||||||
|
// setLoading(false);
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// setEmployees(Employees_cache.data);
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// fetchData(projectId);
|
||||||
|
// }, [projectId]);
|
||||||
|
|
||||||
|
// return { employees, loading, error, recallProjectEmplloyee: fetchData };
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// export const useEmployeesAllOrByProjectId = (projectId, showInactive) => {
|
||||||
|
// const [employees, setEmployees] = useState([]);
|
||||||
|
// const [loading, setLoading] = useState(false);
|
||||||
|
// const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
// const fetchData = async (showInactive) => {
|
||||||
|
// if ( projectId )
|
||||||
|
// {
|
||||||
|
// const Employees_cache = getCachedData("employeeListByProject");
|
||||||
|
// if (!Employees_cache || Employees_cache.projectId !== projectId) {
|
||||||
|
// setLoading(true);
|
||||||
|
// setError(null);
|
||||||
|
// try {
|
||||||
|
// const response = await ProjectRepository.getEmployeesByProject(
|
||||||
|
// projectId
|
||||||
|
// );
|
||||||
|
// setEmployees(response.data);
|
||||||
|
// cacheData("employeeListByProject", {
|
||||||
|
// data: response.data,
|
||||||
|
// projectId,
|
||||||
|
// });
|
||||||
|
// setLoading(false);
|
||||||
|
// } catch (err) {
|
||||||
|
// setError("Failed to fetch data.");
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// setEmployees(Employees_cache.data);
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// } else
|
||||||
|
// {
|
||||||
|
// const cacheKey = showInactive
|
||||||
|
// ? "allInactiveEmployeeList"
|
||||||
|
// : "allEmployeeList";
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const response = await EmployeeRepository.getAllEmployeeList(
|
||||||
|
// showInactive
|
||||||
|
// );
|
||||||
|
// setEmployees(response.data);
|
||||||
|
// cacheData(cacheKey, { data: response.data });
|
||||||
|
// setLoading(false);
|
||||||
|
// } catch (err) {
|
||||||
|
// setError("Failed to fetch data.");
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// fetchData(showInactive); // Fetch data when the component mounts or projectId changes
|
||||||
|
// }, [projectId]); // Re-fetch when projectId changes
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// employees,
|
||||||
|
// loading,
|
||||||
|
// error,
|
||||||
|
// recallEmployeeData: fetchData,
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// export const useEmployeeProfile = (employeeId) => {
|
||||||
|
// const [loading, setLoading] = useState(true);
|
||||||
|
// const [error, setError] = useState();
|
||||||
|
// const [employee, setEmployees] = useState(null);
|
||||||
|
|
||||||
|
// const fetchData = async () => {
|
||||||
|
// if (!employeeId) {
|
||||||
|
// // Reset the state if no employeeId (e.g., opening for 'add' mode)
|
||||||
|
// setEmployees(null);
|
||||||
|
// setLoading(false);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const Employee_cache = getCachedData("employeeProfile");
|
||||||
|
// if (!Employee_cache || Employee_cache.employeeId !== employeeId) {
|
||||||
|
// EmployeeRepository.getEmployeeProfile(employeeId)
|
||||||
|
// .then((response) => {
|
||||||
|
// setEmployees(response.data);
|
||||||
|
// cacheData("employeeProfile", { data: response.data, employeeId });
|
||||||
|
// setLoading(false);
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// setError("Failed to fetch data.");
|
||||||
|
// setLoading(false);
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// setEmployees(Employee_cache.data);
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// fetchData();
|
||||||
|
// }, [employeeId]);
|
||||||
|
|
||||||
|
// return { employee, loading, error };
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Query ---------------------------------------------------------------------------
|
// Query ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useAllEmployees = ( showInactive ) =>
|
export const useAllEmployees = ( showInactive ) =>
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
@ -164,7 +413,12 @@ export const useEmployeeProfile = ( employeeId ) =>
|
|||||||
const res = await EmployeeRepository.getEmployeeProfile(employeeId);
|
const res = await EmployeeRepository.getEmployeeProfile(employeeId);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
enabled: isEnabled,
|
enabled: isEnabled,
|
||||||
|
// initialData: () => {
|
||||||
|
// if (!queryClient) return null;
|
||||||
|
// return queryClient.getQueryData(['employeeProfile', employeeId]) || null;
|
||||||
|
// },
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -236,11 +236,7 @@ export const useEmployeesByProjectAllocated = (selectedProject) =>
|
|||||||
const res = await ProjectRepository.getProjectAllocation( selectedProject );
|
const res = await ProjectRepository.getProjectAllocation( selectedProject );
|
||||||
return res.data || res
|
return res.data || res
|
||||||
},
|
},
|
||||||
enabled: !!selectedProject,
|
enabled:!!selectedProject
|
||||||
onError: ( error ) =>
|
|
||||||
{
|
|
||||||
showToast(error.message || "Error while Fetching project Allocated Employees", "error");
|
|
||||||
}
|
|
||||||
} )
|
} )
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -260,11 +256,7 @@ export const useProjectDetails = ( projectId,isAuto = true ) =>
|
|||||||
const res = await ProjectRepository.getProjectByprojectId( projectId );
|
const res = await ProjectRepository.getProjectByprojectId( projectId );
|
||||||
return res.data || res;
|
return res.data || res;
|
||||||
},
|
},
|
||||||
enabled: !!projectId && isAuto,
|
enabled:!!projectId && isAuto
|
||||||
onError: ( error ) =>
|
|
||||||
{
|
|
||||||
showToast(error.message || "Error while Fetching project Details", "error");
|
|
||||||
}
|
|
||||||
} )
|
} )
|
||||||
return { projects_Details, loading:isLoading, error, refetch };
|
return { projects_Details, loading:isLoading, error, refetch };
|
||||||
}
|
}
|
||||||
@ -278,11 +270,7 @@ export const useProjectsByEmployee = (employeeId) =>
|
|||||||
const res = await ProjectRepository.getProjectsByEmployee( employeeId );
|
const res = await ProjectRepository.getProjectsByEmployee( employeeId );
|
||||||
return res.data || res;
|
return res.data || res;
|
||||||
},
|
},
|
||||||
enabled: !!employeeId,
|
enabled: !!employeeId
|
||||||
onError: ( error ) =>
|
|
||||||
{
|
|
||||||
showToast(error.message || "Error while Fetching project Employee", "error");
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return {projectList, loading:isLoading,error,refetch }
|
return {projectList, loading:isLoading,error,refetch }
|
||||||
}
|
}
|
||||||
@ -296,57 +284,13 @@ export const useProjectName = () =>
|
|||||||
const res = await ProjectRepository.projectNameList();
|
const res = await ProjectRepository.projectNameList();
|
||||||
return res.data || res;
|
return res.data || res;
|
||||||
},
|
},
|
||||||
onError: ( error ) =>
|
|
||||||
{
|
|
||||||
showToast(error.message || "Error while Fetching project Name", "error");
|
|
||||||
}
|
|
||||||
} )
|
} )
|
||||||
return {projectNames:data,loading:isLoading,Error:error,refetch}
|
return {projectNames:data,loading:isLoading,Error:error,refetch}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useProjectInfra = (projectId) => {
|
|
||||||
const {
|
|
||||||
data: projectInfra,
|
|
||||||
isLoading,
|
|
||||||
error,
|
|
||||||
} = useQuery({
|
|
||||||
queryKey: ["ProjectInfra", projectId],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await ProjectRepository.getProjectInfraByproject(projectId);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
enabled: !!projectId ,
|
|
||||||
onError: (error) => {
|
|
||||||
showToast(error.message || "Error while fetching project infra", "error");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { projectInfra, isLoading, error };
|
// -- Mutation-------------------------------
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const useProjectTasks = (workAreaId,IsExpandedArea=false) =>
|
|
||||||
{
|
|
||||||
const { data:ProjectTaskList,isLoading,error } = useQuery( {
|
|
||||||
queryKey: [ "WorkItems",workAreaId ],
|
|
||||||
queryFn: async () =>
|
|
||||||
{
|
|
||||||
const res = await ProjectRepository.getProjectTasksByWorkArea(workAreaId);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
enabled: !!workAreaId && !!IsExpandedArea,
|
|
||||||
onError: ( error ) =>
|
|
||||||
{
|
|
||||||
showToast(error.message || "Error while Fetching project Tasks", "error");
|
|
||||||
}
|
|
||||||
} )
|
|
||||||
return {ProjectTaskList,isLoading,error}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -- -------------Mutation-------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -393,13 +337,14 @@ export const useUpdateProject = ({ onSuccessCallback }) => {
|
|||||||
return await ProjectRepository.updateProject(projectId, updatedData);
|
return await ProjectRepository.updateProject(projectId, updatedData);
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess: ( data, variables ) =>
|
onSuccess: (data, variables) => {
|
||||||
{
|
|
||||||
const { projectId } = variables;
|
const { projectId } = variables;
|
||||||
|
|
||||||
|
// Invalidate queries
|
||||||
queryClient.invalidateQueries(["ProjectsList"]);
|
queryClient.invalidateQueries(["ProjectsList"]);
|
||||||
queryClient.invalidateQueries(["projectinfo", projectId]);
|
queryClient.invalidateQueries(["projectinfo", projectId]);
|
||||||
|
|
||||||
|
// Emit update event
|
||||||
eventBus.emit("project", {
|
eventBus.emit("project", {
|
||||||
keyword: "Update_Project",
|
keyword: "Update_Project",
|
||||||
response: data,
|
response: data,
|
||||||
@ -426,22 +371,21 @@ export const useUpdateProject = ({ onSuccessCallback }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const useManageProjectInfra = ( {onSuccessCallback} ) =>
|
export const useManageProjectInfra = () => {
|
||||||
{
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ( {infraObject, projectId} ) =>
|
mutationFn: async ({infraObject,projectId}) => {
|
||||||
{
|
|
||||||
return await ProjectRepository.manageProjectInfra(infraObject);
|
return await ProjectRepository.manageProjectInfra(infraObject);
|
||||||
},
|
},
|
||||||
onSuccess: ( data, variables ) =>
|
onSuccess: ( response, variables ) =>
|
||||||
{
|
{
|
||||||
const { projectId } = variables;
|
const { projectId } = variables;
|
||||||
queryClient.invalidateQueries(["ProjectInfra", projectId]);
|
showToast( "Details updated successfully.", "success" );
|
||||||
if (onSuccessCallback) onSuccessCallback(data,variables);
|
|
||||||
|
queryClient.invalidateQueries(["projectinfo", projectId]);
|
||||||
|
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(error.message || "Failed to update Project Infra", "error");
|
showToast(error.message || "Failed to update task details", "error");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -488,58 +432,4 @@ export const useManageProjectAllocation = ({
|
|||||||
isSuccess,
|
isSuccess,
|
||||||
isError,
|
isError,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useManageTask = ({onSuccessCallback}) =>
|
|
||||||
{
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation( {
|
|
||||||
mutationFn: async ( payload ) => await ProjectRepository.manageProjectTasks( payload ),
|
|
||||||
onSuccess: ( data, variables ) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
if ( variables[ 0 ]?.id )
|
|
||||||
{
|
|
||||||
showToast( 'Activity Updated Successfully', 'success' );
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
showToast( 'Activity Created Successfully', 'success' );
|
|
||||||
}
|
|
||||||
queryClient.invalidateQueries(["WorkItems"])
|
|
||||||
if (onSuccessCallback) onSuccessCallback(data);
|
|
||||||
},
|
|
||||||
onError: (error) =>
|
|
||||||
{
|
|
||||||
const message =
|
|
||||||
error?.response?.data?.message || error.message || 'Error occurred during API call';
|
|
||||||
showToast(message, 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDeleteProjectTask = (onSuccessCallback) => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async ( {workItemId, workAreaId} ) =>
|
|
||||||
{
|
|
||||||
return await ProjectRepository.deleteProjectTask(workItemId);
|
|
||||||
},
|
|
||||||
onSuccess: ( _, variables ) =>
|
|
||||||
{
|
|
||||||
showToast("Task deleted successfully", "success");
|
|
||||||
queryClient.invalidateQueries([ "WorkItems",variables.workAreaId]);
|
|
||||||
if (onSuccessCallback) onSuccessCallback();
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
showToast(
|
|
||||||
error?.response?.data?.message || error.message || "Failed to delete task",
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
if (onSuccessCallback) onSuccessCallback();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
@ -239,15 +239,11 @@ export const useSubmitTaskComment = ({ actionAllow, onSuccessCallback }) => {
|
|||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess: ( data,variables ) =>
|
onSuccess: ( data ) =>
|
||||||
{
|
{
|
||||||
|
|
||||||
const workAreaId = variables?.commentsData?.workItem?.workArea?.id;
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["taskList"] });
|
queryClient.invalidateQueries({ queryKey: ["taskList"] });
|
||||||
if (actionAllow) {
|
if (actionAllow) {
|
||||||
showToast( "Review submitted successfully.", "success" );
|
showToast("Review submitted successfully.", "success");
|
||||||
|
|
||||||
queryClient.invalidateQueries({ queryKey: [ "WorkItems", workAreaId ] });
|
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
showToast("Comment sent successfully.", "success");
|
showToast("Comment sent successfully.", "success");
|
||||||
@ -274,7 +270,7 @@ export const useCreateTask = ( {onSuccessCallback, onErrorCallback} = {} ) =>
|
|||||||
},
|
},
|
||||||
onSuccess: ( _, variables ) =>
|
onSuccess: ( _, variables ) =>
|
||||||
{
|
{
|
||||||
queryClient.invalidateQueries(["taskList"]);
|
queryClient.invalidateQueries({ queryKey: ["taskList"] });
|
||||||
showToast("Task Assigned Successfully.", "success");
|
showToast("Task Assigned Successfully.", "success");
|
||||||
if (onSuccessCallback) onSuccessCallback(variables);
|
if (onSuccessCallback) onSuccessCallback(variables);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -185,7 +185,7 @@ const AttendancePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container-fluid">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
|
|||||||
@ -214,7 +214,7 @@ const DailyTask = () => {
|
|||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container-fluid">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
|
|||||||
@ -7,11 +7,11 @@ const TaskPlannng = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="container-fluid">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
{ label: "Daily Task Planning" }
|
{ label: "Daily Task Planning", link: "/activities/task" },
|
||||||
]}
|
]}
|
||||||
></Breadcrumb>
|
></Breadcrumb>
|
||||||
<InfraPlanning/>
|
<InfraPlanning/>
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import DirectoryPageHeader from "./DirectoryPageHeader";
|
|||||||
import ManageBucket from "../../components/Directory/ManageBucket";
|
import ManageBucket from "../../components/Directory/ManageBucket";
|
||||||
import { useFab } from "../../Context/FabContext";
|
import { useFab } from "../../Context/FabContext";
|
||||||
import { DireProvider, useDir } from "../../Context/DireContext";
|
import { DireProvider, useDir } from "../../Context/DireContext";
|
||||||
import NotesCardViewDirectory from "../../components/Directory/NotesCardViewDirectory";
|
|
||||||
|
|
||||||
const Directory = ({ IsPage = true, prefernceContacts }) => {
|
const Directory = ({ IsPage = true, prefernceContacts }) => {
|
||||||
const [projectPrefernce, setPerfence] = useState(null);
|
const [projectPrefernce, setPerfence] = useState(null);
|
||||||
@ -32,17 +31,11 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
const [ContactList, setContactList] = useState([]);
|
const [ContactList, setContactList] = useState([]);
|
||||||
const [contactCategories, setContactCategories] = useState([]);
|
const [contactCategories, setContactCategories] = useState([]);
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [viewType, setViewType] = useState("notes");
|
const [listView, setListView] = useState(false);
|
||||||
const [selectedBucketIds, setSelectedBucketIds] = useState([]);
|
const [selectedBucketIds, setSelectedBucketIds] = useState([]);
|
||||||
const [deleteContact, setDeleteContact] = useState(null);
|
const [deleteContact, setDeleteContact] = useState(null);
|
||||||
const [IsDeleting, setDeleting] = useState(false);
|
const [IsDeleting, setDeleting] = useState(false);
|
||||||
const [openBucketModal, setOpenBucketModal] = useState(false);
|
const [openBucketModal, setOpenBucketModal] = useState(false);
|
||||||
const [notes, setNotes] = useState([]);
|
|
||||||
const [filterAppliedNotes, setFilterAppliedNotes] = useState([]);
|
|
||||||
// const [selectedOrgs, setSelectedOrgs] = useState([]);
|
|
||||||
|
|
||||||
// ✅ Changed to an array for multiple selections
|
|
||||||
const [selectedNoteNames, setSelectedNoteNames] = useState([]);
|
|
||||||
|
|
||||||
const [tempSelectedBucketIds, setTempSelectedBucketIds] = useState([]);
|
const [tempSelectedBucketIds, setTempSelectedBucketIds] = useState([]);
|
||||||
const [tempSelectedCategoryIds, setTempSelectedCategoryIds] = useState([]);
|
const [tempSelectedCategoryIds, setTempSelectedCategoryIds] = useState([]);
|
||||||
@ -78,6 +71,8 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
setIsOpenModal(false);
|
setIsOpenModal(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cacheData("Contacts", {data:updatedContacts,isActive:IsActive});
|
||||||
|
// setContactList(updatedContacts);
|
||||||
refetch(IsActive, prefernceContacts);
|
refetch(IsActive, prefernceContacts);
|
||||||
refetchBucket();
|
refetchBucket();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -254,13 +249,12 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
|
|
||||||
return () => setActions([]);
|
return () => setActions([]);
|
||||||
}, [IsPage, buckets]);
|
}, [IsPage, buckets]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPerfence(prefernceContacts);
|
setPerfence(prefernceContacts);
|
||||||
}, [prefernceContacts]);
|
}, [prefernceContacts]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
{IsPage && (
|
{IsPage && (
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
@ -332,74 +326,84 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="card p-0 mb-0">
|
<div className="card p-2 card-minHeight">
|
||||||
<div className="card-body p-1 pb-0">
|
<DirectoryPageHeader
|
||||||
<DirectoryPageHeader
|
searchText={searchText}
|
||||||
searchText={searchText}
|
setSearchText={setSearchText}
|
||||||
setSearchText={setSearchText}
|
setIsActive={setIsActive}
|
||||||
setIsActive={setIsActive}
|
listView={listView}
|
||||||
viewType={viewType}
|
setListView={setListView}
|
||||||
setViewType={setViewType}
|
filteredBuckets={filteredBuckets}
|
||||||
filteredBuckets={filteredBuckets}
|
tempSelectedBucketIds={tempSelectedBucketIds}
|
||||||
tempSelectedBucketIds={tempSelectedBucketIds}
|
handleTempBucketChange={handleTempBucketChange}
|
||||||
handleTempBucketChange={handleTempBucketChange}
|
filteredCategories={filteredCategories}
|
||||||
filteredCategories={filteredCategories}
|
tempSelectedCategoryIds={tempSelectedCategoryIds}
|
||||||
tempSelectedCategoryIds={tempSelectedCategoryIds}
|
handleTempCategoryChange={handleTempCategoryChange}
|
||||||
handleTempCategoryChange={handleTempCategoryChange}
|
clearFilter={clearFilter}
|
||||||
clearFilter={clearFilter}
|
applyFilter={applyFilter}
|
||||||
applyFilter={applyFilter}
|
loading={loading}
|
||||||
loading={loading}
|
IsActive={IsActive}
|
||||||
IsActive={IsActive}
|
setOpenBucketModal={setOpenBucketModal}
|
||||||
setOpenBucketModal={setOpenBucketModal}
|
/>
|
||||||
contactsToExport={contacts}
|
|
||||||
notesToExport={notes}
|
{/* Messages when listView is false */}
|
||||||
selectedNoteNames={selectedNoteNames}
|
{!listView && (
|
||||||
setSelectedNoteNames={setSelectedNoteNames}
|
<div className="d-flex flex-column justify-content-center align-items-center text-center ">
|
||||||
notesForFilter={notes}
|
{loading && <p className="mt-10">Loading...</p>}
|
||||||
setFilterAppliedNotes={setFilterAppliedNotes}
|
{!loading && contacts?.length === 0 && (
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="card-minHeight mt-0">
|
|
||||||
{(viewType === "card" || viewType === "list" || viewType === "notes") && (
|
|
||||||
<div className="d-flex flex-column justify-content-center align-items-center text-center">
|
|
||||||
{!loading && (viewType === "card" || viewType === "list") && contacts?.length === 0 && (
|
|
||||||
<p className="mt-10">No contact found</p>
|
<p className="mt-10">No contact found</p>
|
||||||
)}
|
)}
|
||||||
|
{!loading && contacts?.length > 0 && currentItems.length === 0 && (
|
||||||
|
<p className="mt-10">No matching contact found</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Table view (listView === true) */}
|
||||||
|
{listView ? (
|
||||||
|
<DirectoryListTableHeader>
|
||||||
|
{loading && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={10}>
|
||||||
|
{" "}
|
||||||
|
<p className="mt-10">Loading...</p>{" "}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && contacts?.length === 0 && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={10}>
|
||||||
|
<p className="mt-10">No contact found</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && currentItems.length === 0 && contacts?.length > 0 && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={10}>
|
||||||
|
<p className="mt-10">No matching contact found</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
(viewType === "card" || viewType === "list") &&
|
currentItems.map((contact) => (
|
||||||
contacts?.length > 0 &&
|
<ListViewDirectory
|
||||||
currentItems.length === 0 && (
|
key={contact.id}
|
||||||
<p className="mt-10">No matching contact found</p>
|
IsActive={IsActive}
|
||||||
)}
|
contact={contact}
|
||||||
</div>
|
setSelectedContact={setSelectedContact}
|
||||||
)}
|
setIsOpenModal={setIsOpenModal}
|
||||||
|
setOpen_contact={setOpen_contact}
|
||||||
{viewType === "list" && (
|
setIsOpenModalNote={setIsOpenModalNote}
|
||||||
<div className="card cursor-pointer mt-5">
|
IsDeleted={setDeleteContact}
|
||||||
<div className="card-body p-2 pb-1">
|
restore={handleDeleteContact}
|
||||||
<DirectoryListTableHeader>
|
/>
|
||||||
{!loading &&
|
))}
|
||||||
currentItems.map((contact) => (
|
</DirectoryListTableHeader>
|
||||||
<ListViewDirectory
|
) : (
|
||||||
key={contact.id}
|
<div className="row mt-5">
|
||||||
IsActive={IsActive}
|
|
||||||
contact={contact}
|
|
||||||
setSelectedContact={setSelectedContact}
|
|
||||||
setIsOpenModal={setIsOpenModal}
|
|
||||||
setOpen_contact={setOpen_contact}
|
|
||||||
setIsOpenModalNote={setIsOpenModalNote}
|
|
||||||
IsDeleted={setDeleteContact}
|
|
||||||
restore={handleDeleteContact}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</DirectoryListTableHeader>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{viewType === "card" && (
|
|
||||||
<div className="row mt-4">
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
currentItems.map((contact) => (
|
currentItems.map((contact) => (
|
||||||
<div
|
<div
|
||||||
@ -421,26 +425,15 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{viewType === "notes" && (
|
|
||||||
<div className="mt-0">
|
|
||||||
<NotesCardViewDirectory
|
|
||||||
notes={notes}
|
|
||||||
setNotesForFilter={setNotes}
|
|
||||||
searchText={searchText}
|
|
||||||
setIsOpenModalNote={setIsOpenModalNote}
|
|
||||||
filterAppliedNotes={filterAppliedNotes}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
{!loading &&
|
{!loading &&
|
||||||
viewType !== "notes" &&
|
|
||||||
contacts?.length > 0 &&
|
contacts?.length > 0 &&
|
||||||
currentItems.length > ITEMS_PER_PAGE && (
|
currentItems.length > ITEMS_PER_PAGE && (
|
||||||
<nav aria-label="Page navigation">
|
<nav aria-label="Page navigation">
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
<li
|
||||||
|
className={`page-item ${currentPage === 1 ? "disabled" : ""}`}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link btn-xs"
|
className="page-link btn-xs"
|
||||||
onClick={() => paginate(currentPage - 1)}
|
onClick={() => paginate(currentPage - 1)}
|
||||||
@ -452,8 +445,9 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
{[...Array(totalPages)].map((_, index) => (
|
{[...Array(totalPages)].map((_, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
className={`page-item ${
|
||||||
}`}
|
currentPage === index + 1 ? "active" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
@ -464,7 +458,11 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<li className={`page-item ${currentPage === totalPages ? "disabled" : ""}`}>
|
<li
|
||||||
|
className={`page-item ${
|
||||||
|
currentPage === totalPages ? "disabled" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
onClick={() => paginate(currentPage + 1)}
|
onClick={() => paginate(currentPage + 1)}
|
||||||
@ -480,4 +478,4 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Directory;
|
export default Directory;
|
||||||
|
|||||||
@ -7,38 +7,33 @@ const DirectoryListTableHeader = ({ children }) => {
|
|||||||
<table className="table px-2">
|
<table className="table px-2">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colSpan={2} className="text-start">
|
<th colSpan={2}>
|
||||||
<div className="d-flex align-items-center gap-1">
|
<div className="d-flex align-items-center gap-1">
|
||||||
<span>Name</span>
|
<span>Name</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-2 text-start">
|
<th className="px-2 text-start">
|
||||||
<div className="d-flex align-items-center gap-1">
|
<div className="d-flex text-center align-items-center gap-1 justify-content-start">
|
||||||
<span>Email</span>
|
<span>Email</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="mx-2 text-start">
|
<th className="mx-2">
|
||||||
<div className="d-flex align-items-center gap-1">
|
<div className="d-flex align-items-center m-0 p-0 gap-1">
|
||||||
<span>Phone</span>
|
<span>Phone</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th colSpan={2} className="mx-2 ps-20 text-start">
|
<th colSpan={2} className="mx-2 ps-20">
|
||||||
<span>Organization</span>
|
Organization
|
||||||
</th>
|
|
||||||
<th className="mx-2 text-start">
|
|
||||||
<span>Category</span>
|
|
||||||
</th>
|
|
||||||
<th className="text-start">
|
|
||||||
<span>Action</span>
|
|
||||||
</th>
|
</th>
|
||||||
|
<th className="mx-2">Category</th>
|
||||||
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="table-border-bottom-0 overflow-auto text-start">
|
<tbody className="table-border-bottom-0 overflow-auto">
|
||||||
{children}
|
{children}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DirectoryListTableHeader;
|
export default DirectoryListTableHeader;
|
||||||
|
|||||||
@ -1,559 +1,196 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { exportToCSV, exportToExcel, printTable, exportToPDF } from "../../utils/tableExportUtils";
|
|
||||||
|
|
||||||
const DirectoryPageHeader = ({
|
const DirectoryPageHeader = ({
|
||||||
searchText,
|
searchText,
|
||||||
setSearchText,
|
setSearchText,
|
||||||
setIsActive,
|
setIsActive,
|
||||||
viewType,
|
listView,
|
||||||
setViewType,
|
setListView,
|
||||||
filteredBuckets,
|
filteredBuckets,
|
||||||
tempSelectedBucketIds,
|
tempSelectedBucketIds,
|
||||||
handleTempBucketChange,
|
handleTempBucketChange,
|
||||||
filteredCategories,
|
filteredCategories,
|
||||||
tempSelectedCategoryIds,
|
tempSelectedCategoryIds,
|
||||||
handleTempCategoryChange,
|
handleTempCategoryChange,
|
||||||
clearFilter,
|
clearFilter,
|
||||||
applyFilter,
|
applyFilter,
|
||||||
loading,
|
loading,
|
||||||
IsActive,
|
IsActive,
|
||||||
contactsToExport,
|
setOpenBucketModal,
|
||||||
notesToExport,
|
|
||||||
selectedNoteNames,
|
|
||||||
setSelectedNoteNames,
|
|
||||||
notesForFilter,
|
|
||||||
setFilterAppliedNotes
|
|
||||||
}) => {
|
}) => {
|
||||||
const [filtered, setFiltered] = useState(0);
|
const [filtered, setFiltered] = useState();
|
||||||
const [filteredNotes, setFilteredNotes] = useState([]);
|
|
||||||
const [noteCreators, setNoteCreators] = useState([]);
|
|
||||||
const [allCreators, setAllCreators] = useState([]);
|
|
||||||
const [allOrganizations, setAllOrganizations] = useState([]);
|
|
||||||
const [filteredOrganizations, setFilteredOrganizations] = useState([]);
|
|
||||||
const [selectedCreators, setSelectedCreators] = useState([]);
|
|
||||||
const [selectedOrgs, setSelectedOrgs] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFiltered(tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length);
|
setFiltered(
|
||||||
}, [tempSelectedBucketIds, tempSelectedCategoryIds]);
|
tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length
|
||||||
|
|
||||||
// New state to track active filters for notes
|
|
||||||
const [notesFilterCount, setNotesFilterCount] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Calculate the number of active filters for notes
|
|
||||||
setNotesFilterCount(selectedCreators.length + selectedOrgs.length);
|
|
||||||
}, [selectedCreators, selectedOrgs]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (viewType === "notes") {
|
|
||||||
if (notesToExport && notesToExport.length > 0) {
|
|
||||||
const uniqueNames = [...new Set(notesToExport.map(note => {
|
|
||||||
const firstName = note.createdBy?.firstName || "";
|
|
||||||
const lastName = note.createdBy?.lastName || "";
|
|
||||||
return `${firstName} ${lastName}`.trim();
|
|
||||||
}).filter(name => name !== ""))];
|
|
||||||
setNoteCreators(uniqueNames.sort());
|
|
||||||
} else {
|
|
||||||
setNoteCreators([]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setNoteCreators([]);
|
|
||||||
}
|
|
||||||
}, [notesToExport, viewType]);
|
|
||||||
|
|
||||||
// Separate effect to clear selection only when switching away from notes
|
|
||||||
useEffect(() => {
|
|
||||||
if (viewType !== "notes" && selectedNoteNames.length > 0) {
|
|
||||||
setSelectedNoteNames([]);
|
|
||||||
}
|
|
||||||
}, [viewType]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const creatorsSet = new Set();
|
|
||||||
const orgsSet = new Set();
|
|
||||||
|
|
||||||
notesForFilter.forEach((note) => {
|
|
||||||
const creator = `${note.createdBy?.firstName || ""} ${note.createdBy?.lastName || ""}`.trim();
|
|
||||||
if (creator) creatorsSet.add(creator);
|
|
||||||
|
|
||||||
const org = note.organizationName;
|
|
||||||
if (org) orgsSet.add(org);
|
|
||||||
});
|
|
||||||
|
|
||||||
setAllCreators([...creatorsSet].sort());
|
|
||||||
setAllOrganizations([...orgsSet].sort());
|
|
||||||
setFilteredOrganizations([...orgsSet].sort());
|
|
||||||
}, [notesForFilter])
|
|
||||||
|
|
||||||
|
|
||||||
const handleToggleNoteName = (name) => {
|
|
||||||
setSelectedNoteNames(prevSelectedNames => {
|
|
||||||
if (prevSelectedNames.includes(name)) {
|
|
||||||
return prevSelectedNames.filter(n => n !== name);
|
|
||||||
} else {
|
|
||||||
return [...prevSelectedNames, name];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateFilteredOrganizations = () => {
|
|
||||||
if (selectedCreators.length === 0) {
|
|
||||||
setFilteredOrganizations(allOrganizations);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredOrgsSet = new Set();
|
|
||||||
notesForFilter.forEach((note) => {
|
|
||||||
const creator = `${note.createdBy?.firstName || ""} ${note.createdBy?.lastName || ""}`.trim();
|
|
||||||
if (selectedCreators.includes(creator)) {
|
|
||||||
if (note.organizationName) {
|
|
||||||
filteredOrgsSet.add(note.organizationName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setFilteredOrganizations([...filteredOrgsSet].sort());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleCreator = (name) => {
|
|
||||||
const updated = selectedCreators.includes(name)
|
|
||||||
? selectedCreators.filter((n) => n !== name)
|
|
||||||
: [...selectedCreators, name];
|
|
||||||
|
|
||||||
setSelectedCreators(updated);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleOrg = (name) => {
|
|
||||||
const updated = selectedOrgs.includes(name)
|
|
||||||
? selectedOrgs.filter((n) => n !== name)
|
|
||||||
: [...selectedOrgs, name];
|
|
||||||
|
|
||||||
setSelectedOrgs(updated);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExport = (type) => {
|
|
||||||
let dataToExport = [];
|
|
||||||
|
|
||||||
if (viewType === "notes") {
|
|
||||||
if (!notesToExport || notesToExport.length === 0) {
|
|
||||||
console.warn("No notes to export.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodeHtmlEntities = (html) => {
|
|
||||||
const textarea = document.createElement("textarea");
|
|
||||||
textarea.innerHTML = html;
|
|
||||||
return textarea.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanNoteText = (html) => {
|
|
||||||
if (!html) return "";
|
|
||||||
const stripped = html.replace(/<[^>]+>/g, "");
|
|
||||||
const decoded = decodeHtmlEntities(stripped);
|
|
||||||
return decoded.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanName = (name) => {
|
|
||||||
if (!name) return "";
|
|
||||||
return name.replace(/\u00A0/g, " ").replace(/\s+/g, " ").trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
dataToExport = notesToExport.map(note => ({
|
|
||||||
"Name": cleanName(`${note.createdBy?.firstName || ""} ${note.createdBy?.lastName || ""}`),
|
|
||||||
"Notes": cleanNoteText(note.note),
|
|
||||||
"Created At": note.createdAt
|
|
||||||
? new Date(note.createdAt).toLocaleString("en-IN")
|
|
||||||
: "",
|
|
||||||
"Updated At": note.updatedAt
|
|
||||||
? new Date(note.updatedAt).toLocaleString("en-IN")
|
|
||||||
: "",
|
|
||||||
"Updated By": cleanName(
|
|
||||||
`${note.updatedBy?.firstName || ""} ${note.updatedBy?.lastName || ""}`
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (!contactsToExport || contactsToExport.length === 0) {
|
|
||||||
console.warn("No contacts to export.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dataToExport = contactsToExport.map(contact => ({
|
|
||||||
Name: contact.name || '',
|
|
||||||
Organization: contact.organization || '',
|
|
||||||
Email: contact.contactEmails?.map(email => email.emailAddress).join(', ') || '',
|
|
||||||
Phone: contact.contactPhones?.map(phone => phone.phoneNumber).join(', ') || '',
|
|
||||||
Category: contact.contactCategory?.name || '',
|
|
||||||
Tags: contact.tags?.map(tag => tag.name).join(', ') || '',
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const today = new Date();
|
|
||||||
const formattedDate = `${today.getFullYear()}${String(today.getMonth() + 1).padStart(2, '0')}${String(today.getDate()).padStart(2, '0')}`;
|
|
||||||
|
|
||||||
const filename =
|
|
||||||
viewType === "notes"
|
|
||||||
? `Directory_Notes_${formattedDate}`
|
|
||||||
: `Directory_Contacts_${formattedDate}`;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case "csv":
|
|
||||||
exportToCSV(dataToExport, filename);
|
|
||||||
break;
|
|
||||||
case "excel":
|
|
||||||
exportToExcel(dataToExport, filename);
|
|
||||||
break;
|
|
||||||
case "pdf":
|
|
||||||
exportToPDF(dataToExport, filename);
|
|
||||||
break;
|
|
||||||
case "print":
|
|
||||||
printTable(dataToExport, filename);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyCombinedFilter = () => {
|
|
||||||
const lowerSearch = searchText?.toLowerCase() || "";
|
|
||||||
|
|
||||||
const filtered = notesForFilter.filter((noteItem) => {
|
|
||||||
const creator = `${noteItem.createdBy?.firstName || ""} ${noteItem.createdBy?.lastName || ""}`.trim();
|
|
||||||
const org = noteItem.organizationName;
|
|
||||||
|
|
||||||
const matchesCreator = selectedCreators.length === 0 || selectedCreators.includes(creator);
|
|
||||||
const matchesOrg = selectedOrgs.length === 0 || selectedOrgs.includes(org);
|
|
||||||
|
|
||||||
const plainNote = noteItem?.note?.replace(/<[^>]+>/g, "").toLowerCase();
|
|
||||||
|
|
||||||
const stringValues = [];
|
|
||||||
const extractStrings = (obj) => {
|
|
||||||
for (const key in obj) {
|
|
||||||
const value = obj[key];
|
|
||||||
if (typeof value === "string") {
|
|
||||||
stringValues.push(value.toLowerCase());
|
|
||||||
} else if (typeof value === "object" && value !== null) {
|
|
||||||
extractStrings(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
extractStrings(noteItem);
|
|
||||||
stringValues.push(plainNote, creator.toLowerCase());
|
|
||||||
|
|
||||||
const matchesSearch = stringValues.some((val) => val.includes(lowerSearch));
|
|
||||||
|
|
||||||
return matchesCreator && matchesOrg && matchesSearch;
|
|
||||||
});
|
|
||||||
|
|
||||||
setFilteredNotes(filtered);
|
|
||||||
setFilterAppliedNotes(filtered);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="row mx-0 px-0 align-items-center mt-0">
|
|
||||||
<div className="col-12 col-md-6 mb-0 px-1 d-flex align-items-center gap-4">
|
|
||||||
<ul className="nav nav-tabs mb-0" role="tablist">
|
|
||||||
<li className="nav-item" role="presentation">
|
|
||||||
<button
|
|
||||||
className={`nav-link ${viewType === "notes" ? "active" : ""}`}
|
|
||||||
onClick={() => setViewType("notes")}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<i className="bx bx-note me-1"></i> Notes
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item" role="presentation">
|
|
||||||
<button
|
|
||||||
className={`nav-link ${viewType === "card" ? "active" : ""}`}
|
|
||||||
onClick={() => setViewType("card")}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<i className="bx bx-user me-1"></i> Contacts
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr className="my-0 mb-2" style={{ borderTop: "1px solid #dee2e6" }} />
|
|
||||||
|
|
||||||
<div className="row mx-0 px-0 align-items-center mt-0">
|
|
||||||
<div className="col-12 col-md-6 mb-2 px-5 d-flex align-items-center gap-4">
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="search"
|
|
||||||
className="form-control me-0"
|
|
||||||
placeholder={viewType === "notes" ? "Search Notes..." : "Search Contact..."}
|
|
||||||
value={searchText}
|
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
|
||||||
style={{ width: "200px", height: "30px" }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Filter by funnel icon for Notes view */}
|
|
||||||
{viewType === "notes" && (
|
|
||||||
<div className="dropdown" style={{ width: "fit-content" }}>
|
|
||||||
<a
|
|
||||||
className="dropdown-toggle hide-arrow cursor-pointer d-flex align-items-center position-relative"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<i className={`fa-solid fa-filter ms-1 fs-5 ${notesFilterCount > 0 ? "text-primary" : "text-muted"}`}></i>
|
|
||||||
{notesFilterCount > 0 && (
|
|
||||||
<span className="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-warning" style={{ fontSize: "0.4rem" }}>
|
|
||||||
{notesFilterCount}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div className="dropdown-menu p-0" style={{ minWidth: "700px" }}>
|
|
||||||
{/* Scrollable Filter Content */}
|
|
||||||
<div
|
|
||||||
className="p-3"
|
|
||||||
style={{
|
|
||||||
maxHeight: "300px",
|
|
||||||
overflowY: "auto",
|
|
||||||
overflowX: "hidden",
|
|
||||||
whiteSpace: "normal"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="d-flex gap-3">
|
|
||||||
{/* Created By */}
|
|
||||||
<div style={{ flex: 0.50, maxHeight: "260px", overflowY: "auto" }}>
|
|
||||||
<div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}>
|
|
||||||
<p className="text-muted mb-2 pt-2">Created By</p>
|
|
||||||
</div>
|
|
||||||
{allCreators.map((name, idx) => (
|
|
||||||
<div className="form-check mb-1" key={`creator-${idx}`}>
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id={`creator-${idx}`}
|
|
||||||
checked={selectedCreators.includes(name)}
|
|
||||||
onChange={() => handleToggleCreator(name)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label text-nowrap" htmlFor={`creator-${idx}`}>
|
|
||||||
{name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Organization */}
|
|
||||||
<div style={{ flex: 1, maxHeight: "260px", overflowY: "auto",overflowX: "hidden", }}>
|
|
||||||
<div style={{ position: "sticky", top: 0, background: "#fff", zIndex: 1 }}>
|
|
||||||
<p className="text-muted mb-2 pt-2">Organization</p>
|
|
||||||
</div>
|
|
||||||
{filteredOrganizations.map((org, idx) => (
|
|
||||||
<div className="form-check mb-1" key={`org-${idx}`}>
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id={`org-${idx}`}
|
|
||||||
checked={selectedOrgs.includes(org)}
|
|
||||||
onChange={() => handleToggleOrg(org)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label text-nowrap" htmlFor={`org-${idx}`}>
|
|
||||||
{org}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sticky Footer Buttons */}
|
|
||||||
<div
|
|
||||||
className="d-flex justify-content-end gap-2 p-2 "
|
|
||||||
style={{
|
|
||||||
background: "#fff",
|
|
||||||
position: "sticky",
|
|
||||||
bottom: 0
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="btn btn-xs btn-secondary"
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedCreators([]);
|
|
||||||
setSelectedOrgs([]);
|
|
||||||
setFilteredOrganizations(allOrganizations);
|
|
||||||
setFilterAppliedNotes(notesForFilter);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-xs btn-primary"
|
|
||||||
onClick={() => {
|
|
||||||
applyCombinedFilter();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Apply Filter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
{(viewType === "card" || viewType === "list") && (
|
|
||||||
<div className="d-flex gap-2">
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`btn btn-xs ${viewType === "card" ? "btn-primary" : "btn-outline-primary"}`}
|
|
||||||
onClick={() => setViewType("card")}
|
|
||||||
>
|
|
||||||
<i className="bx bx-grid-alt"></i>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`btn btn-xs ${viewType === "list" ? "btn-primary" : "btn-outline-primary"}`}
|
|
||||||
onClick={() => setViewType("list")}
|
|
||||||
>
|
|
||||||
<i className="bx bx-list-ul me-1"></i>
|
|
||||||
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Filter by funnel icon for Contacts view (retains numerical badge) */}
|
|
||||||
{viewType !== "notes" && (
|
|
||||||
<div className="dropdown-center" style={{ width: "fit-content" }}>
|
|
||||||
<a
|
|
||||||
className="dropdown-toggle hide-arrow cursor-pointer d-flex align-items-center position-relative"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<i className={`fa-solid fa-filter ms-1 fs-5 ${filtered > 0 ? "text-primary" : "text-muted"}`}></i>
|
|
||||||
{filtered > 0 && (
|
|
||||||
<span className="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-warning" style={{ fontSize: "0.4rem" }}>
|
|
||||||
{filtered}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<ul className="dropdown-menu p-3" style={{ width: "700px" }}>
|
|
||||||
<p className="text-muted m-0 h6">Filter by</p>
|
|
||||||
|
|
||||||
<div className="d-flex flex-nowrap">
|
|
||||||
<div className="mt-1 me-4" style={{ flexBasis: "50%" }}>
|
|
||||||
<p className="text-small mb-1">Buckets</p>
|
|
||||||
<div className="d-flex flex-wrap">
|
|
||||||
{filteredBuckets.map(({ id, name }) => (
|
|
||||||
<div className="form-check me-3 mb-1" style={{ minWidth: "calc(50% - 15px)" }} key={id}>
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id={`bucket-${id}`}
|
|
||||||
checked={tempSelectedBucketIds.includes(id)}
|
|
||||||
onChange={() => handleTempBucketChange(id)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label text-nowrap text-small" htmlFor={`bucket-${id}`}>
|
|
||||||
{name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-1" style={{ flexBasis: "50%" }}>
|
|
||||||
<p className="text-small mb-1">Categories</p>
|
|
||||||
<div className="d-flex flex-wrap">
|
|
||||||
{filteredCategories.map(({ id, name }) => (
|
|
||||||
<div className="form-check me-3 mb-1" style={{ minWidth: "calc(50% - 15px)" }} key={id}>
|
|
||||||
<input
|
|
||||||
className="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id={`cat-${id}`}
|
|
||||||
checked={tempSelectedCategoryIds.includes(id)}
|
|
||||||
onChange={() => handleTempCategoryChange(id)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label text-nowrap text-small" htmlFor={`cat-${id}`}>
|
|
||||||
{name}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="d-flex justify-content-end gap-2 mt-1">
|
|
||||||
<button
|
|
||||||
className="btn btn-xs btn-secondary"
|
|
||||||
onClick={(e) => {
|
|
||||||
// e.stopPropagation();
|
|
||||||
clearFilter();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-xs btn-primary"
|
|
||||||
onClick={(e) => {
|
|
||||||
applyFilter();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Apply Filter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12 col-md-6 mb-2 px-5 d-flex justify-content-end align-items-center gap-2">
|
|
||||||
{(viewType === "list" || viewType === "card") && (
|
|
||||||
<label className="switch switch-primary mb-0">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="switch-input me-3"
|
|
||||||
onChange={() => setIsActive(!IsActive)}
|
|
||||||
checked={!IsActive}
|
|
||||||
disabled={loading}
|
|
||||||
/>
|
|
||||||
<span className="switch-toggle-slider">
|
|
||||||
<span className="switch-on"></span>
|
|
||||||
<span className="switch-off"></span>
|
|
||||||
</span>
|
|
||||||
<span className="ms-12">Show Inactive Contacts</span>
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="btn-group">
|
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-label-secondary dropdown-toggle"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<i className="bx bx-export me-2 bx-sm"></i>Export
|
|
||||||
</button>
|
|
||||||
<ul className="dropdown-menu">
|
|
||||||
<li>
|
|
||||||
<a className="dropdown-item" href="#" onClick={(e) => { e.preventDefault(); handleExport("csv"); }}>
|
|
||||||
<i className="bx bx-file me-1"></i> CSV
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a className="dropdown-item" href="#" onClick={(e) => { e.preventDefault(); handleExport("excel"); }}>
|
|
||||||
<i className="bx bxs-file-export me-1"></i> Excel
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{viewType !== "notes" && (
|
|
||||||
<li>
|
|
||||||
<a className="dropdown-item" href="#" onClick={(e) => { e.preventDefault(); handleExport("pdf"); }}>
|
|
||||||
<i className="bx bxs-file-pdf me-1"></i> PDF
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
|
}, [tempSelectedBucketIds, tempSelectedCategoryIds]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* <div className="row">vikas</div> */}
|
||||||
|
<div className="row mx-0 px-0 align-items-center mt-2">
|
||||||
|
<div className="col-12 col-md-6 mb-2 px-1 d-flex align-items-center gap-4 ">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
className="form-control form-control-sm me-2"
|
||||||
|
placeholder="Search Contact..."
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
|
style={{ width: "200px" }}
|
||||||
|
/>
|
||||||
|
<div className="d-flex gap-2 ">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-xs ${
|
||||||
|
!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"
|
||||||
|
>
|
||||||
|
<i className="bx bx-grid-alt"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-xs ${
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<i className="bx bx-list-ul "></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="dropdown" style={{ width: "fit-content" }}>
|
||||||
|
<div className="dropdown" style={{ width: "fit-content" }}>
|
||||||
|
<a
|
||||||
|
className="dropdown-toggle hide-arrow cursor-pointer d-flex align-items-center position-relative"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className={`fa-solid fa-filter ms-1 fs-5 ${
|
||||||
|
filtered > 0 ? "text-primary" : "text-muted"
|
||||||
|
}`}
|
||||||
|
></i>
|
||||||
|
|
||||||
|
{filtered > 0 && (
|
||||||
|
<span
|
||||||
|
className="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-warning"
|
||||||
|
style={{ fontSize: "0.4rem" }}
|
||||||
|
>
|
||||||
|
{filtered}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul className="dropdown-menu p-3" style={{ width: "320px" }}>
|
||||||
|
<div>
|
||||||
|
<p className="text-muted m-0 h6 ">Filter by</p>
|
||||||
|
|
||||||
|
{/* Bucket Filter */}
|
||||||
|
<div className="mt-1">
|
||||||
|
<p className="text-small mb-1 ">Buckets</p>
|
||||||
|
<div className="d-flex flex-wrap">
|
||||||
|
{filteredBuckets.map(({ id, name }) => (
|
||||||
|
<div
|
||||||
|
className="form-check me-3 mb-1"
|
||||||
|
style={{ minWidth: "33.33%" }}
|
||||||
|
key={id}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id={`bucket-${id}`}
|
||||||
|
checked={tempSelectedBucketIds.includes(id)}
|
||||||
|
onChange={() => handleTempBucketChange(id)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="form-check-label text-nowrap text-small "
|
||||||
|
htmlFor={`bucket-${id}`}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr className="m-0" />
|
||||||
|
{/* Category Filter */}
|
||||||
|
<div className="mt-1">
|
||||||
|
<p className="text-small mb-1 ">Categories</p>
|
||||||
|
<div className="d-flex flex-wrap">
|
||||||
|
{filteredCategories.map(({ id, name }) => (
|
||||||
|
<div
|
||||||
|
className="form-check me-3 mb-1"
|
||||||
|
style={{ minWidth: "33.33%" }}
|
||||||
|
key={id}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id={`cat-${id}`}
|
||||||
|
checked={tempSelectedCategoryIds.includes(id)}
|
||||||
|
onChange={() => handleTempCategoryChange(id)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="form-check-label text-nowrap text-small"
|
||||||
|
htmlFor={`cat-${id}`}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex justify-content-end gap-2 mt-1">
|
||||||
|
<button
|
||||||
|
className="btn btn-xs btn-secondary"
|
||||||
|
onClick={clearFilter}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-xs btn-primary"
|
||||||
|
onClick={applyFilter}
|
||||||
|
>
|
||||||
|
Apply Filter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6 mb-2 px-1 d-flex justify-content-end gap-2 align-items-center text-end">
|
||||||
|
<label className="switch switch-primary align-self-start mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="switch-input me-3"
|
||||||
|
onChange={() => setIsActive(!IsActive)}
|
||||||
|
value={IsActive}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
<span className="switch-toggle-slider">
|
||||||
|
<span className="switch-on"></span>
|
||||||
|
<span className="switch-off"></span>
|
||||||
|
</span>
|
||||||
|
<span className=" list-inline-item ms-12 ">
|
||||||
|
Show Inactive Contacts
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DirectoryPageHeader;
|
export default DirectoryPageHeader;
|
||||||
|
|||||||
@ -19,13 +19,13 @@ const LoginPage = () => {
|
|||||||
|
|
||||||
const loginSchema = IsLoginWithOTP
|
const loginSchema = IsLoginWithOTP
|
||||||
? z.object({
|
? z.object({
|
||||||
username: z.string().email({ message: "Valid email required" }),
|
username: z.string().email({ message: "Valid email required" }),
|
||||||
})
|
})
|
||||||
: z.object({
|
: z.object({
|
||||||
username: z.string().email({ message: "Valid email required" }),
|
username: z.string().email({ message: "Valid email required" }),
|
||||||
password: z.string().min(1, { message: "Password required" }),
|
password: z.string().min(1, { message: "Password required" }),
|
||||||
rememberMe: z.boolean(),
|
rememberMe: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -53,7 +53,6 @@ const LoginPage = () => {
|
|||||||
navigate("/dashboard");
|
navigate("/dashboard");
|
||||||
} else {
|
} else {
|
||||||
await AuthRepository.sendOTP({ email: data.username });
|
await AuthRepository.sendOTP({ email: data.username });
|
||||||
showToast("OTP has been sent to your email.", "success");
|
|
||||||
localStorage.setItem("otpUsername", data.username);
|
localStorage.setItem("otpUsername", data.username);
|
||||||
localStorage.setItem("otpSentTime", now.toString());
|
localStorage.setItem("otpSentTime", now.toString());
|
||||||
navigate("/auth/login-otp");
|
navigate("/auth/login-otp");
|
||||||
@ -115,18 +114,18 @@ const LoginPage = () => {
|
|||||||
<label className="form-label" htmlFor="password">
|
<label className="form-label" htmlFor="password">
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
<div className="input-group input-group-merge d-flex align-items-center border rounded px-2">
|
<div className="input-group input-group-merge">
|
||||||
<input
|
<input
|
||||||
type={hidepass ? "password" : "text"}
|
type={hidepass ? "password" : "text"}
|
||||||
autoComplete="true"
|
autoComplete="true"
|
||||||
id="password"
|
id="password"
|
||||||
{...register("password")}
|
{...register("password")}
|
||||||
className="form-control form-control-xl border-0 shadow-none"
|
className="form-control"
|
||||||
placeholder="••••••••••••"
|
placeholder="••••••••••••"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-link p-0 ms-2 "
|
className="btn border-top border-end border-bottom"
|
||||||
onClick={() => setHidepass(!hidepass)}
|
onClick={() => setHidepass(!hidepass)}
|
||||||
style={{
|
style={{
|
||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
@ -151,7 +150,6 @@ const LoginPage = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="mb-3 d-flex justify-content-between">
|
<div className="mb-3 d-flex justify-content-between">
|
||||||
<div className="form-check d-flex">
|
<div className="form-check d-flex">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -67,13 +67,9 @@ const ResetPasswordPage = () => {
|
|||||||
navigate("/auth/login", { replace: true });
|
navigate("/auth/login", { replace: true });
|
||||||
// setLoading(false);
|
// setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debugger;
|
showToast("Link is expries or Invalid ", "error");
|
||||||
|
setTokenExpired(true);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (error?.response?.status === 400) {
|
|
||||||
showToast("Please check valid Credentials", "error");
|
|
||||||
} else {
|
|
||||||
setTokenExpired(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,10 +77,7 @@ const ResetPasswordPage = () => {
|
|||||||
return (
|
return (
|
||||||
<AuthWrapper>
|
<AuthWrapper>
|
||||||
<h4 className="mb-2 ">Invalid Link 🔒</h4>
|
<h4 className="mb-2 ">Invalid Link 🔒</h4>
|
||||||
<p className="mb-4" style={{ fontSize: "12px" }}>
|
<p className="mb-4" style={{fontSize: "12px"}}>This link appears to be invalid or expired. Please use the 'Forgot Password' feature to set your new password.</p>
|
||||||
This link appears to be invalid or expired. Please use the 'Forgot
|
|
||||||
Password' feature to set your new password.
|
|
||||||
</p>
|
|
||||||
<div className="text-center mb-4">
|
<div className="text-center mb-4">
|
||||||
<Link to="/auth/forgot-password" className="btn btn-outline-primary">
|
<Link to="/auth/forgot-password" className="btn btn-outline-primary">
|
||||||
Go to Forgot Password
|
Go to Forgot Password
|
||||||
@ -149,6 +142,7 @@ const ResetPasswordPage = () => {
|
|||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
borderBottomLeftRadius: 0,
|
borderBottomLeftRadius: 0,
|
||||||
borderLeft: 0,
|
borderLeft: 0,
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hidepass ? (
|
{hidepass ? (
|
||||||
@ -191,6 +185,7 @@ const ResetPasswordPage = () => {
|
|||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
borderBottomLeftRadius: 0,
|
borderBottomLeftRadius: 0,
|
||||||
borderLeft: 0,
|
borderLeft: 0,
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{hidepass1 ? (
|
{hidepass1 ? (
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { hasUserPermission } from "../../utils/authUtils";
|
|||||||
import { ITEMS_PER_PAGE, MANAGE_EMPLOYEES } from "../../utils/constants";
|
import { ITEMS_PER_PAGE, MANAGE_EMPLOYEES } from "../../utils/constants";
|
||||||
import { clearCacheKey } from "../../slices/apiDataManager";
|
import { clearCacheKey } from "../../slices/apiDataManager";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp
|
import SuspendEmp from "../../components/Employee/SuspendEmp";
|
||||||
import {
|
import {
|
||||||
exportToCSV,
|
exportToCSV,
|
||||||
exportToExcel,
|
exportToExcel,
|
||||||
@ -29,19 +29,16 @@ import GlobalModel from "../../components/common/GlobalModel";
|
|||||||
import usePagination from "../../hooks/usePagination";
|
import usePagination from "../../hooks/usePagination";
|
||||||
|
|
||||||
const EmployeeList = () => {
|
const EmployeeList = () => {
|
||||||
const selectedProjectId = useSelector(
|
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
|
||||||
(store) => store.localVariables.projectId
|
const [selectedProject, setSelectedProject] = useState(() => selectedProjectId || "");
|
||||||
);
|
const { projects, loading: projectLoading } = useProjects();
|
||||||
|
|
||||||
const [showInactive, setShowInactive] = useState(false);
|
const [showInactive, setShowInactive] = useState(false);
|
||||||
const [showAllEmployees, setShowAllEmployees] = useState(false);
|
const [showAllEmployees, setShowAllEmployees] = useState(false);
|
||||||
const Manage_Employee = useHasUserPermission(MANAGE_EMPLOYEES);
|
const Manage_Employee = useHasUserPermission(MANAGE_EMPLOYEES);
|
||||||
|
|
||||||
const { employees, loading, setLoading, error, recallEmployeeData } =
|
const { employees, loading, setLoading, error, recallEmployeeData } =
|
||||||
useEmployeesAllOrByProjectId(
|
useEmployeesAllOrByProjectId(showAllEmployees ? null : selectedProject, showInactive);
|
||||||
showAllEmployees ? null : selectedProjectId, // Use selectedProjectId here
|
const [projectsList, setProjectsList] = useState(projects || []);
|
||||||
showInactive
|
|
||||||
);
|
|
||||||
|
|
||||||
const [employeeList, setEmployeeList] = useState([]);
|
const [employeeList, setEmployeeList] = useState([]);
|
||||||
const [ modelConfig, setModelConfig ] = useState();
|
const [ modelConfig, setModelConfig ] = useState();
|
||||||
@ -49,6 +46,7 @@ const EmployeeList = () => {
|
|||||||
// const [currentPage, setCurrentPage] = useState(1);
|
// const [currentPage, setCurrentPage] = useState(1);
|
||||||
// const [itemsPerPage] = useState(ITEMS_PER_PAGE);
|
// const [itemsPerPage] = useState(ITEMS_PER_PAGE);
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
|
const [isEmployeeModalOpen, setIsEmployeeModalOpen] = useState(false);
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [filteredData, setFilteredData] = useState([]);
|
const [filteredData, setFilteredData] = useState([]);
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
@ -68,27 +66,14 @@ const EmployeeList = () => {
|
|||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
/**
|
const handleSearch = (e) => {
|
||||||
* Applies the search filter to a given array of employee data.
|
const value = e.target.value.toLowerCase();
|
||||||
* @param {Array} data - The array of employee objects to filter.
|
setSearchText(value);
|
||||||
* @param {string} text - The search text.
|
|
||||||
* @returns {Array} The filtered array.
|
|
||||||
*/
|
|
||||||
const applySearchFilter = (data, text) => {
|
|
||||||
if (!text) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
const lowercasedText = text.toLowerCase().trim(); // Ensure search text is trimmed and lowercase
|
|
||||||
|
|
||||||
return data.filter((item) => {
|
if (!employeeList.length) return;
|
||||||
// **IMPROVED FULL NAME CONSTRUCTION**
|
|
||||||
const firstName = item.firstName || "";
|
|
||||||
const middleName = item.middleName || "";
|
|
||||||
const lastName = item.lastName || "";
|
|
||||||
|
|
||||||
// Join parts, then trim any excess spaces if a middle name is missing
|
|
||||||
const fullName = `${firstName} ${middleName} ${lastName}`.toLowerCase().trim().replace(/\s+/g, ' ');
|
|
||||||
|
|
||||||
|
const results = employeeList.filter((item) => {
|
||||||
|
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
|
||||||
const email = item.email ? item.email.toLowerCase() : "";
|
const email = item.email ? item.email.toLowerCase() : "";
|
||||||
const phoneNumber = item.phoneNumber ? item.phoneNumber.toLowerCase() : "";
|
const phoneNumber = item.phoneNumber ? item.phoneNumber.toLowerCase() : "";
|
||||||
const jobRole = item.jobRole ? item.jobRole.toLowerCase() : "";
|
const jobRole = item.jobRole ? item.jobRole.toLowerCase() : "";
|
||||||
@ -100,12 +85,8 @@ const EmployeeList = () => {
|
|||||||
jobRole.includes(value)
|
jobRole.includes(value)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearch = (e) => {
|
setFilteredData(results);
|
||||||
const value = e.target.value;
|
|
||||||
setSearchText(value);
|
|
||||||
setCurrentPage(1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -127,11 +108,11 @@ const EmployeeList = () => {
|
|||||||
modalElement.classList.remove("show");
|
modalElement.classList.remove("show");
|
||||||
modalElement.style.display = "none";
|
modalElement.style.display = "none";
|
||||||
document.body.classList.remove("modal-open");
|
document.body.classList.remove("modal-open");
|
||||||
document.querySelector(".modal-backdrop")?.remove();
|
document.querySelector(".modal-backdrop").remove();
|
||||||
}
|
}
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
clearCacheKey("employeeProfile");
|
clearCacheKey("employeeProfile");
|
||||||
recallEmployeeData(showInactive, showAllEmployees ? null : selectedProjectId); // Use selectedProjectId here
|
recallEmployeeData(showInactive);
|
||||||
};
|
};
|
||||||
const handleShow = () => setShowModal(true);
|
const handleShow = () => setShowModal(true);
|
||||||
const handleClose = () => setShowModal( false );
|
const handleClose = () => setShowModal( false );
|
||||||
@ -152,9 +133,32 @@ const EmployeeList = () => {
|
|||||||
setEmployeeList(sorted);
|
setEmployeeList(sorted);
|
||||||
setFilteredData(sorted);
|
setFilteredData(sorted);
|
||||||
}
|
}
|
||||||
}, [loading, employees, selectedProjectId, showAllEmployees]);
|
}, [loading, employees, selectedProject, showAllEmployees]);
|
||||||
|
|
||||||
|
|
||||||
|
// const suspendEmployee = (id) => {
|
||||||
|
// setemployeeLodaing(true);
|
||||||
|
// EmployeeRepository.deleteEmployee(id)
|
||||||
|
// .then((response) => {
|
||||||
|
// showToast("Employee deleted successfully.", "success");
|
||||||
|
// clearCacheKey("employeeListByProject");
|
||||||
|
// clearCacheKey("allEmployeeList");
|
||||||
|
// clearCacheKey("allInactiveEmployeeList");
|
||||||
|
// clearCacheKey("employeeProfile");
|
||||||
|
// setEmployeeList([]);
|
||||||
|
// recallEmployeeData(showInactive);
|
||||||
|
// setemployeeLodaing(false);
|
||||||
|
// setIsDeleteModalOpen(false);
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// const message =
|
||||||
|
// error.response?.data?.message ||
|
||||||
|
// error.message ||
|
||||||
|
// "An unexpected error occurred";
|
||||||
|
// showToast(message, "error");
|
||||||
|
// setemployeeLodaing(false);
|
||||||
|
// setIsDeleteModalOpen(false);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
const handleConfigData = (config) => {
|
const handleConfigData = (config) => {
|
||||||
setModelConfig(config);
|
setModelConfig(config);
|
||||||
@ -190,7 +194,7 @@ const EmployeeList = () => {
|
|||||||
|
|
||||||
const handleToggle = (e) => {
|
const handleToggle = (e) => {
|
||||||
setShowInactive(e.target.checked);
|
setShowInactive(e.target.checked);
|
||||||
recallEmployeeData(e.target.checked, showAllEmployees ? null : selectedProjectId); // Use selectedProjectId here
|
recallEmployeeData(e.target.checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAllEmployeesToggle = (e) => {
|
const handleAllEmployeesToggle = (e) => {
|
||||||
@ -203,6 +207,8 @@ const handleAllEmployeesToggle = (e) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleEmployeeModel = (id) => {
|
const handleEmployeeModel = (id) => {
|
||||||
setSelecedEmployeeId(id);
|
setSelecedEmployeeId(id);
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
@ -213,19 +219,24 @@ const handleAllEmployeesToggle = (e) => {
|
|||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleProjectSelection = (e) => {
|
||||||
if (!showAllEmployees) {
|
const newProjectId = e.target.value;
|
||||||
recallEmployeeData(showInactive, selectedProjectId);
|
setSelectedProject(newProjectId);
|
||||||
|
if (newProjectId) {
|
||||||
|
setShowAllEmployees(false);
|
||||||
}
|
}
|
||||||
}, [selectedProjectId, showInactive, showAllEmployees, recallEmployeeData]);
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedProject(selectedProjectId || "");
|
||||||
|
}, [selectedProjectId]);
|
||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if(employees.some((item) => item.id == msg.employeeId)){
|
if(employees.some((item) => item.id == msg.employeeId)){
|
||||||
setEmployeeList([]);
|
setEmployeeList([]);
|
||||||
recallEmployeeData(showInactive, showAllEmployees ? null : selectedProjectId); // Use selectedProjectId here
|
recallEmployeeData(showInactive);
|
||||||
}
|
}
|
||||||
},[employees, showInactive, showAllEmployees, selectedProjectId] // Add all relevant dependencies
|
},[employees]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -277,7 +288,7 @@ const handleAllEmployeesToggle = (e) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container-fluid">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
@ -332,7 +343,7 @@ const handleAllEmployeesToggle = (e) => {
|
|||||||
|
|
||||||
{/* Right side: Search + Export + Add Employee */}
|
{/* Right side: Search + Export + Add Employee */}
|
||||||
<div className="d-flex flex-wrap align-items-center justify-content-end gap-3 flex-grow-1">
|
<div className="d-flex flex-wrap align-items-center justify-content-end gap-3 flex-grow-1">
|
||||||
{/* Search Input - ALWAYS ENABLED */}
|
{/* Search */}
|
||||||
<div className="dataTables_filter">
|
<div className="dataTables_filter">
|
||||||
<label className="mb-0">
|
<label className="mb-0">
|
||||||
<input
|
<input
|
||||||
@ -381,7 +392,7 @@ const handleAllEmployeesToggle = (e) => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add Employee Button */}
|
{/* Add Employee */}
|
||||||
{Manage_Employee && (
|
{Manage_Employee && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
@ -395,6 +406,7 @@ const handleAllEmployeesToggle = (e) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<table
|
<table
|
||||||
className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap"
|
className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap"
|
||||||
id="DataTables_Table_0"
|
id="DataTables_Table_0"
|
||||||
@ -489,17 +501,7 @@ const handleAllEmployeesToggle = (e) => {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{/* Conditional messages for no data or no search results */}
|
{!loading && employeeList?.length === 0 && (
|
||||||
{!loading && displayData?.length === 0 && searchText && !showAllEmployees ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={8}>
|
|
||||||
<small className="muted">
|
|
||||||
'{searchText}' employee not found
|
|
||||||
</small>{" "}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : null}
|
|
||||||
{!loading && displayData?.length === 0 && (!searchText || showAllEmployees) ? (
|
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={8}
|
colSpan={8}
|
||||||
@ -508,58 +510,72 @@ const handleAllEmployeesToggle = (e) => {
|
|||||||
No Data Found
|
No Data Found
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : null}
|
)}
|
||||||
|
{!loading &&
|
||||||
|
employeeList &&
|
||||||
|
currentItems.length === 0 &&
|
||||||
|
employeeList.length !== 0 && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={8}>
|
||||||
|
<small className="muted">
|
||||||
|
'{searchText}' employee not found
|
||||||
|
</small>{" "}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Render current items */}
|
{currentItems &&
|
||||||
{currentItems && !loading && currentItems.map((item) => (
|
!loading &&
|
||||||
<tr className="odd" key={item.id}>
|
currentItems.map((item) => (
|
||||||
<td className="sorting_1" colSpan={2}>
|
<tr className="odd" key={item.id}>
|
||||||
<div className="d-flex justify-content-start align-items-center user-name">
|
<td className="sorting_1" colSpan={2}>
|
||||||
<Avatar
|
<div className="d-flex justify-content-start align-items-center user-name">
|
||||||
firstName={item.firstName}
|
<Avatar
|
||||||
lastName={item.lastName}
|
firstName={item.firstName}
|
||||||
></Avatar>
|
lastName={item.lastName}
|
||||||
<div className="d-flex flex-column">
|
></Avatar>
|
||||||
<a
|
<div className="d-flex flex-column">
|
||||||
onClick={() =>
|
<a
|
||||||
navigate(
|
onClick={() =>
|
||||||
`/employee/${item.id}?for=attendance`
|
navigate(
|
||||||
)
|
`/employee/${item.id}?for=attendance`
|
||||||
}
|
)
|
||||||
className="text-heading text-truncate cursor-pointer"
|
}
|
||||||
>
|
className="text-heading text-truncate cursor-pointer"
|
||||||
<span className="fw-normal">
|
>
|
||||||
{item.firstName} {item.middleName}{" "}
|
<span className="fw-normal">
|
||||||
{item.lastName}
|
{item.firstName} {item.middleName}{" "}
|
||||||
</span>
|
{item.lastName}
|
||||||
</a>
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</td>
|
<td className="text-start d-none d-sm-table-cell">
|
||||||
<td className="text-start d-none d-sm-table-cell">
|
{item.email ? (
|
||||||
{item.email ? (
|
<span className="text-truncate">
|
||||||
|
<i className="bx bxs-envelope text-primary me-2"></i>
|
||||||
|
|
||||||
|
{item.email}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-truncate text-italic">
|
||||||
|
NA
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="text-start d-none d-sm-table-cell">
|
||||||
<span className="text-truncate">
|
<span className="text-truncate">
|
||||||
<i className="bx bxs-envelope text-primary me-2"></i>
|
<i className="bx bxs-phone-call text-primary me-2"></i>
|
||||||
{item.email}
|
{item.phoneNumber}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
</td>
|
||||||
<span className="text-truncate text-italic">
|
<td className=" d-none d-sm-table-cell text-start">
|
||||||
NA
|
<span className="text-truncate">
|
||||||
|
<i className="bx bxs-wrench text-success me-2"></i>
|
||||||
|
{item.jobRole || "Not Assign Yet"}
|
||||||
</span>
|
</span>
|
||||||
)}
|
</td>
|
||||||
</td>
|
|
||||||
<td className="text-start d-none d-sm-table-cell">
|
|
||||||
<span className="text-truncate">
|
|
||||||
<i className="bx bxs-phone-call text-primary me-2"></i>
|
|
||||||
{item.phoneNumber}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className=" d-none d-sm-table-cell text-start">
|
|
||||||
<span className="text-truncate">
|
|
||||||
<i className="bx bxs-wrench text-success me-2"></i>
|
|
||||||
{item.jobRole || "Not Assign Yet"}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td className=" d-none d-md-table-cell">
|
<td className=" d-none d-md-table-cell">
|
||||||
{moment(item.joiningDate)?.format("DD-MMM-YYYY")}
|
{moment(item.joiningDate)?.format("DD-MMM-YYYY")}
|
||||||
@ -687,6 +703,7 @@ const handleAllEmployeesToggle = (e) => {
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -123,7 +123,7 @@ const EmployeeProfile = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="container-fluid">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
|
|||||||
@ -91,7 +91,7 @@ useEffect(() => {
|
|||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container-fluid">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
|
|||||||
@ -78,10 +78,14 @@ const ProjectDetails = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-xl-4 col-lg-5 col-md-5 mt-5">
|
<div className="col-xl-4 col-lg-5 col-md-5 mt-5">
|
||||||
<AboutProject ></AboutProject>
|
{/* About User */}
|
||||||
|
<AboutProject data={projects_Details}></AboutProject>
|
||||||
|
{/* About User */}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-xl-4 col-lg-5 col-md-5 mt-5">
|
<div className="col-xl-4 col-lg-5 col-md-5 mt-5">
|
||||||
|
{/* Profile Overview */}
|
||||||
<ProjectOverview project={projectId} />
|
<ProjectOverview project={projectId} />
|
||||||
|
{/* Profile Overview */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -91,7 +95,7 @@ const ProjectDetails = () => {
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-lg-12 col-xl-12">
|
<div className="col-lg-12 col-xl-12">
|
||||||
{/* Teams */}
|
{/* Teams */}
|
||||||
<Teams ></Teams>
|
<Teams project={projects_Details}></Teams>
|
||||||
{/* Teams */}
|
{/* Teams */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -131,12 +135,26 @@ const ProjectDetails = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(setProjectId(projectId));
|
dispatch(setProjectId(projectId));
|
||||||
|
// setProject(projects_Details);
|
||||||
|
// setProjectDetails(projects_Details);
|
||||||
}, [projects_Details, projectId]);
|
}, [projects_Details, projectId]);
|
||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if (msg.keyword === "Update_Project" && project.id === msg.response.id) {
|
if (msg.keyword === "Update_Project" && project.id === msg.response.id) {
|
||||||
|
// clearCacheKey("projectInfo")
|
||||||
|
// ProjectRepository.getProjectByprojectId(projectId)
|
||||||
|
// .then((response) => {
|
||||||
|
// setProjectDetails(response.data);
|
||||||
|
// setProject(response.data);
|
||||||
|
// cacheData("projectInfo", { projectId, data: response.data });
|
||||||
|
// setLoading(false);
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// console.error(error);
|
||||||
|
// setError("Failed to fetch data.");
|
||||||
|
// setLoading(false);
|
||||||
|
// });
|
||||||
refetch()
|
refetch()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -150,7 +168,7 @@ const ProjectDetails = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{}
|
{}
|
||||||
<div className="container-fluid">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
|
|||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||||
import { useProjects, useCreateProject } from "../../hooks/useProjects";
|
import { useProjects, useCreateProject } from "../../hooks/useProjects";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
// import {
|
// import {
|
||||||
// getCachedData,
|
// getCachedData,
|
||||||
@ -31,6 +32,7 @@ const ProjectList = () => {
|
|||||||
const [HasManageProject, setHasManageProject] = useState(
|
const [HasManageProject, setHasManageProject] = useState(
|
||||||
HasManageProjectPermission
|
HasManageProjectPermission
|
||||||
);
|
);
|
||||||
|
const dispatch = useDispatch();
|
||||||
const { mutate: createProject } = useCreateProject({
|
const { mutate: createProject } = useCreateProject({
|
||||||
onSuccessCallback: () => {
|
onSuccessCallback: () => {
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
@ -80,7 +82,24 @@ const ProjectList = () => {
|
|||||||
}
|
}
|
||||||
}, [loginUser, HasManageProjectPermission]);
|
}, [loginUser, HasManageProjectPermission]);
|
||||||
|
|
||||||
|
// const handleSubmitForm = (newProject, setloading, reset) => {
|
||||||
|
// ProjectRepository.manageProject(newProject)
|
||||||
|
// .then((response) => {
|
||||||
|
// const cachedProjects = getCachedData("projectslist") || [];
|
||||||
|
// const updatedProjects = [...cachedProjects, response.data];
|
||||||
|
// cacheData("projectslist", updatedProjects);
|
||||||
|
// setProjectList((prev) => [...prev, response.data]);
|
||||||
|
// setloading(false);
|
||||||
|
// reset();
|
||||||
|
// sortingProject(getCachedData("projectslist"));
|
||||||
|
// showToast("Project Created successfully.", "success");
|
||||||
|
// setShowModal(false);
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// showToast(error.message, "error");
|
||||||
|
// setShowModal(false);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
const handleSubmitForm = (newProject, setloading, reset) => {
|
const handleSubmitForm = (newProject, setloading, reset) => {
|
||||||
setloading(true);
|
setloading(true);
|
||||||
@ -140,227 +159,219 @@ const ProjectList = () => {
|
|||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container-fluid">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
{ label: "Projects", link: null },
|
{ label: "Projects", link: null },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div className="card cursor-pointer mb-5">
|
|
||||||
<div className="card-body p-2 pb-1">
|
|
||||||
<div className="d-flex flex-wrap justify-content-between align-items-start">
|
|
||||||
<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 className="d-flex gap-2 mb-2">
|
<div className="d-flex flex-wrap justify-content-between align-items-start mb-4">
|
||||||
<button
|
<div className="d-flex flex-wrap align-items-start">
|
||||||
type="button"
|
<div className="flex-grow-1 me-2 mb-2">
|
||||||
className={`btn btn-sm p-1 ${
|
<input
|
||||||
!listView ? "btn-primary" : "btn-outline-primary"
|
type="search"
|
||||||
}`}
|
className="form-control form-control-sm"
|
||||||
onClick={() => setListView(false)}
|
placeholder="Search projects..."
|
||||||
data-bs-toggle="tooltip"
|
value={searchTerm}
|
||||||
data-bs-custom-class="tooltip"
|
onChange={(e) => {
|
||||||
title="Card View"
|
setSearchTerm(e.target.value);
|
||||||
>
|
setCurrentPage(1);
|
||||||
<i className="bx bx-grid-alt fs-5"></i>
|
}}
|
||||||
</button>
|
/>
|
||||||
<button
|
</div>
|
||||||
type="button"
|
|
||||||
className={`btn btn-sm p-1 ${
|
|
||||||
listView ? "btn-primary" : "btn-outline-primary"
|
|
||||||
}`}
|
|
||||||
onClick={() => setListView(true)}
|
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
data-bs-custom-class="tooltip"
|
|
||||||
title="List View"
|
|
||||||
>
|
|
||||||
<i className="bx bx-list-ul fs-5"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="dropdown ms-3 mt-1">
|
<div className="d-flex gap-2 mb-2">
|
||||||
<a
|
<button
|
||||||
className="dropdown-toggle hide-arrow cursor-pointer p-1 mt-3 "
|
type="button"
|
||||||
data-bs-toggle="dropdown"
|
className={`btn btn-sm ${
|
||||||
aria-expanded="false"
|
!listView ? "btn-primary" : "btn-outline-primary"
|
||||||
data-bs-custom-class="tooltip"
|
}`}
|
||||||
title="Filter"
|
onClick={() => setListView(false)}
|
||||||
>
|
data-bs-toggle="tooltip"
|
||||||
<i className="fa-solid fa-filter fs-4"></i>
|
data-bs-offset="0,8"
|
||||||
</a>
|
data-bs-placement="top"
|
||||||
<ul className="dropdown-menu p-2 text-capitalize">
|
data-bs-custom-class="tooltip"
|
||||||
{[
|
title="Card View"
|
||||||
{
|
>
|
||||||
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
|
<i className="bx bx-grid-alt bx-sm"></i>
|
||||||
label: "Active",
|
</button>
|
||||||
},
|
<button
|
||||||
{
|
type="button"
|
||||||
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
|
className={`btn btn-sm ${
|
||||||
label: "On Hold",
|
listView ? "btn-primary" : "btn-outline-primary"
|
||||||
},
|
}`}
|
||||||
{
|
onClick={() => setListView(true)}
|
||||||
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
|
data-bs-toggle="tooltip"
|
||||||
label: "Inactive",
|
data-bs-offset="0,8"
|
||||||
},
|
data-bs-placement="top"
|
||||||
{
|
data-bs-custom-class="tooltip"
|
||||||
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
|
title="List View"
|
||||||
label: "Completed",
|
>
|
||||||
},
|
<i className="bx bx-list-ul bx-sm"></i>
|
||||||
].map(({ id, label }) => (
|
</button>
|
||||||
<li key={id}>
|
</div>
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div className="dropdown ms-3">
|
||||||
<button
|
<a
|
||||||
type="button"
|
className="dropdown-toggle hide-arrow cursor-pointer"
|
||||||
data-bs-toggle="tooltip"
|
data-bs-toggle="dropdown"
|
||||||
data-bs-offset="0,8"
|
aria-expanded="false"
|
||||||
data-bs-placement="top"
|
>
|
||||||
data-bs-custom-class="tooltip"
|
<i className="bx bx-filter bx-lg"></i>
|
||||||
title="Add New Project"
|
</a>
|
||||||
className={`p-1 me-2 bg-primary rounded-circle ${
|
<ul className="dropdown-menu p-2 text-capitalize">
|
||||||
!HasManageProject && "d-none"
|
{[
|
||||||
}`}
|
{
|
||||||
onClick={handleShow}
|
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
|
||||||
>
|
label: "Active",
|
||||||
<i className="bx bx-plus fs-4 text-white"></i>
|
},
|
||||||
</button>
|
{
|
||||||
</div>
|
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
|
||||||
|
label: "On Hold",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
|
||||||
|
label: "Inactive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
|
||||||
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm btn-primary ${
|
||||||
|
!HasManageProject && "d-none"
|
||||||
|
}`}
|
||||||
|
onClick={handleShow}
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
|
Create New Project
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading && <p className="text-center">Loading...</p>}
|
{loading && <p className="text-center">Loading...</p>}
|
||||||
{!loading && filteredProjects.length === 0 && !listView && (
|
{!loading && filteredProjects.length === 0 && !listView && (
|
||||||
<p className="text-center text-muted">No projects found.</p>
|
<p className="text-center text-muted">No projects found.</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{listView ? (
|
<div className="row">
|
||||||
<div className="card cursor-pointer">
|
{listView ? (
|
||||||
<div className="card-body p-2">
|
<div className="table-responsive text-nowrap py-2 ">
|
||||||
<div className="table-responsive text-nowrap py-2 ">
|
<table className="table px-2">
|
||||||
<table className="table m-3">
|
<thead>
|
||||||
<thead>
|
<tr>
|
||||||
|
<th className="text-start" colSpan={5}>
|
||||||
|
Project Name
|
||||||
|
</th>
|
||||||
|
<th className="mx-2 text-start">Contact Person</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 bx-sm"></i>
|
||||||
|
</a>
|
||||||
|
<ul className="dropdown-menu p-2 text-capitalize">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
|
||||||
|
label: "Active",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
|
||||||
|
label: "On Hold",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
|
||||||
|
label: "Inactive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
|
||||||
|
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>
|
<tr>
|
||||||
<th className="text-start" colSpan={5}>
|
<td colSpan="12" className="text-center py-4">
|
||||||
Project Name
|
No projects found
|
||||||
</th>
|
</td>
|
||||||
<th className="mx-2 text-start">Contact Person</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 bx-sm"></i>
|
|
||||||
</a>
|
|
||||||
<ul className="dropdown-menu p-2 text-capitalize">
|
|
||||||
{[
|
|
||||||
{
|
|
||||||
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
|
|
||||||
label: "Active",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
|
|
||||||
label: "On Hold",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
|
|
||||||
label: "Inactive",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
|
|
||||||
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>
|
</tr>
|
||||||
</thead>
|
) : (
|
||||||
<tbody className="table-border-bottom-0 overflow-auto ">
|
currentItems.map((project) => (
|
||||||
{currentItems.length === 0 ? (
|
<ProjectListView
|
||||||
<tr>
|
key={project.id}
|
||||||
<td colSpan="12" className="text-center py-4">
|
projectData={project}
|
||||||
No projects found
|
recall={sortingProject}
|
||||||
</td>
|
/>
|
||||||
</tr>
|
))
|
||||||
) : (
|
)}
|
||||||
currentItems.map((project) => (
|
</tbody>
|
||||||
<ProjectListView
|
</table>
|
||||||
key={project.id}
|
</div>
|
||||||
projectData={project}
|
) : (
|
||||||
recall={sortingProject}
|
currentItems.map((project) => (
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>{" "}
|
|
||||||
</div>{" "}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="row">
|
|
||||||
{currentItems.map((project) => (
|
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
key={project.id}
|
key={project.id}
|
||||||
projectData={project}
|
projectData={project}
|
||||||
recall={sortingProject}
|
recall={sortingProject}
|
||||||
/>
|
/>
|
||||||
))}
|
))
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
{!loading && totalPages > 1 && (
|
{!loading && totalPages > 1 && (
|
||||||
<nav>
|
<nav>
|
||||||
|
|||||||
@ -140,14 +140,14 @@ const ProjectListView = ({ projectData, recall }) => {
|
|||||||
|
|
||||||
<tr className={`py-8 ${isPending ? "bg-light opacity-50 pointer-events-none" : ""} `}>
|
<tr className={`py-8 ${isPending ? "bg-light opacity-50 pointer-events-none" : ""} `}>
|
||||||
<td className="text-start" colSpan={5}>
|
<td className="text-start" colSpan={5}>
|
||||||
<span
|
<strong
|
||||||
className="text-primary cursor-pointer"
|
className="text-primary cursor-pointer"
|
||||||
onClick={() => navigate(`/projects/${projectInfo.id}`)}
|
onClick={() => navigate(`/projects/${projectInfo.id}`)}
|
||||||
>
|
>
|
||||||
{projectInfo.shortName
|
{projectInfo.shortName
|
||||||
? `${projectInfo.name} (${projectInfo.shortName})`
|
? `${projectInfo.name} (${projectInfo.shortName})`
|
||||||
: projectInfo.name}
|
: projectInfo.name}
|
||||||
</span>
|
</strong>
|
||||||
</td>
|
</td>
|
||||||
<td className="text-start small">{projectInfo.contactPerson}</td>
|
<td className="text-start small">{projectInfo.contactPerson}</td>
|
||||||
<td className="small text-center">
|
<td className="small text-center">
|
||||||
|
|||||||
@ -1,20 +1,18 @@
|
|||||||
import { api } from "../utils/axiosClient";
|
import { api } from "../utils/axiosClient";
|
||||||
|
|
||||||
const AuthRepository = {
|
const AuthRepository = {
|
||||||
// Public routes (no auth token required)
|
login: (data) => api.post("/api/auth/login", data),
|
||||||
login: (data) => api.postPublic("/api/auth/login", data),
|
refreshToken: (data) => api.post("/api/auth/refresh-token", data),
|
||||||
refreshToken: (data) => api.postPublic("/api/auth/refresh-token", data),
|
|
||||||
forgotPassword: (data) => api.postPublic("/api/auth/forgot-password", data),
|
|
||||||
resetPassword: (data) => api.postPublic("/api/auth/reset-password", data),
|
|
||||||
sendOTP: (data) => api.postPublic("/api/auth/send-otp", data),
|
|
||||||
verifyOTP: (data) => api.postPublic("/api/auth/login-otp", data),
|
|
||||||
register: (data) => api.postPublic("/api/auth/register", data),
|
|
||||||
sendMail: (data) => api.postPublic("/api/auth/sendmail", data),
|
|
||||||
|
|
||||||
// Protected routes (require auth token)
|
|
||||||
logout: (data) => api.post("/api/auth/logout", data),
|
logout: (data) => api.post("/api/auth/logout", data),
|
||||||
profile: () => api.get("/api/user/profile"),
|
profile: () => api.get(`/api/user/profile`),
|
||||||
changepassword: (data) => api.post("/api/auth/change-password", data),
|
register: (data) => api.post("api/auth/register", data),
|
||||||
|
resetPassword: (data) => api.post("/api/auth/reset-password", data),
|
||||||
|
forgotPassword: (data) => api.post("/api/auth/forgot-password", data),
|
||||||
|
sendMail: (data) => api.post("/api/auth/sendmail", data),
|
||||||
|
changepassword: ( data ) => api.post( "/api/auth/change-password", data ),
|
||||||
|
sendOTP: ( data ) => api.post( 'api/auth/send-otp', data ),
|
||||||
|
verifyOTP:(data)=>api.post("api/auth/login-otp",data)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthRepository;
|
export default AuthRepository;
|
||||||
|
|||||||
@ -32,7 +32,4 @@ export const DirectoryRepository = {
|
|||||||
UpdateNote: (id, data) => api.put(`/api/directory/note/${id}`, data),
|
UpdateNote: (id, data) => api.put(`/api/directory/note/${id}`, data),
|
||||||
DeleteNote: (id, isActive) =>
|
DeleteNote: (id, isActive) =>
|
||||||
api.delete(`/api/directory/note/${id}?active=${isActive}`),
|
api.delete(`/api/directory/note/${id}?active=${isActive}`),
|
||||||
|
|
||||||
GetNotes: (pageSize, pageNumber) =>
|
|
||||||
api.get(`/api/directory/notes?pageSize=${pageSize}&pageNumber=${pageNumber}`),
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,11 +23,7 @@ const ProjectRepository = {
|
|||||||
deleteProject: ( id ) => api.delete( `/projects/${ id }` ),
|
deleteProject: ( id ) => api.delete( `/projects/${ id }` ),
|
||||||
getProjectsByEmployee: ( id ) => api.get( `/api/project/assigned-projects/${ id }` ),
|
getProjectsByEmployee: ( id ) => api.get( `/api/project/assigned-projects/${ id }` ),
|
||||||
updateProjectsByEmployee:(id,data)=>api.post(`/api/project/assign-projects/${id}`,data),
|
updateProjectsByEmployee:(id,data)=>api.post(`/api/project/assign-projects/${id}`,data),
|
||||||
projectNameList: () => api.get( "/api/project/list/basic" ),
|
projectNameList:()=>api.get("/api/project/list/basic")
|
||||||
|
|
||||||
getProjectDetails:(id)=>api.get(`/api/project/details/${id}`),
|
|
||||||
getProjectInfraByproject: ( id ) => api.get( `/api/project/infra-details/${ id }` ),
|
|
||||||
getProjectTasksByWorkArea:(id)=>api.get(`/api/project/tasks/${id}`)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TasksRepository = {
|
export const TasksRepository = {
|
||||||
|
|||||||
@ -72,7 +72,7 @@ export function startSignalR(loggedUser) {
|
|||||||
cacheData("hasReceived", false);
|
cacheData("hasReceived", false);
|
||||||
eventBus.emit("assign_project_one", data);
|
eventBus.emit("assign_project_one", data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.error("Error in cacheData:", e);
|
console.error("Error in cacheData:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eventBus.emit("assign_project_all", data);
|
eventBus.emit("assign_project_all", data);
|
||||||
@ -107,7 +107,9 @@ export function startSignalR(loggedUser) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
connection
|
connection
|
||||||
.start();
|
.start()
|
||||||
|
.then(() => console.log("SignalR connected"))
|
||||||
|
.catch((err) => console.error("SignalR error:", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopSignalR() {
|
export function stopSignalR() {
|
||||||
|
|||||||
@ -7,22 +7,18 @@ import { BASE_URL } from "./constants";
|
|||||||
const base_Url = BASE_URL
|
const base_Url = BASE_URL
|
||||||
|
|
||||||
export const axiosClient = axios.create({
|
export const axiosClient = axios.create({
|
||||||
baseURL: base_Url,
|
baseURL: base_Url, // Your Web API URL
|
||||||
withCredentials: false,
|
withCredentials: false, // Required if the API uses cookies
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json", // Specify the content type
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto retry failed requests (e.g., network issues)
|
|
||||||
axiosRetry(axiosClient, { retries: 3 });
|
axiosRetry(axiosClient, { retries: 3 });
|
||||||
|
|
||||||
// Request Interceptor — Add Bearer token if required
|
// Request interceptor to add Bearer token
|
||||||
axiosClient.interceptors.request.use(
|
axiosClient.interceptors.request.use(
|
||||||
async (config) => {
|
async (config) => {
|
||||||
const requiresAuth = config.authRequired !== false; // default to true
|
if (config.authRequired) {
|
||||||
|
|
||||||
if (requiresAuth) {
|
|
||||||
const token = localStorage.getItem("jwtToken");
|
const token = localStorage.getItem("jwtToken");
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers["Authorization"] = `Bearer ${token}`;
|
config.headers["Authorization"] = `Bearer ${token}`;
|
||||||
@ -31,24 +27,25 @@ axiosClient.interceptors.request.use(
|
|||||||
config._retry = false;
|
config._retry = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => Promise.reject(error)
|
(error) => Promise.reject(error)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 🔄 Response Interceptor — Handle 401, refresh token, etc.
|
// // Response interceptor to handle responses globally (optional)
|
||||||
|
// Add an interceptor to handle expired tokens
|
||||||
axiosClient.interceptors.response.use(
|
axiosClient.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
|
|
||||||
async (error) => {
|
async (error) => {
|
||||||
const originalRequest = error.config;
|
const originalRequest = error.config;
|
||||||
|
|
||||||
// Skip retry for public requests or already retried ones
|
// Prevent infinite loop
|
||||||
if (!originalRequest || originalRequest._retry || originalRequest.authRequired === false) {
|
if (!originalRequest || originalRequest._retry) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid showing multiple toasts
|
// Only show one toast per request
|
||||||
if (!originalRequest._toastShown) {
|
if (!originalRequest._toastShown) {
|
||||||
originalRequest._toastShown = true;
|
originalRequest._toastShown = true;
|
||||||
|
|
||||||
@ -64,6 +61,7 @@ axiosClient.interceptors.response.use(
|
|||||||
const isRefreshRequest = error.config.url.includes("refresh-token");
|
const isRefreshRequest = error.config.url.includes("refresh-token");
|
||||||
|
|
||||||
if (status === 401 && !isRefreshRequest) {
|
if (status === 401 && !isRefreshRequest) {
|
||||||
|
// Mark as retried to avoid loops
|
||||||
originalRequest._retry = true;
|
originalRequest._retry = true;
|
||||||
|
|
||||||
const refreshToken = localStorage.getItem("refreshToken");
|
const refreshToken = localStorage.getItem("refreshToken");
|
||||||
@ -76,7 +74,7 @@ axiosClient.interceptors.response.use(
|
|||||||
stopSignalR();
|
stopSignalR();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Refresh token call
|
// Refresh token
|
||||||
const res = await axiosClient.post("/api/Auth/refresh-token", {
|
const res = await axiosClient.post("/api/Auth/refresh-token", {
|
||||||
token: localStorage.getItem("jwtToken"),
|
token: localStorage.getItem("jwtToken"),
|
||||||
refreshToken,
|
refreshToken,
|
||||||
@ -84,14 +82,16 @@ axiosClient.interceptors.response.use(
|
|||||||
|
|
||||||
const { token, refreshToken: newRefreshToken } = res.data.data;
|
const { token, refreshToken: newRefreshToken } = res.data.data;
|
||||||
|
|
||||||
// Save updated tokens
|
// Save new tokens
|
||||||
localStorage.setItem("jwtToken", token);
|
localStorage.setItem("jwtToken", token);
|
||||||
localStorage.setItem("refreshToken", newRefreshToken);
|
localStorage.setItem("refreshToken", newRefreshToken);
|
||||||
|
|
||||||
startSignalR()
|
startSignalR()
|
||||||
// Set Authorization header
|
// Set Authorization header
|
||||||
originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
||||||
return axiosClient(originalRequest);
|
|
||||||
|
// Optional: Instead of retrying, you may choose to reload app or go to home
|
||||||
|
return axiosClient(originalRequest); // <== only retry once
|
||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
redirectToLogin();
|
redirectToLogin();
|
||||||
return Promise.reject(refreshError);
|
return Promise.reject(refreshError);
|
||||||
@ -101,12 +101,11 @@ axiosClient.interceptors.response.use(
|
|||||||
showToast("An unknown error occurred.", "error");
|
showToast("An unknown error occurred.", "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Generic API function
|
// Generic API Call
|
||||||
const apiRequest = async (method, url, data = {}, config = {}) => {
|
const apiRequest = async (method, url, data = {}, config = {}) => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosClient({
|
const response = await axiosClient({
|
||||||
@ -122,16 +121,15 @@ const apiRequest = async (method, url, data = {}, config = {}) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Exported API wrapper
|
|
||||||
export const api = {
|
export const api = {
|
||||||
// Public routes (no token required)
|
// For public routes like login, set authRequired: false
|
||||||
postPublic: (url, data = {}, customHeaders = {}) =>
|
postPublic: (url, data = {}, customHeaders = {}) =>
|
||||||
apiRequest("post", url, data, {
|
apiRequest("post", url, data, {
|
||||||
headers: { ...customHeaders },
|
headers: { ...customHeaders },
|
||||||
authRequired: false,
|
authRequired: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Authenticated routes
|
// For protected routes, authRequired defaults to true
|
||||||
get: (url, params = {}, customHeaders = {}) =>
|
get: (url, params = {}, customHeaders = {}) =>
|
||||||
apiRequest("get", url, params, {
|
apiRequest("get", url, params, {
|
||||||
headers: { ...customHeaders },
|
headers: { ...customHeaders },
|
||||||
@ -156,8 +154,7 @@ export const api = {
|
|||||||
authRequired: true,
|
authRequired: true,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
//export default axiosClient;
|
||||||
// Redirect helper
|
|
||||||
function redirectToLogin() {
|
function redirectToLogin() {
|
||||||
window.location.href = "/auth/login";
|
window.location.href = "/auth/login";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,9 +11,9 @@ export const VIEW_PROJECTS = "6ea44136-987e-44ba-9e5d-1cf8f5837ebc"
|
|||||||
|
|
||||||
export const MANAGE_EMPLOYEES = "a97d366a-c2bb-448d-be93-402bd2324566"
|
export const MANAGE_EMPLOYEES = "a97d366a-c2bb-448d-be93-402bd2324566"
|
||||||
|
|
||||||
export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373"
|
export const MANAGE_PROJECT_INFRA = "f2aee20a-b754-4537-8166-f9507b44585b"
|
||||||
|
|
||||||
export const VIEW_PROJECT_INFRA = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"
|
export const VIEW_PROJECT_INFRA = "c7b68e33-72f0-474f-bd96-77636427ecc8"
|
||||||
|
|
||||||
export const REGULARIZE_ATTENDANCE ="57802c4a-00aa-4a1f-a048-fd2f70dd44b6"
|
export const REGULARIZE_ATTENDANCE ="57802c4a-00aa-4a1f-a048-fd2f70dd44b6"
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user