fetched assigned organozation to project

This commit is contained in:
pramod mahajan 2025-09-20 13:59:20 +05:30
parent 158c934a9f
commit 7dafd4a45f
10 changed files with 176 additions and 422 deletions

View File

@ -1,5 +1,4 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
// import ManageOrganization from './components/Organization/ManageOrganization'
import { useOrganizationModal } from './hooks/useOrganization'; import { useOrganizationModal } from './hooks/useOrganization';
import OrganizationModal from './components/Organization/OrganizationModal'; import OrganizationModal from './components/Organization/OrganizationModal';

View File

@ -15,7 +15,7 @@ import {
} from "../../hooks/useOrganization"; } from "../../hooks/useOrganization";
const AssignOrg = ({ setStep }) => { const AssignOrg = ({ setStep }) => {
const { isOpen, orgData, startStep, onOpen, flowType, prevStep,onClose } = const { isOpen, orgData, startStep, onOpen, flowType, prevStep, onClose } =
useOrganizationModal(); useOrganizationModal();
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const { data: masterService, isLoading: isMasterserviceLoading } = const { data: masterService, isLoading: isMasterserviceLoading } =
@ -24,7 +24,9 @@ const AssignOrg = ({ setStep }) => {
useProjectAssignedServices(selectedProject); useProjectAssignedServices(selectedProject);
const { data: orgType, isLoading: orgLoading } = useOrganizationType(); const { data: orgType, isLoading: orgLoading } = useOrganizationType();
const { mutate: AssignToProject, isPending } = useAssignOrgToProject(()=>onClose()); const { mutate: AssignToProject, isPending } = useAssignOrgToProject(() =>
onClose()
);
const mergedServices = useMemo(() => { const mergedServices = useMemo(() => {
if (!masterService || !projectServices) return []; if (!masterService || !projectServices) return [];
@ -57,17 +59,17 @@ const AssignOrg = ({ setStep }) => {
AssignToProject(payload); AssignToProject(payload);
}; };
const handleEdit = () => { const handleEdit = () => {
onOpen({ startStep: 4 , orgData:orgData}); onOpen({ startStep: 4, orgData: orgData });
}; };
const handleBack =()=>{ const handleBack = () => {
if(prevStep == 1 && flowType == "assign"){ if (prevStep == 1 && flowType == "assign") {
onOpen({ startStep: prevStep }) onOpen({ startStep: prevStep });
}else if(prevStep == 1 && flowType == "assign"){ } else if (prevStep == 1 && flowType == "assign") {
onOpen({ startStep: 1 }) onOpen({ startStep: 1 });
}else{ } else {
onOpen({ startStep: 2 }) onOpen({ startStep: 2 });
} }
} };
if (isMasterserviceLoading || isLoading) if (isMasterserviceLoading || isLoading)
return <div className="text-center">Loading....</div>; return <div className="text-center">Loading....</div>;
return ( return (
@ -150,17 +152,32 @@ const AssignOrg = ({ setStep }) => {
<Label htmlFor="organizationTypeId" required> <Label htmlFor="organizationTypeId" required>
Organization Type Organization Type
</Label> </Label>
<select
{...register("organizationTypeId")} <div className="d-flex flex-wrap gap-3 mt-1">
className="form-select form-select-sm"
>
<option value="">Select type</option>
{orgType?.data.map((type) => ( {orgType?.data.map((type) => (
<option key={type.id} value={type.id}> <div
{type.name} key={type.id}
</option> className="form-check d-flex align-items-center gap-2 p-0 m-0"
>
<input
type="radio"
id={`organizationType-${type.id}`}
value={type.id}
{...register("organizationTypeId", {
required: "Please select an organization type",
})}
className="form-check-input m-0"
/>
<label
className="form-check-label m-0"
htmlFor={`organizationType-${type.id}`}
>
{type.name}
</label>
</div>
))} ))}
</select> </div>
{errors.organizationTypeId && ( {errors.organizationTypeId && (
<span className="danger-text"> <span className="danger-text">
{errors.organizationTypeId.message} {errors.organizationTypeId.message}
@ -169,7 +186,9 @@ const AssignOrg = ({ setStep }) => {
</div> </div>
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Select Services</label> <Label htmlFor="serviceId" required>
Select Services
</Label>
{mergedServices?.map((service) => ( {mergedServices?.map((service) => (
<div key={service.id} className="form-check"> <div key={service.id} className="form-check">
<input <input
@ -193,7 +212,7 @@ const AssignOrg = ({ setStep }) => {
<button <button
type="button" type="button"
className="btn btn-xs btn-outline-secondary" className="btn btn-xs btn-outline-secondary"
onClick={ handleBack} onClick={handleBack}
disabled={isPending} disabled={isPending}
> >
<i className="bx bx-left-arrow-alt"></i> Back <i className="bx bx-left-arrow-alt"></i> Back

View File

@ -1,371 +0,0 @@
import { zodResolver } from "@hookform/resolvers/zod";
import React, { useMemo, useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import {
defaultOrganizationValues,
organizationSchema,
} from "./OrganizationSchema";
import Modal from "../common/Modal";
import {
useCreateOrganization,
useOrganizationBySPRID,
useOrganizationModal,
useOrganizationsList,
} from "../../hooks/useOrganization";
import Label from "../common/Label";
import SelectMultiple from "../common/SelectMultiple";
import { useServices } from "../../hooks/masterHook/useMaster";
import AssignOrg from "./AssignOrg";
const ManageOrganization = ({
projectOrganizations ,
organizationId = null,
}) => {
const [step, setStep] = useState(1);
const orgModal = useOrganizationModal();
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({
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
}
);
const onSubmit = (OrgPayload) => {
CreateOrganization(OrgPayload);
};
const RenderTitle = useMemo(() => {
if (organizationId) {
return "Update Organization";
}
if (step === 1) {
return projectOrganizations && projectOrganizations !== null
? "Add Organization"
: "Find Organization";
}
if (step === 2) {
return "Organization Details";
}
if (step === 3) {
return "Assign Organization";
}
return "Manage Organization"; // fallback
}, [step, orgModal?.orgData, organizationId]);
const contentBody = (
<div>
{/* ---------- STEP 1: Service Provider- Form Own Tenant list ---------- */}
{step === 1 && (
<div className="d-block">
<div className="text-start mb-1">
<Label className="text-secondary">Find Organization</Label>
<input
type="text"
value={SPRID}
className="form-control form-control-sm w-auto"
placeholder="Enter Organization"
aria-describedby="search-label"
/>
</div>
<div className="py-2 text-tiny text-center">
<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
className={`d-flex ${
projectOrganizations
? "justify-content-end"
: "justify-content-between"
} text-secondary mt-3`}
>
{!projectOrganizations && (
<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 1: Service Provider From Own Other Tenant ---------- */}
{step === 2 && (
<div className="d-block">
<div className="text-start mb-1">
<Label className="text-secondary">Find Organization</Label>
<input
type="text"
className="form-control form-control-sm w-auto"
placeholder="Enter Servicee Provider Id"
aria-describedby="search-label"
/>
</div>
{/* ======== org list ======*/}
<div className="d-flex flex-column gap-2 border-0 bg-none">
{OrgListbySPRID?.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">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 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 2: Existing Organization Details ---------- */}
{step === 3 && (
<AssignOrg Organization={Organization} setStep={setStep}/>
)}
{/* ---------- STEP 3: Add New Organization ---------- */}
{step === 4 && (
<FormProvider {...method}>
<form className="form" onSubmit={handleSubmit(onSubmit)}>
<div className="mb-1 text-start">
<Label htmlFor="name" required>
Organization Name
</Label>
<input
className="form-control form-control-sm"
{...register("name")}
/>
{errors.name && (
<span className="danger-text">{errors.name.message}</span>
)}
</div>
<div className="mb-1 text-start">
<Label htmlFor="contactPerson" required>
Contact Person
</Label>
<input
className="form-control form-control-sm"
{...register("contactPerson")}
/>
{errors.contactPerson && (
<span className="danger-text">
{errors.contactPerson.message}
</span>
)}
</div>
<div className="mb-1 text-start">
<Label htmlFor="contactNumber" required>
Contact Number
</Label>
<input
className="form-control form-control-sm"
{...register("contactNumber")}
/>
{errors.contactNumber && (
<span className="danger-text">
{errors.contactNumber.message}
</span>
)}
</div>
<div className="mb-1 text-start">
<Label htmlFor="email" required>
Email Address
</Label>
<input
className="form-control form-control-sm"
{...register("email")}
/>
{errors.email && (
<span className="danger-text">{errors.email.message}</span>
)}
</div>
<div className="mb-1 text-start">
<SelectMultiple
name="serviceIds"
label="Services"
required
valueKey="id"
options={masterService?.data || []}
/>
{errors.serviceIds && (
<span className="danger-text">{errors.serviceIds.message}</span>
)}
</div>
<div className="mb-1 text-start">
<Label htmlFor="address" required>
Address
</Label>
<textarea
className="form-control form-control-sm"
{...register("address")}
rows={2}
/>
{errors.address && (
<span className="danger-text">{errors.address.message}</span>
)}
</div>
<div className="d-flex justify-content-between gap-2 my-2">
<button
type="button"
className="btn btn-sm btn-outline-secondary"
onClick={() => setStep(1)}
>
Back
</button>
<div>
<button
type="submit"
className="btn btn-sm btn-primary"
disabled={isPending || isLoading}
>
{isPending ? "Please Wait..." : "Submit"}
</button>
</div>
</div>
</form>
</FormProvider>
)}
</div>
);
return (
<Modal
isOpen={orgModal.isOpen}
onClose={orgModal.onClose}
title={RenderTitle}
body={contentBody}
/>
);
};
export default ManageOrganization;

View File

@ -13,7 +13,7 @@ import { OrgCardSkeleton } from "./OrganizationSkeleton";
// Zod schema: only allow exactly 4 digits // Zod schema: only allow exactly 4 digits
const OrgPicker2 = ({ title, placeholder }) => { const OrgPickerFromSPId = ({ title, placeholder }) => {
const {onClose, startStep, flowType, onOpen,prevStep } = useOrganizationModal(); const {onClose, startStep, flowType, onOpen,prevStep } = useOrganizationModal();
const { const {
@ -156,4 +156,4 @@ const handleOrg=(orgId)=>{
); );
}; };
export default OrgPicker2; export default OrgPickerFromSPId;

View File

@ -7,7 +7,7 @@ import { ITEMS_PER_PAGE } from "../../utils/constants";
import Label from "../common/Label"; import Label from "../common/Label";
import Pagination from "../common/Pagination"; import Pagination from "../common/Pagination";
const OrgPicker = ({ title }) => { const OrgPickerfromTenant = ({ title }) => {
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const { data, isLoading } = useOrganizationsList( const { data, isLoading } = useOrganizationsList(
@ -144,4 +144,4 @@ const OrgPicker = ({ title }) => {
); );
}; };
export default OrgPicker; export default OrgPickerfromTenant;

View File

@ -17,8 +17,8 @@ import SelectMultiple from "../common/SelectMultiple";
import { useServices } from "../../hooks/masterHook/useMaster"; import { useServices } from "../../hooks/masterHook/useMaster";
import AssignOrg from "./AssignOrg"; import AssignOrg from "./AssignOrg";
import ManagOrg from "./ManagOrg"; import ManagOrg from "./ManagOrg";
import OrgPicker from "./OrgPicker"; import OrgPickerFromSPId from "./OrgPickerFromSPId";
import OrgPicker2 from "./OrgPicker2"; import OrgPickerfromTenant from "./OrgPickerfromTenant";
const OrganizationModal = () => { const OrganizationModal = () => {
const { isOpen, orgData, startStep, onOpen, onClose, onToggle } = const { isOpen, orgData, startStep, onOpen, onClose, onToggle } =
@ -79,7 +79,7 @@ const OrganizationModal = () => {
<div> <div>
{/* ---------- STEP 1: Service Provider- Form Own Tenant list ---------- */} {/* ---------- STEP 1: Service Provider- Form Own Tenant list ---------- */}
{startStep === 1 && ( {startStep === 1 && (
<OrgPicker <OrgPickerfromTenant
title="Find Organization" title="Find Organization"
@ -87,7 +87,7 @@ const OrganizationModal = () => {
)} )}
{startStep === 2 && ( {startStep === 2 && (
<OrgPicker2 <OrgPickerFromSPId
title="Find Organization" title="Find Organization"
placeholder="Enter Service Provider Id" placeholder="Enter Service Provider Id"
projectOrganizations={orgData} projectOrganizations={orgData}

View File

@ -0,0 +1,99 @@
import React from "react";
import { useProjectAssignedOrganizations } from "../../../hooks/useProjects";
import { useSelectedProject } from "../../../slices/apiDataManager";
const ProjectAssignedOrgs = () => {
const selectedProject = useSelectedProject();
const { data, isLoading, isError, error } =
useProjectAssignedOrganizations(selectedProject);
const contactList = [
{
key: "name",
label: "Organization Name",
getValue: (org) => (
<div className="d-flex gap-2 py-1 ">
<i class="bx bx-buildings"></i>
<span
className="text-truncate d-inline-block "
style={{ maxWidth: "150px" }}
>
{org?.name || "N/A"}
</span>
</div>
),
align: "text-start",
},
{
key: "sprid",
label: "Service Provider Id",
getValue: (org) => (
<span
className="text-truncate d-inline-block"
style={{ maxWidth: "200px" }}
>
{org?.sprid || "N/A"}
</span>
),
align: "text-center",
},
{
key: "isActive",
label: "Status",
getValue: (org) => (
<span
className={`text-truncate d-inline-block badge bg-label-${org.isActive ? "primary" :"secondary"}`}
style={{ maxWidth: "80px" }}
>
{org?.isActive ? "Active" : "Inactive"}
</span>
),
align: "text-start",
},
];
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>{error.message}</div>;
return (
<div>
<div className="dataTables_wrapper no-footer mx-5 pb-2">
<table className="table dataTable text-nowrap">
<thead>
<tr className="table_header_border">
{contactList.map((col) => (
<th key={col.key} className={col.align}>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{Array.isArray(data) && data.length > 0 ? (
data.map((row, i) => (
<tr key={i}>
{contactList.map((col) => (
<td key={col.key} className={col.align}>
{col.getValue(row)}
</td>
))}
</tr>
))
) : (
<tr style={{ height: "200px" }}>
<td
colSpan={contactList.length}
className="text-center align-middle"
>
No contacts found
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
);
};
export default ProjectAssignedOrgs;

View File

@ -1,29 +1,28 @@
import React from "react"; import React from "react";
import { useOrganizationModal } from "../../hooks/useOrganization"; import { useOrganizationModal } from "../../hooks/useOrganization";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import ProjectAssignedOrgs from "./ProjectOrganization/ProjectAssignedOrgs";
const ProjectOrganizations = () => { const ProjectOrganizations = () => {
const {onOpen,startStep,flowType} = useOrganizationModal(); const { onOpen, startStep, flowType } = useOrganizationModal();
const selectedProject = useSelectedProject() const selectedProject = useSelectedProject();
return ( return (
<div className="card"> <div className="card">
<div className="card-header"> <div className="card-header">
<div className="d-flex justify-content-end px-2"> <div className="d-flex justify-content-end px-2">
<button <button
type="button" type="button"
className="link-button btn btn-xs rounded-md link-button-sm m-1 btn-primary" className="link-button btn btn-xs rounded-md link-button-sm m-1 btn-primary"
onClick={() => onOpen({startStep:1,flowType:"assign"})} onClick={() => onOpen({ startStep: 1, flowType: "assign" })}
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>
Add Organization Add Organization
</button> </button>
</div>
</div> </div>
</div>
<div className="card-body"> <div className="row">
<p className="text-secondary"> <ProjectAssignedOrgs />
Not found Organization connected with current Project
</p>
</div> </div>
</div> </div>
); );

View File

@ -268,7 +268,16 @@ export const useProjectLevelEmployeePermission = (employeeId, projectId) => {
}); });
}; };
export const useProjectAssignedOrganizations =(projectId)=>{
return useQuery({
queryKey: ["projectAssignedServices", projectId],
queryFn: async () => {
const resp = await ProjectRepository.getProjectAssignedOrganizations(projectId);
return resp.data;
},
enabled:!!projectId,
});
}
export const useProjectAssignedServices =(projectId)=>{ export const useProjectAssignedServices =(projectId)=>{
return useQuery({ return useQuery({
queryKey: ["projectAssignedServices", projectId], queryKey: ["projectAssignedServices", projectId],

View File

@ -49,8 +49,8 @@ const ProjectRepository = {
// Services // Services
getProjectAssignedServices:(projectId)=>api.get(`/api/Project/get/assigned/services/${projectId}`),
getProjectAssignedServices:(projectId)=>api.get(`/api/Project/get/assigned/services/${projectId}`) getProjectAssignedOrganizations:(projectId)=>api.get(`/api/Project/get/assigned/organization/${projectId}`)
}; };
export const TasksRepository = { export const TasksRepository = {