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

This commit is contained in:
Kartik Sharma 2025-09-19 15:27:16 +05:30
commit ea350db98b
9 changed files with 455 additions and 108 deletions

View File

@ -0,0 +1,9 @@
import React from 'react'
const AssignOrg = () => {
return (
<div>AssignOrg</div>
)
}
export default AssignOrg

View File

@ -8,7 +8,9 @@ import {
import Modal from "../common/Modal"; import Modal from "../common/Modal";
import { import {
useCreateOrganization, useCreateOrganization,
useOrganizationBySPRID,
useOrganizationModal, useOrganizationModal,
useOrganizationsList,
} from "../../hooks/useOrganization"; } from "../../hooks/useOrganization";
import Label from "../common/Label"; import Label from "../common/Label";
import SelectMultiple from "../common/SelectMultiple"; import SelectMultiple from "../common/SelectMultiple";
@ -18,14 +20,26 @@ const ManageOrganization = ({
projectOrganizations = ["ee"], projectOrganizations = ["ee"],
organizationId = null, organizationId = null,
}) => { }) => {
const [step, setStep] = useState(1); // 1 = Service Provider, 2 = Org Details, 3 = Add New Org const [step, setStep] = useState(1);
const orgModal = useOrganizationModal(); const orgModal = useOrganizationModal();
const { data: services, isLoading } = useServices(); const { data: masterService, isLoading } = useServices();
const [searchText, setSearchText] = useState();
const [SPRID, setSPRID] = useState("");
const { data: orgList, isLoading: orgLoading } = useOrganizationsList(
20,
1,
true,
searchText
);
const { data: OrgListbySPRID, isLoading: isLoadingBySPRID } =
useOrganizationBySPRID(SPRID);
const [Organization, setOrganization] = useState({});
const method = useForm({ const method = useForm({
resolver: zodResolver(organizationSchema), resolver: zodResolver(organizationSchema),
defaultValues: defaultOrganizationValues, defaultValues: defaultOrganizationValues,
}); });
console.log(masterService);
const { const {
handleSubmit, handleSubmit,
@ -52,7 +66,7 @@ const ManageOrganization = ({
} }
if (step === 1) { if (step === 1) {
return projectOrganizations && projectOrganizations.length > 0 return projectOrganizations && projectOrganizations !== null
? "Add Organization" ? "Add Organization"
: "Find Organization"; : "Find Organization";
} }
@ -74,32 +88,82 @@ const ManageOrganization = ({
{step === 1 && ( {step === 1 && (
<div className="d-block"> <div className="d-block">
<div className="text-start mb-1"> <div className="text-start mb-1">
<Label className="text-secondary">Enter Service Provider ID</Label> <Label className="text-secondary">Find Organization</Label>
<input <input
type="text" type="text"
value={SPRID}
className="form-control form-control-sm w-auto" className="form-control form-control-sm w-auto"
placeholder="SPR - ID" placeholder="Enter Organization"
aria-describedby="search-label" aria-describedby="search-label"
/> />
</div> </div>
<div className="py-2 text-start"> <div className="py-2 text-tiny text-center">
{orgModal.orgData && (<p className="text-secondary">Don't have Service provder id, Select Service Provider <span className="text-primary text-decoration-underline cursor-pointer" onClick={()=>orgModal.orgData ? setStep(2):setStep(3)}>Choose Provider</span></p> <div className="d-flex flex-column gap-2 border-0 bg-none">
)} {orgList?.map((org) => (
<div className="list-group-item list-group-item-action d-flex align-items-center cursor-pointer border-0">
<div className="d-flex align-items-center justify-content-center me-3">
<i className="bx bx-building-house bx-md text-primary"></i>
</div>
<div className="w-100">
<div className="d-flex justify-content-between">
<div className="user-info text-start">
<h6 className="mb-1 fw-normal">{org.name}</h6>
<small className="text-body-secondary">
{org.contactPerson}
</small>
<div className="user-status">
<small>In Meeting</small>
</div>
</div>
<div className="add-btn">
<button
className="btn btn-primary btn-xs"
onClick={() => {
setOrganization(org);
setStep(3);
}}
>
Add
</button>
</div>
</div>
</div>
</div>
))}
</div>
{orgModal.orgData && (
<p className="text-secondary">
Don't have required organization, Please find using{" "}
<span
className="text-mutes cursor-pointer text-decoration-underline"
onClick={() => setStep(2)}
>
SPRID
</span>
</p>
)}
</div> </div>
{/* ======== org list ======*/} <div
{/* <div className="list-group mt-3"> className={`d-flex ${
<div projectOrganizations
className="list-group-item list-group-item-action cursor-pointer" ? "justify-content-end"
onClick={() => setStep(2)} : "justify-content-between"
> } text-secondary mt-3`}
<i className="bx bx-building-house me-2"></i> >
Sample Organization 1 {!projectOrganizations && (
</div> <button
</div> */} type="button"
className="btn btn-xs btn-outline-secondary"
onClick={() => setStep(1)}
>
<i className="bx bx-left-arrow-alt"></i> Back
</button>
)}
<div className="d-flex justify-content-center text-secondary mt-3">
<button <button
type="button" type="button"
className="btn btn-xs btn-secondary" className="btn btn-xs btn-secondary"
@ -115,21 +179,54 @@ const ManageOrganization = ({
{/* ---------- STEP 1: Service Provider From Own Other Tenant ---------- */} {/* ---------- STEP 1: Service Provider From Own Other Tenant ---------- */}
{step === 2 && ( {step === 2 && (
<div className="d-block"> <div className="d-block">
{/* Optional: dropdown if projectOrganizations exist */} <div className="text-start mb-1">
{/* Optional: dropdown if projectOrganizations exist */} <Label className="text-secondary">Find Organization</Label>
<p className="text-secondary">Select Tags</p> <input
type="text"
className="form-control form-control-sm w-auto"
placeholder="Enter Servicee Provider Id"
aria-describedby="search-label"
/>
</div>
{/* ======== org list ======*/} {/* ======== org list ======*/}
<div className="list-group mt-3"> <div className="d-flex flex-column gap-2 border-0 bg-none">
<div {OrgListbySPRID?.map((org) => (
className="list-group-item list-group-item-action cursor-pointer" <div className="list-group-item list-group-item-action d-flex align-items-center cursor-pointer border-0">
onClick={() => setStep(3)} <div className="d-flex align-items-center justify-content-center me-3">
> <i className="bx bx-building-house bx-md text-primary"></i>
<i className="bx bx-building-house me-2"></i> </div>
Sample Organization 1
</div> <div className="w-100">
<div className="d-flex justify-content-between">
<div className="user-info text-start">
<h6 className="mb-1 fw-normal">Icing sweet gummies</h6>
<small className="text-body-secondary">15 minutes</small>
<div className="user-status">
<small>In Meeting</small>
</div>
</div>
<div className="add-btn">
<button
className="btn btn-primary btn-xs"
onClick={() => setStep(3)}
>
Add
</button>
</div>
</div>
</div>
</div>
))}
</div> </div>
<div className="d-flex justify-content-center text-secondary mt-3"> <div className="d-flex justify-content-between gap-2 mt-3">
<button
type="button"
className="btn btn-xs btn-outline-secondary"
onClick={() => setStep(1)}
>
<i className="bx bx-left-arrow-alt"></i> Back
</button>
<button <button
type="button" type="button"
className="btn btn-xs btn-secondary" className="btn btn-xs btn-secondary"
@ -144,37 +241,100 @@ const ManageOrganization = ({
{/* ---------- STEP 2: Existing Organization Details ---------- */} {/* ---------- STEP 2: Existing Organization Details ---------- */}
{step === 3 && ( {step === 3 && (
<div> <div className="row text-black mb-3">
<p className="text-muted small"> <div className="col-12 mb-3"></div>
Show organization details here (from SPR list). User selects <div className="text-start mb-2">
services and clicks Add. <div className="text-muted">{Organization.name}</div>
</p> </div>
{/* Row 1 */}
<div className="mb-2"> <div className="col-md-6 mb-3">
<Label>Services Offered</Label> <div className="d-flex">
<ul className="list-group"> <label
<li className="list-group-item"> className="form-label me-2 mb-0 fw-semibold text-start"
<input type="checkbox" className="form-check-input me-2" /> style={{ minWidth: "130px" }}
Service 1 >
</li> Constact Person :
<li className="list-group-item"> </label>
<input type="checkbox" className="form-check-input me-2" /> <div className="text-muted">{Organization.name}</div>
Service 2 </div>
</li> </div>
</ul> <div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Contact Number :
</label>
<div className="text-muted">{Organization.contactNumber}</div>
</div>
</div>
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Email Address :
</label>
<div className="text-muted">{Organization.email}</div>
</div>
</div>
<div className="col-12 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start text-wrap"
style={{ maxWidth: "130px" }}
>
Service provider Id (SPRID) :
</label>
<div className="text-muted">{Organization.sprid}</div>
</div>
</div> </div>
<div className="d-flex justify-content-between mt-3"> <div className="col-12 mb-3">
<button <div className="d-flex">
type="button" <label
className="btn btn-sm btn-outline-secondary" className="form-label me-1 mb-0 fw-semibold text-start"
onClick={() => setStep(1)} style={{ minWidth: "130px" }}
> >
Back Address :
</button> </label>
<button type="button" className="btn btn-sm btn-primary"> <div className="text-muted text-start">
Add {Organization.address}
</button> </div>
</div>
</div>
<div className="text-black text-start">
<div className="mb-2">
<Label className="fs-6">Add Services</Label>
<ul className="list-group list-group-flush">
{masterService.data &&
masterService.data?.map((serv) => (
<li className="list-group-item py-1">
<input
type="checkbox"
className="form-check-input me-2"
/>
{serv.name}
</li>
))}
</ul>
</div>
<div className="d-flex justify-content-between mt-3">
<button
type="button"
className="btn btn-xs btn-outline-secondary"
onClick={() => setStep(1)}
>
<i className="bx bx-left-arrow-alt"></i> Back
</button>
<button type="button" className="btn btn-sm btn-primary">
Add
</button>
</div>
</div> </div>
</div> </div>
)} )}
@ -309,48 +469,3 @@ const ManageOrganization = ({
}; };
export default ManageOrganization; export default ManageOrganization;
// <div className="d-flex flex-column gap-2 border-0 bg-none">
// <div className="list-group-item list-group-item-action d-flex align-items-center cursor-pointer border-0">
// <div className="d-flex align-items-center justify-content-center me-3">
// <i className="bx bx-building-house bx-md text-primary"></i>
// </div>
// <div className="w-100">
// <div className="d-flex justify-content-between">
// <div className="user-info text-start">
// <h6 className="mb-1 fw-normal">Icing sweet gummies</h6>
// <small className="text-body-secondary">15 minutes</small>
// <div className="user-status">
// <small>In Meeting</small>
// </div>
// </div>
// <div className="add-btn">
// <button className="btn btn-primary btn-sm">Add</button>
// </div>
// </div>
// </div>
// </div>
// {/* Icon item */}
// <div className="list-group-item list-group-item-action d-flex align-items-center cursor-pointer border-0">
// <div className="d-flex align-items-center justify-content-center me-3">
// <i className="bx bx-building-house bx-md text-primary"></i>
// </div>
// <div className="w-100">
// <div className="d-flex justify-content-between">
// <div className="user-info text-start">
// <h6 className="mb-1 fw-normal">Icing sweet gummies</h6>
// <small className="text-body-secondary">15 minutes</small>
// <div className="user-status">
// <small>In Meeting</small>
// </div>
// </div>
// <div className="add-btn">
// <button className="btn btn-primary btn-sm">Add</button>
// </div>
// </div>
// </div>
// </div>
// </div>

View File

@ -0,0 +1,193 @@
const ManageOrganization1 = ({
projectOrganizations = [],
organizationId = null,
}) => {
const [step, setStep] = useState(1); // default = scenario decision
const orgModal = useOrganizationModal();
const { data: services, isLoading } = useServices();
const method = useForm({
resolver: zodResolver(organizationSchema),
defaultValues: defaultOrganizationValues,
});
const {
handleSubmit,
register,
reset,
formState: { errors },
} = method;
const { mutate: CreateOrganization, isPending } = useCreateOrganization(
() => {
reset(defaultOrganizationValues);
orgModal.onClose();
setStep(1); // reset to first step
}
);
// 🔹 Decide first step when modal opens
useEffect(() => {
if (orgModal.isOpen) {
if (organizationId) {
setStep(3); // update flow show org details directly
} else if (projectOrganizations && projectOrganizations.length > 0) {
setStep(1); // Scenario 1 from current tenant list
} else {
setStep(2); // Scenario 2 search with SPRID
}
}
}, [orgModal.isOpen, organizationId, projectOrganizations]);
const onSubmit = (OrgPayload) => {
CreateOrganization(OrgPayload);
};
const RenderTitle = useMemo(() => {
if (organizationId) return "Update Organization";
if (step === 1) return "Add Organization"; // current tenant
if (step === 2) return "Find Organization"; // search with SPRID
if (step === 3) return "Organization Details";
if (step === 4) return "Create Organization";
return "Manage Organization";
}, [step, organizationId]);
const contentBody = (
<div>
{/* ---------- STEP 1: From Current Tenant Organizations ---------- */}
{step === 1 && (
<div className="d-block">
<div className="list-group mt-3">
{projectOrganizations.map((org, idx) => (
<div
key={idx}
className="list-group-item list-group-item-action cursor-pointer"
onClick={() => setStep(3)}
>
<i className="bx bx-building-house me-2"></i>
{org}
</div>
))}
</div>
<div className="d-flex justify-content-between text-secondary mt-3">
<button
type="button"
className="btn btn-xs btn-outline-secondary"
onClick={() => setStep(2)} // jump to SPRID search
>
<i className="bx bx-search-alt"></i> Find with SPRID
</button>
<button
type="button"
className="btn btn-xs btn-secondary"
onClick={() => setStep(4)}
>
<i className="bx bx-plus-circle me-2"></i>
Add New Organization
</button>
</div>
</div>
)}
{/* ---------- STEP 2: Search by Service Provider ID ---------- */}
{step === 2 && (
<div className="d-block">
<div className="text-start mb-1">
<Label className="text-secondary">Enter Service Provider ID</Label>
<input
type="text"
className="form-control form-control-sm w-auto"
placeholder="SPR - ID"
/>
</div>
{/* Example SPR results */}
<div className="list-group mt-3">
<div
className="list-group-item list-group-item-action cursor-pointer"
onClick={() => setStep(3)}
>
<i className="bx bx-building-house me-2"></i>
Sample Organization (SPRID)
</div>
</div>
<div className="d-flex justify-content-between gap-2 mt-3">
<button
type="button"
className="btn btn-xs btn-outline-secondary"
onClick={() => setStep(1)}
>
<i className="bx bx-left-arrow-alt"></i> Back
</button>
<button
type="button"
className="btn btn-xs btn-secondary"
onClick={() => setStep(4)}
>
<i className="bx bx-plus-circle me-2"></i>
Add New Organization
</button>
</div>
</div>
)}
{/* ---------- STEP 3: Organization Details ---------- */}
{step === 3 && (
<div>
<p className="text-muted small">
Show organization details here (from SPR or tenant list). User
selects services and clicks Add.
</p>
<div className="mb-2">
<Label>Services Offered</Label>
<ul className="list-group">
<li className="list-group-item">
<input type="checkbox" className="form-check-input me-2" />
Service 1
</li>
<li className="list-group-item">
<input type="checkbox" className="form-check-input me-2" />
Service 2
</li>
</ul>
</div>
<div className="d-flex justify-content-between mt-3">
<button
type="button"
className="btn btn-sm btn-outline-secondary"
onClick={() => setStep(1)}
>
Back
</button>
<button type="button" className="btn btn-sm btn-primary">
Add
</button>
</div>
</div>
)}
{/* ---------- STEP 4: Create New Organization ---------- */}
{step === 4 && (
<FormProvider {...method}>
<form className="form" onSubmit={handleSubmit(onSubmit)}>
{/* same form as your code, unchanged */}
{/* ... */}
</form>
</FormProvider>
)}
</div>
);
return (
<Modal
isOpen={orgModal.isOpen}
onClose={orgModal.onClose}
title={RenderTitle}
body={contentBody}
/>
);
};
export default ManageOrganization;

View File

@ -7,7 +7,8 @@ const ProjectOrganizations = () => {
const selectedProject = useSelectedProject() const selectedProject = useSelectedProject()
return ( return (
<div className="card"> <div className="card">
<div className="d-flex justify-content-end px-2"> <div className="card-header">
<div className="d-flex justify-content-end px-2">
<button <button
type="button" type="button"
className="link-button btn btn-xs rounded-md link-button-sm m-1 btn-primary" className="link-button btn btn-xs rounded-md link-button-sm m-1 btn-primary"
@ -17,6 +18,7 @@ const ProjectOrganizations = () => {
Add Organization Add Organization
</button> </button>
</div> </div>
</div>
<div className="card-body"> <div className="card-body">
<p className="text-secondary"> <p className="text-secondary">

View File

@ -24,6 +24,15 @@ export const useOrganizationModal = () => {
}; };
}; };
export const useOrganizationBySPRID =(sprid)=>{
return useQuery({
queryKey:["organization by",sprid],
queryFn:async()=>await OrganizationRepository.getOrganizationBySPRID(sprid),
enabled:!!sprid
})
}
export const useOrganizationsList = ( export const useOrganizationsList = (
pageSize, pageSize,
pageNumber, pageNumber,

View File

@ -268,6 +268,18 @@ export const useProjectLevelEmployeePermission = (employeeId, projectId) => {
}); });
}; };
export const useProjectAssignedServices =(projectId)=>{
return useQuery({
queryKey: ["projectAssignedServices", projectId],
queryFn: async () => {
const resp = await ProjectRepository.getProjectAssignedServices(projectId);
return resp.data;
},
enabled:!!projectId,
});
}
// -- -------------Mutation------------------------------- // -- -------------Mutation-------------------------------
export const useCreateProject = ({ onSuccessCallback }) => { export const useCreateProject = ({ onSuccessCallback }) => {

View File

@ -98,7 +98,7 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
Pagination={ Pagination={
<Pagination <Pagination
currentPage={currentPage} currentPage={currentPage}
totalPages={data.totalPages} totalPages={data?.totalPages}
onPageChange={paginate} onPageChange={paginate}
/> />
} }

View File

@ -10,6 +10,8 @@ const OrganizationRepository = {
); );
}, },
getOrganizationBySPRID :()=>api.get(`/api/Organization/list?sprid=${sprid}`),
assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data) assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data)
}; };

View File

@ -45,7 +45,12 @@ const ProjectRepository = {
getProjectLevelModules:()=>api.get(`/api/Project/get/proejct-level/modules`), getProjectLevelModules:()=>api.get(`/api/Project/get/proejct-level/modules`),
getProjectLevelEmployeePermissions:(employeeId,projectId)=>api.get(`/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}`), getProjectLevelEmployeePermissions:(employeeId,projectId)=>api.get(`/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}`),
updateProjectLevelEmployeePermission:(data)=>api.post(`/api/Project/assign/project-level-permission`,data), updateProjectLevelEmployeePermission:(data)=>api.post(`/api/Project/assign/project-level-permission`,data),
getAllProjectLevelPermission:(projectId)=>api.get(`/api/Project/get/all/project-level-permission/${projectId}`) getAllProjectLevelPermission:(projectId)=>api.get(`/api/Project/get/all/project-level-permission/${projectId}`),
// Services
getProjectAssignedServices:(projectId)=>api.get(`/api/Project/get/assigned/services/${projectId}`)
}; };
export const TasksRepository = { export const TasksRepository = {