initially setup service provider form

This commit is contained in:
pramod mahajan 2025-09-18 19:23:24 +05:30
parent 7fa2ca9227
commit 1452e77bc5
8 changed files with 386 additions and 111 deletions

View File

@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import React from "react";
import React, { useMemo, useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import {
defaultOrganizationValues,
@ -14,7 +14,11 @@ import Label from "../common/Label";
import SelectMultiple from "../common/SelectMultiple";
import { useServices } from "../../hooks/masterHook/useMaster";
const ManageOrganization = () => {
const ManageOrganization = ({
projectOrganizations = ["ee"],
organizationId = null,
}) => {
const [step, setStep] = useState(1); // 1 = Service Provider, 2 = Org Details, 3 = Add New Org
const orgModal = useOrganizationModal();
const { data: services, isLoading } = useServices();
@ -34,123 +38,319 @@ const ManageOrganization = () => {
() => {
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.length > 0
? "Add Organization"
: "Find Organization";
}
if (step === 2) {
return "Organization Details";
}
if (step === 3) {
return "Create Organization";
}
return "Manage Organization"; // fallback
}, [step, orgModal?.orgData, organizationId]);
const contentBody = (
<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>
{/* ---------- STEP 1: Service Provider- Form Own Tenant list ---------- */}
{step === 1 && (
<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"
aria-describedby="search-label"
/>
</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="py-2 text-start">
{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>
<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>
{/* ======== org list ======*/}
{/* <div className="list-group mt-3">
<div
className="list-group-item list-group-item-action cursor-pointer"
onClick={() => setStep(2)}
>
<i className="bx bx-building-house me-2"></i>
Sample Organization 1
</div>
</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 className="d-flex justify-content-center text-secondary mt-3">
<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>
)}
<div className="mb-1 text-start">
<SelectMultiple
name="serviceIds"
label="Services"
required={true}
valueKey="id"
options={services?.data || []}
/>
{errors.serviceIds && (
<span className="danger-text">{errors.serviceIds.message}</span>
)}
</div>
{/* ---------- STEP 1: Service Provider From Own Other Tenant ---------- */}
{step === 2 && (
<div className="d-block">
{/* Optional: dropdown if projectOrganizations exist */}
{/* Optional: dropdown if projectOrganizations exist */}
<p className="text-secondary">Select Tags</p>
{/* ======== org list ======*/}
<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 1
</div>
</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 className="d-flex justify-content-center text-secondary mt-3">
<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>
)}
<div className="d-flex justify-content-end gap-2 my-2">
<button
type="button"
className="btn btn-sm btn-secondary"
onClick={orgModal.onClose}
disabled={isPending || isLoading}
>
Cancel
</button>
<button
type="submit"
className="btn btn-sm btn-primary"
disabled={isPending || isLoading}
>
{isPending ? "Please Wait..." : "Submit"}
</button>
{/* ---------- STEP 2: Existing Organization Details ---------- */}
{step === 3 && (
<div>
<p className="text-muted small">
Show organization details here (from SPR 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>
</form>
</FormProvider>
)}
{/* ---------- 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={services?.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="button"
className="btn btn-sm btn-secondary me-2"
onClick={orgModal.onClose}
disabled={isPending || isLoading}
>
Cancel
</button>
<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="Manage Organization"
title={RenderTitle}
body={contentBody}
/>
);
};
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

@ -43,6 +43,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
},
{ key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) },
{ key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam },
{ key: "organization", icon: "bx bx-buildings", label: "Organization"},
];
return (
<div className="nav-align-top">

View File

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

View File

@ -10,23 +10,44 @@ import showToast from "../services/toastService";
export const useOrganizationModal = () => {
const dispatch = useDispatch();
const isOpen = useSelector(
(state) => state.localVariables.OrganizationModal.isOpen
const { isOpen, orgData } = useSelector(
(state) => state.localVariables.OrganizationModal
);
return {
isOpen,
onOpen: () => dispatch(openOrgModal()),
orgData,
onOpen: (dat) =>
dispatch(openOrgModal({ isOpen: true, orgData: dat || null })),
onClose: () => dispatch(closeOrgModal()),
Togggle: () => dispatch(toggleOrgModal(isOpen)),
onToggle: () => dispatch(toggleOrgModal()),
};
};
export const useOrganizationsList = (pageSize, pageNumber, active, sprid, searchString="") => {
export const useOrganizationsList = (
pageSize,
pageNumber,
active,
sprid,
searchString = ""
) => {
return useQuery({
queryKey: ["organizationList", pageSize, pageNumber, active, sprid, searchString],
queryFn: async() => {
const resp = await OrganizationRepository.getOrganizationList(pageSize, pageNumber, active, sprid, searchString);
queryKey: [
"organizationList",
pageSize,
pageNumber,
active,
sprid,
searchString,
],
queryFn: async () => {
const resp = await OrganizationRepository.getOrganizationList(
pageSize,
pageNumber,
active,
sprid,
searchString
);
return resp.data;
},
keepPreviousData: true,
@ -34,12 +55,12 @@ export const useOrganizationsList = (pageSize, pageNumber, active, sprid, search
};
export const useCreateOrganization = (onSuccessCallback) => {
const useClient = useQueryClient()
const useClient = useQueryClient();
return useMutation({
mutationFn: async (OrgPayload) =>
await OrganizationRepository.createOrganization(OrgPayload),
onSuccess: (_, variables) => {
useClient.invalidateQueries({queryKey:["organizationList"]})
useClient.invalidateQueries({ queryKey: ["organizationList"] });
showToast("Organization created successfully", "success");
if (onSuccessCallback) onSuccessCallback();
},

View File

@ -69,7 +69,7 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
};
if (isError) return <div>{error.message}</div>;
if (isLoading) return gridView ? <CardViewContactSkeleton /> : <ListViewContactSkeleton />;
// if (isLoading) return gridView ? <CardViewContactSkeleton /> : <ListViewContactSkeleton />;
return (
<div className="row mt-5">
@ -94,7 +94,7 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
) : (
<div className="col-12">
<ListViewContact
data={data.data}
data={data?.data}
Pagination={
<Pagination
currentPage={currentPage}

View File

@ -23,6 +23,7 @@ import DirectoryPage from "../Directory/DirectoryPage";
import { useProjectAccess } from "../../hooks/useProjectAccess"; // new
import "./ProjectDetails.css";
import ProjectOrganizations from "../../components/Project/ProjectOrganizations";
const ProjectDetails = () => {
const projectId = useSelectedProject();
@ -96,6 +97,8 @@ const ProjectDetails = () => {
return <ProjectDocuments />;
case "setting":
return <ProjectSetting />;
case "organization":
return <ProjectOrganizations />;
default:
return <ComingSoonPage />;
}

View File

@ -9,6 +9,8 @@ const OrganizationRepository = {
}searchString=${searchString}`
);
},
assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data)
};
export default OrganizationRepository;

View File

@ -13,7 +13,8 @@ const localVariablesSlice = createSlice({
reload:false,
OrganizationModal:{
isOpen:false
isOpen:false,
orgData:null,
}
},
@ -37,15 +38,32 @@ const localVariablesSlice = createSlice({
state.defaultDateRange = action.payload;
},
openOrgModal: (state) => {
state.OrganizationModal.isOpen = true;
},
openOrgModal: (state, action) => {
debugger;
if (typeof action.payload === "boolean") {
state.OrganizationModal.isOpen = action.payload;
if (!action.payload) {
state.OrganizationModal.orgData = null;
}
} else if (typeof action.payload === "object") {
const { isOpen, orgData } = action.payload;
state.OrganizationModal.isOpen =
typeof isOpen === "boolean" ? isOpen : state.OrganizationModal.isOpen;
state.OrganizationModal.orgData =
orgData !== undefined ? orgData : state.OrganizationModal.orgData;
}
}
,
closeOrgModal: (state) => {
state.OrganizationModal.isOpen = false;
},
toggleOrgModal: (state) => {
state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen;
},
addedOrgModal:(state,action)=>{
state.OrganizationModal.orgData = action.payload;
}
},
});